Merge "Statsd notifies listener with pendingintent."
diff --git a/Android.bp b/Android.bp
index 3d56254..c5d64fb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -578,7 +578,6 @@
         "wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl",
         "wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl",
         "wifi/java/android/net/wifi/IWifiScanner.aidl",
-        "wifi/java/android/net/wifi/IRttManager.aidl",
         "packages/services/PacProcessor/com/android/net/IProxyService.aidl",
         "packages/services/Proxy/com/android/net/IProxyCallback.aidl",
         "packages/services/Proxy/com/android/net/IProxyPortListener.aidl",
diff --git a/Android.mk b/Android.mk
index a78a01a..7e2f472 100644
--- a/Android.mk
+++ b/Android.mk
@@ -827,35 +827,35 @@
 
 # ==== hiddenapi lists =======================================
 
-# Copy blacklist and light greylist over into the build folder.
+# Copy light greylist and dark greylist over into the build folder.
 # This is for ART buildbots which need to mock these lists and have alternative
 # rules for building them. Other rules in the build system should depend on the
 # files in the build folder.
 
-$(eval $(call copy-one-file,frameworks/base/config/hiddenapi-blacklist.txt,\
-                            $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST)))
 $(eval $(call copy-one-file,frameworks/base/config/hiddenapi-light-greylist.txt,\
                             $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)))
+$(eval $(call copy-one-file,frameworks/base/config/hiddenapi-dark-greylist.txt,\
+                            $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)))
 
-# Generate dark greylist as private API minus (blacklist plus light greylist).
+# Generate blacklist as private API minus (light greylist plus dark greylist).
 
-$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST): PRIVATE_API := $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE)
-$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST): BLACKLIST := $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST)
-$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST): LIGHT_GREYLIST := $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)
-$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST): $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE) \
-                                              $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST) \
-                                              $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)
-	if [ ! -z "`comm -12 <(sort $(BLACKLIST)) <(sort $(LIGHT_GREYLIST))`" ]; then \
-		echo "There should be no overlap between $(BLACKLIST) and $(LIGHT_GREYLIST)" 1>&2; \
+$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST): PRIVATE_API := $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE)
+$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST): LIGHT_GREYLIST := $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)
+$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST): DARK_GREYLIST := $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)
+$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST): $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE) \
+                                          $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST) \
+                                          $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)
+	if [ ! -z "`comm -12 <(sort $(LIGHT_GREYLIST)) <(sort $(DARK_GREYLIST))`" ]; then \
+		echo "There should be no overlap between $(LIGHT_GREYLIST) and $(DARK_GREYLIST)" 1>&2; \
 		exit 1; \
-	elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(BLACKLIST))`" ]; then \
-		echo "$(BLACKLIST) must be a subset of $(PRIVATE_API)" 1>&2; \
-		exit 2; \
 	elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(LIGHT_GREYLIST))`" ]; then \
 		echo "$(LIGHT_GREYLIST) must be a subset of $(PRIVATE_API)" 1>&2; \
+		exit 2; \
+	elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(DARK_GREYLIST))`" ]; then \
+		echo "$(DARK_GREYLIST) must be a subset of $(PRIVATE_API)" 1>&2; \
 		exit 3; \
 	fi
-	comm -23 <(sort $(PRIVATE_API)) <(sort $(BLACKLIST) $(LIGHT_GREYLIST)) > $@
+	comm -23 <(sort $(PRIVATE_API)) <(sort $(LIGHT_GREYLIST) $(DARK_GREYLIST)) > $@
 
 # Include subdirectory makefiles
 # ============================================================
diff --git a/api/current.txt b/api/current.txt
index c03798f..9204289 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6739,6 +6739,7 @@
     field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455
     field public static final int TAG_CERT_AUTHORITY_INSTALLED = 210029; // 0x3346d
     field public static final int TAG_CERT_AUTHORITY_REMOVED = 210030; // 0x3346e
+    field public static final int TAG_CRYPTO_SELF_TEST_COMPLETED = 210031; // 0x3346f
     field public static final int TAG_KEYGUARD_DISABLED_FEATURES_SET = 210021; // 0x33465
     field public static final int TAG_KEYGUARD_DISMISSED = 210006; // 0x33456
     field public static final int TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT = 210007; // 0x33457
@@ -40504,6 +40505,7 @@
   public final class Call {
     method public void answer(int);
     method public void conference(android.telecom.Call);
+    method public void deflect(android.net.Uri);
     method public void disconnect();
     method public java.util.List<java.lang.String> getCannedTextResponses();
     method public java.util.List<android.telecom.Call> getChildren();
@@ -40614,6 +40616,7 @@
     field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = 3072; // 0xc00
     field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_RX = 1024; // 0x400
     field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
+    field public static final int CAPABILITY_SUPPORT_DEFLECT = 16777216; // 0x1000000
     field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
     field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
     field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
@@ -40761,6 +40764,7 @@
     method public void onAnswer();
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onCallEvent(java.lang.String, android.os.Bundle);
+    method public void onDeflect(android.net.Uri);
     method public void onDisconnect();
     method public void onExtrasChanged(android.os.Bundle);
     method public void onHandoverComplete();
@@ -40829,6 +40833,7 @@
     field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = 3072; // 0xc00
     field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_RX = 1024; // 0x400
     field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
+    field public static final int CAPABILITY_SUPPORT_DEFLECT = 33554432; // 0x2000000
     field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
     field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
     field public static final java.lang.String EVENT_CALL_MERGE_FAILED = "android.telecom.event.CALL_MERGE_FAILED";
diff --git a/api/system-current.txt b/api/system-current.txt
index cc36b54..32d235d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5036,22 +5036,19 @@
   }
 
   public class UiccSlotInfo implements android.os.Parcelable {
-    ctor public UiccSlotInfo(boolean, boolean, java.lang.String, int);
+    ctor public UiccSlotInfo(boolean, boolean, java.lang.String, int, int);
     method public int describeContents();
     method public java.lang.String getCardId();
     method public int getCardStateInfo();
     method public boolean getIsActive();
     method public boolean getIsEuicc();
+    method public int getLogicalSlotIdx();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CARD_STATE_INFO_ABSENT = 1; // 0x1
     field public static final int CARD_STATE_INFO_ERROR = 3; // 0x3
     field public static final int CARD_STATE_INFO_PRESENT = 2; // 0x2
     field public static final int CARD_STATE_INFO_RESTRICTED = 4; // 0x4
     field public static final android.os.Parcelable.Creator<android.telephony.UiccSlotInfo> CREATOR;
-    field public final java.lang.String cardId;
-    field public final int cardStateInfo;
-    field public final boolean isActive;
-    field public final boolean isEuicc;
   }
 
   public abstract class VisualVoicemailService extends android.app.Service {
@@ -5712,6 +5709,7 @@
     ctor public ImsCallSessionImplBase();
     method public void accept(int, android.telephony.ims.ImsStreamMediaProfile);
     method public void close();
+    method public void deflect(java.lang.String);
     method public void extendToConference(java.lang.String[]);
     method public java.lang.String getCallId();
     method public android.telephony.ims.ImsCallProfile getCallProfile();
diff --git a/api/test-current.txt b/api/test-current.txt
index 4cfe401..b02da04 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1038,6 +1038,7 @@
 
   public class AccessibilityNodeInfo implements android.os.Parcelable {
     method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
+    method public void writeToParcelNoRecycle(android.os.Parcel, int);
   }
 
   public final class AccessibilityWindowInfo implements android.os.Parcelable {
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index b46c5c1..90158a0 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -44,6 +44,7 @@
     src/external/KernelUidCpuActiveTimeReader.cpp \
     src/external/KernelUidCpuClusterTimeReader.cpp \
     src/external/StatsPullerManagerImpl.cpp \
+    src/external/puller_util.cpp \
     src/logd/LogEvent.cpp \
     src/logd/LogListener.cpp \
     src/logd/LogReader.cpp \
@@ -175,6 +176,7 @@
     tests/AnomalyMonitor_test.cpp \
     tests/anomaly/AnomalyTracker_test.cpp \
     tests/ConfigManager_test.cpp \
+    tests/external/puller_util_test.cpp \
     tests/indexed_priority_queue_test.cpp \
     tests/LogEntryMatcher_test.cpp \
     tests/LogReader_test.cpp \
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index f0eaeff..8483b02 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -181,7 +181,9 @@
     return toString().compare(that.toString()) < 0;
 };
 
-
+bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2) {
+    return EqualsTo(s1, s2);
+}
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 0e90f2a..183a995 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -77,6 +77,7 @@
     : mAnomalyMonitor(new AnomalyMonitor(MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS))
 {
     mUidMap = new UidMap();
+    StatsPuller::SetUidMap(mUidMap);
     mConfigManager = new ConfigManager();
     mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, time(nullptr), [this](const ConfigKey& key) {
         sp<IStatsCompanionService> sc = getStatsCompanionService();
diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp
index 41e4705..fc0ad7c 100644
--- a/cmds/statsd/src/external/StatsPuller.cpp
+++ b/cmds/statsd/src/external/StatsPuller.cpp
@@ -19,6 +19,7 @@
 
 #include "StatsPuller.h"
 #include "guardrail/StatsdStats.h"
+#include "puller_util.h"
 
 namespace android {
 namespace os {
@@ -26,6 +27,9 @@
 
 using std::lock_guard;
 
+sp<UidMap> StatsPuller::mUidMap = nullptr;
+void StatsPuller::SetUidMap(const sp<UidMap>& uidMap) { mUidMap = uidMap; }
+
 // ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
 StatsPuller::StatsPuller(const int tagId)
     : mTagId(tagId) {
@@ -54,7 +58,8 @@
     mLastPullTimeSec = curTime;
     bool ret = PullInternal(&mCachedData);
     if (ret) {
-        (*data) = mCachedData;
+      mergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId);
+      (*data) = mCachedData;
     }
     return ret;
 }
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index 3446f9b..82a8611 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -18,11 +18,14 @@
 
 #include <android/os/StatsLogEventWrapper.h>
 #include <utils/String16.h>
+#include <utils/RefBase.h>
 #include <mutex>
 #include <vector>
+#include "packages/UidMap.h"
 
-#include "logd/LogEvent.h"
 #include "guardrail/StatsdStats.h"
+#include "logd/LogEvent.h"
+#include "puller_util.h"
 
 using android::os::StatsLogEventWrapper;
 
@@ -30,7 +33,7 @@
 namespace os {
 namespace statsd {
 
-class StatsPuller {
+class StatsPuller : public virtual RefBase {
 public:
     StatsPuller(const int tagId);
 
@@ -44,7 +47,9 @@
     // Clear cache if elapsed time is more than cooldown time
     int ClearCacheIfNecessary(long timestampSec);
 
-protected:
+    static void SetUidMap(const sp<UidMap>& uidMap);
+
+   protected:
     // The atom tag id this puller pulls
     const int mTagId;
 
@@ -67,6 +72,8 @@
     long mLastPullTimeSec;
 
     int clearCache();
+
+    static sp<UidMap> mUidMap;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
index 19b3a86..0b772b3 100644
--- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define DEBUG true
+#define DEBUG false
 #include "Log.h"
 
 #include <android/os/IStatsCompanionService.h>
@@ -24,6 +24,9 @@
 #include "CpuTimePerUidFreqPuller.h"
 #include "CpuTimePerUidPuller.h"
 #include "ResourceHealthManagerPuller.h"
+#include "KernelUidCpuActiveTimeReader.h"
+#include "KernelUidCpuClusterTimeReader.h"
+#include "SubsystemSleepStatePuller.h"
 #include "StatsCompanionServicePuller.h"
 #include "StatsPullerManagerImpl.h"
 #include "StatsService.h"
@@ -44,60 +47,130 @@
 namespace os {
 namespace statsd {
 
+const std::map<int, PullAtomInfo> StatsPullerManagerImpl::kAllPullAtomInfo = {
+    // wifi_bytes_transfer
+    {android::util::WIFI_BYTES_TRANSFER,
+     {{2, 3, 4, 5},
+      {},
+      1,
+      new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}},
+    // wifi_bytes_transfer_by_fg_bg
+    {android::util::WIFI_BYTES_TRANSFER_BY_FG_BG,
+     {{3, 4, 5, 6},
+      {2},
+      1,
+      new StatsCompanionServicePuller(
+          android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}},
+    // mobile_bytes_transfer
+    {android::util::MOBILE_BYTES_TRANSFER,
+     {{2, 3, 4, 5},
+      {},
+      1,
+      new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}},
+    // mobile_bytes_transfer_by_fg_bg
+    {android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG,
+     {{3, 4, 5, 6},
+      {2},
+      1,
+      new StatsCompanionServicePuller(
+          android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}},
+    // bluetooth_bytes_transfer
+    {android::util::BLUETOOTH_BYTES_TRANSFER,
+     {{2, 3},
+      {},
+      1,
+      new StatsCompanionServicePuller(
+          android::util::BLUETOOTH_BYTES_TRANSFER)}},
+    // kernel_wakelock
+    {android::util::KERNEL_WAKELOCK,
+     {{},
+      {},
+      1,
+      new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
+    // subsystem_sleep_state
+    {android::util::SUBSYSTEM_SLEEP_STATE,
+     {{},
+      {},
+      1,
+      new StatsCompanionServicePuller(android::util::SUBSYSTEM_SLEEP_STATE)}},
+    // cpu_time_per_freq
+    {android::util::CPU_TIME_PER_FREQ,
+     {{3},
+      {2},
+      1,
+      new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
+    // cpu_time_per_uid
+    {android::util::CPU_TIME_PER_UID,
+     {{2, 3}, {}, 1, new CpuTimePerUidPuller()}},
+    // cpu_time_per_uid_freq
+    {android::util::CPU_TIME_PER_UID_FREQ,
+     {{3}, {2}, 1, new CpuTimePerUidFreqPuller()}},
+    // wifi_activity_energy_info
+    {android::util::WIFI_ACTIVITY_ENERGY_INFO,
+     {{},
+      {},
+      1,
+      new StatsCompanionServicePuller(
+          android::util::WIFI_ACTIVITY_ENERGY_INFO)}},
+    // modem_activity_info
+    {android::util::MODEM_ACTIVITY_INFO,
+     {{},
+      {},
+      1,
+      new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}},
+    // bluetooth_activity_info
+    {android::util::BLUETOOTH_ACTIVITY_INFO,
+     {{},
+      {},
+      1,
+      new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
+    // system_elapsed_realtime
+    {android::util::SYSTEM_ELAPSED_REALTIME,
+     {{},
+      {},
+      1,
+      new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME)}},
+    // system_uptime
+    {android::util::SYSTEM_UPTIME,
+     {{},
+      {},
+      1,
+      new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
+    // cpu_active_time
+    {android::util::CPU_ACTIVE_TIME,
+     {{3}, {2}, 1, new KernelUidCpuActiveTimeReader()}},
+    // cpu_cluster_time
+    {android::util::CPU_CLUSTER_TIME,
+     {{3}, {2}, 1, new KernelUidCpuClusterTimeReader()}},
+    // disk_space
+    {android::util::DISK_SPACE,
+     {{}, {}, 1, new StatsCompanionServicePuller(android::util::DISK_SPACE)}},
+    // remaining_battery_capacity
+    {android::util::REMAINING_BATTERY_CAPACITY,
+     {{},
+      {},
+      1,
+      new ResourceHealthManagerPuller(
+          android::util::REMAINING_BATTERY_CAPACITY)}},
+    // full_battery_capacity
+    {android::util::FULL_BATTERY_CAPACITY,
+     {{},
+      {},
+      1,
+      new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}}};
+
 StatsPullerManagerImpl::StatsPullerManagerImpl()
     : mCurrentPullingInterval(LONG_MAX) {
-    mPullers.insert({android::util::KERNEL_WAKELOCK,
-                     make_shared<StatsCompanionServicePuller>(android::util::KERNEL_WAKELOCK)});
-    mPullers.insert({android::util::WIFI_BYTES_TRANSFER,
-                     make_shared<StatsCompanionServicePuller>(android::util::WIFI_BYTES_TRANSFER)});
-    mPullers.insert(
-            {android::util::MOBILE_BYTES_TRANSFER,
-             make_shared<StatsCompanionServicePuller>(android::util::MOBILE_BYTES_TRANSFER)});
-    mPullers.insert({android::util::WIFI_BYTES_TRANSFER_BY_FG_BG,
-                     make_shared<StatsCompanionServicePuller>(
-                             android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)});
-    mPullers.insert({android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG,
-                     make_shared<StatsCompanionServicePuller>(
-                             android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)});
-    mPullers.insert(
-            {android::util::SUBSYSTEM_SLEEP_STATE,
-             make_shared<SubsystemSleepStatePuller>()});
-    mPullers.insert({android::util::CPU_TIME_PER_FREQ, make_shared<StatsCompanionServicePuller>(android::util::CPU_TIME_PER_FREQ)});
-    mPullers.insert({android::util::CPU_TIME_PER_UID, make_shared<CpuTimePerUidPuller>()});
-    mPullers.insert({android::util::CPU_TIME_PER_UID_FREQ, make_shared<CpuTimePerUidFreqPuller>()});
-    mPullers.insert(
-            {android::util::SYSTEM_ELAPSED_REALTIME,
-             make_shared<StatsCompanionServicePuller>(android::util::SYSTEM_ELAPSED_REALTIME)});
-    mPullers.insert({android::util::SYSTEM_UPTIME,
-                     make_shared<StatsCompanionServicePuller>(android::util::SYSTEM_UPTIME)});
-    mPullers.insert({android::util::DISK_SPACE,
-                     make_shared<StatsCompanionServicePuller>(android::util::DISK_SPACE)});
-    mPullers.insert(
-            {android::util::BLUETOOTH_ACTIVITY_INFO,
-             make_shared<StatsCompanionServicePuller>(android::util::BLUETOOTH_ACTIVITY_INFO)});
-
-    mPullers.insert(
-            {android::util::BLUETOOTH_BYTES_TRANSFER,
-             make_shared<StatsCompanionServicePuller>(android::util::BLUETOOTH_BYTES_TRANSFER)});
-    mPullers.insert(
-            {android::util::WIFI_ACTIVITY_ENERGY_INFO,
-             make_shared<StatsCompanionServicePuller>(android::util::WIFI_ACTIVITY_ENERGY_INFO)});
-    mPullers.insert({android::util::MODEM_ACTIVITY_INFO,
-                     make_shared<StatsCompanionServicePuller>(android::util::MODEM_ACTIVITY_INFO)});
-    mPullers.insert({android::util::REMAINING_BATTERY_CAPACITY,
-                     make_shared<ResourceHealthManagerPuller>(android::util::REMAINING_BATTERY_CAPACITY)});
-    mPullers.insert({android::util::FULL_BATTERY_CAPACITY,
-                     make_shared<ResourceHealthManagerPuller>(android::util::FULL_BATTERY_CAPACITY)});
     mStatsCompanionService = StatsService::getStatsCompanionService();
 }
 
 bool StatsPullerManagerImpl::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) {
     if (DEBUG) ALOGD("Initiating pulling %d", tagId);
 
-    if (mPullers.find(tagId) != mPullers.end()) {
-        bool ret = mPullers.find(tagId)->second->Pull(data);
-        ALOGD("pulled %d items", (int)data->size());
-        return ret;
+    if (kAllPullAtomInfo.find(tagId) != kAllPullAtomInfo.end()) {
+      bool ret = kAllPullAtomInfo.find(tagId)->second.puller->Pull(data);
+      ALOGD("pulled %d items", (int)data->size());
+      return ret;
     } else {
         ALOGD("Unknown tagId %d", tagId);
         return false;  // Return early since we don't know what to pull.
@@ -110,7 +183,7 @@
 }
 
 bool StatsPullerManagerImpl::PullerForMatcherExists(int tagId) const {
-    return mPullers.find(tagId) != mPullers.end();
+  return kAllPullAtomInfo.find(tagId) != kAllPullAtomInfo.end();
 }
 
 void StatsPullerManagerImpl::RegisterReceiver(int tagId, wp<PullDataReceiver> receiver,
@@ -201,16 +274,16 @@
 
 int StatsPullerManagerImpl::ForceClearPullerCache() {
     int totalCleared = 0;
-    for (auto puller : mPullers) {
-        totalCleared += puller.second->ForceClearCache();
+    for (const auto& pulledAtom : kAllPullAtomInfo) {
+        totalCleared += pulledAtom.second.puller->ForceClearCache();
     }
     return totalCleared;
 }
 
 int StatsPullerManagerImpl::ClearPullerCacheIfNecessary(long timestampSec) {
     int totalCleared = 0;
-    for (auto puller : mPullers) {
-        totalCleared += puller.second->ClearCacheIfNecessary(timestampSec);
+    for (const auto& pulledAtom : kAllPullAtomInfo) {
+        totalCleared += pulledAtom.second.puller->ClearCacheIfNecessary(timestampSec);
     }
     return totalCleared;
 }
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.h b/cmds/statsd/src/external/StatsPullerManagerImpl.h
index 3535fa3..76a4c14 100644
--- a/cmds/statsd/src/external/StatsPullerManagerImpl.h
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.h
@@ -32,6 +32,20 @@
 namespace os {
 namespace statsd {
 
+typedef struct {
+  // The field numbers of the fields that need to be summed when merging
+  // isolated uid with host uid.
+  std::vector<int> additiveFields;
+  // The field numbers of the fields that can't be merged when merging
+  // data belong to isolated uid and host uid.
+  std::vector<int> nonAdditiveFields;
+  // How long should the puller wait before doing an actual pull again. Default
+  // 1 sec. Set this to 0 if this is handled elsewhere.
+  long coolDownSec = 1;
+  // The actual puller
+  sp<StatsPuller> puller;
+} PullAtomInfo;
+
 class StatsPullerManagerImpl : public virtual RefBase {
 public:
     static StatsPullerManagerImpl& GetInstance();
@@ -53,7 +67,9 @@
 
     int ClearPullerCacheIfNecessary(long timestampSec);
 
-private:
+    const static std::map<int, PullAtomInfo> kAllPullAtomInfo;
+
+   private:
     StatsPullerManagerImpl();
 
     // use this to update alarm
@@ -61,9 +77,6 @@
 
     sp<IStatsCompanionService> get_stats_companion_service();
 
-    // mapping from simple matcher tagId to puller
-    std::map<int, std::shared_ptr<StatsPuller>> mPullers;
-
     typedef struct {
         // pull_interval_sec : last_pull_time_sec
         std::pair<uint64_t, uint64_t> timeInfo;
@@ -81,8 +94,6 @@
     // bucket size. All pulled metrics start pulling based on this time, so that they can be
     // correctly attributed to the correct buckets.
     long mTimeBaseSec;
-
-    LogEvent parse_pulled_data(String16 data);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp
new file mode 100644
index 0000000..7cfc1d48
--- /dev/null
+++ b/cmds/statsd/src/external/puller_util.cpp
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+#define DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include "StatsPullerManagerImpl.h"
+#include "field_util.h"
+#include "puller_util.h"
+#include "statslog.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::map;
+using std::shared_ptr;
+using std::vector;
+
+DimensionsValue* getFieldValue(shared_ptr<LogEvent> event, int tagId, int fieldNum) {
+  Field field;
+  buildSimpleAtomField(tagId, fieldNum, &field);
+  return event->findFieldValueOrNull(field);
+}
+
+bool shouldMerge(shared_ptr<LogEvent>& lhs, shared_ptr<LogEvent>& rhs,
+                 const vector<int>& nonAdditiveFields, int tagId) {
+  for (int f : nonAdditiveFields) {
+    DimensionsValue* lValue = getFieldValue(lhs, tagId, f);
+    DimensionsValue* rValue = getFieldValue(rhs, tagId, f);
+    if (!compareDimensionsValue(*lValue, *rValue)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// merge rhs to lhs
+void mergeEvent(shared_ptr<LogEvent>& lhs, shared_ptr<LogEvent>& rhs,
+                const vector<int>& additiveFields, int tagId) {
+  for (int f : additiveFields) {
+    DimensionsValue* lValue = getFieldValue(lhs, tagId, f);
+    DimensionsValue* rValue = getFieldValue(rhs, tagId, f);
+    if (lValue->has_value_int()) {
+      lValue->set_value_int(lValue->value_int() + rValue->value_int());
+    } else if (lValue->has_value_long()) {
+      lValue->set_value_long(lValue->value_long() + rValue->value_long());
+    }
+  }
+}
+
+// process all data and merge isolated with host if necessary
+void mergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data,
+                                const sp<UidMap>& uidMap, int tagId) {
+  if (StatsPullerManagerImpl::kAllPullAtomInfo.find(tagId) ==
+      StatsPullerManagerImpl::kAllPullAtomInfo.end()) {
+    VLOG("Unknown pull atom id %d", tagId);
+    return;
+  }
+  if (android::util::kAtomsWithUidField.find(tagId) ==
+      android::util::kAtomsWithUidField.end()) {
+    VLOG("No uid to merge for atom %d", tagId);
+    return;
+  }
+  const vector<int>& additiveFields =
+      StatsPullerManagerImpl::kAllPullAtomInfo.find(tagId)
+          ->second.additiveFields;
+  const vector<int>& nonAdditiveFields =
+      StatsPullerManagerImpl::kAllPullAtomInfo.find(tagId)
+          ->second.nonAdditiveFields;
+
+  // map of host uid to isolated uid data index in the original vector.
+  // because of non additive fields, there could be multiple of them that can't
+  // be merged into one
+  map<int, vector<int>> hostToIsolated;
+  // map of host uid to their position in the original vector
+  map<int, vector<int>> hostPosition;
+  vector<int> isolatedUidPos;
+  // all uids in the original vector
+  vector<int> allUids;
+  for (size_t i = 0; i < data.size(); i++) {
+    // uid field is always first primitive filed, if present
+    DimensionsValue* uidField = getFieldValue(data[i], tagId, 1);
+    if (!uidField) {
+      VLOG("Bad data for %d, %s", tagId, data[i]->ToString().c_str());
+      return;
+    }
+    int uid = uidField->value_int();
+    allUids.push_back(uid);
+    const int hostUid = uidMap->getHostUidOrSelf(uid);
+    if (hostUid != uid) {
+      uidField->set_value_int(hostUid);
+      hostToIsolated[hostUid].push_back(i);
+      isolatedUidPos.push_back(i);
+    }
+  }
+  vector<shared_ptr<LogEvent>> mergedData;
+  for (size_t i = 0; i < allUids.size(); i++) {
+    if (hostToIsolated.find(allUids[i]) != hostToIsolated.end()) {
+      hostPosition[allUids[i]].push_back(i);
+    } else if (std::find(isolatedUidPos.begin(), isolatedUidPos.end(), i) != isolatedUidPos.end()) {
+      continue;
+    } else {
+      mergedData.push_back(data[i]);
+    }
+  }
+  for (auto iter = hostToIsolated.begin(); iter != hostToIsolated.end();
+       iter++) {
+    int uid = iter->first;
+    vector<int>& isolated = hostToIsolated[uid];
+    vector<int> toBeMerged;
+    toBeMerged.insert(toBeMerged.begin(), isolated.begin(), isolated.end());
+    if (hostPosition.find(uid) != hostPosition.end()) {
+      vector<int>& host = hostPosition[uid];
+      toBeMerged.insert(toBeMerged.end(), host.begin(), host.end());
+    }
+    vector<bool> used(toBeMerged.size());
+    for (size_t i = 0; i < toBeMerged.size(); i++) {
+      if (used[i] == true) {
+        continue;
+      }
+      for (size_t j = i + 1; j < toBeMerged.size(); j++) {
+        shared_ptr<LogEvent>& lhs = data[toBeMerged[i]];
+        shared_ptr<LogEvent>& rhs = data[toBeMerged[j]];
+        if (shouldMerge(lhs, rhs, nonAdditiveFields, tagId)) {
+          mergeEvent(lhs, rhs, additiveFields, tagId);
+          used[j] = true;
+        }
+      }
+    }
+    for (size_t i = 0; i < toBeMerged.size(); i++) {
+      if (used[i] == false) {
+      mergedData.push_back(data[i]);
+    }
+    }
+  }
+  data.clear();
+  data = mergedData;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/external/puller_util.h b/cmds/statsd/src/external/puller_util.h
new file mode 100644
index 0000000..70d5321
--- /dev/null
+++ b/cmds/statsd/src/external/puller_util.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <vector>
+#include "HashableDimensionKey.h"
+#include "StatsPuller.h"
+#include "logd/LogEvent.h"
+#include "packages/UidMap.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void mergeIsolatedUidsToHostUid(std::vector<std::shared_ptr<LogEvent>>& data,
+                                const sp<UidMap>& uidMap, int tagId);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 1dcd853..c8857aa 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -294,6 +294,28 @@
     }
 }
 
+int LogEvent::GetInt(size_t key, status_t* err) const {
+  DimensionsValue value;
+  if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
+    *err = BAD_INDEX;
+    return 0;
+  }
+  const DimensionsValue* leafValue = getSingleLeafValue(&value);
+  switch (leafValue->value_case()) {
+    case DimensionsValue::ValueCase::kValueInt:
+      return leafValue->value_int();
+    case DimensionsValue::ValueCase::kValueLong:
+    case DimensionsValue::ValueCase::kValueBool:
+    case DimensionsValue::ValueCase::kValueFloat:
+    case DimensionsValue::ValueCase::kValueTuple:
+    case DimensionsValue::ValueCase::kValueStr:
+    case DimensionsValue::ValueCase::VALUE_NOT_SET: {
+      *err = BAD_TYPE;
+      return 0;
+    }
+  }
+}
+
 const char* LogEvent::GetString(size_t key, status_t* err) const {
     DimensionsValue value;
     if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index eb2c008..d521e09 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -76,6 +76,7 @@
      * Returns BAD_TYPE if the index is available but the data is the wrong type.
      */
     int64_t GetLong(size_t key, status_t* err) const;
+    int GetInt(size_t key, status_t* err) const;
     const char* GetString(size_t key, status_t* err) const;
     bool GetBool(size_t key, status_t* err) const;
     float GetFloat(size_t key, status_t* err) const;
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 3304f6c..f1da452 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -91,7 +91,7 @@
     void removeIsolatedUid(int isolatedUid, int parentUid);
 
     // Returns the host uid if it exists. Otherwise, returns the same uid that was passed-in.
-    int getHostUidOrSelf(int uid) const;
+    virtual int getHostUidOrSelf(int uid) const;
 
     // Gets the output. If every config key has received the output, then the output is cleared.
     UidMapping getOutput(const ConfigKey& key);
diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp
new file mode 100644
index 0000000..7d9c8a8
--- /dev/null
+++ b/cmds/statsd/tests/external/puller_util_test.cpp
@@ -0,0 +1,269 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "external/puller_util.h"
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+#include "../metrics/metrics_test_helper.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using namespace testing;
+using std::make_shared;
+using std::shared_ptr;
+using std::vector;
+using testing::Contains;
+/*
+ * Test merge isolated and host uid
+ */
+
+int uidAtomTagId = android::util::CPU_TIME_PER_UID_FREQ;
+int nonUidAtomTagId = android::util::SYSTEM_UPTIME;
+int timestamp = 1234;
+int isolatedUid = 30;
+int isolatedAdditiveData = 31;
+int isolatedNonAdditiveData = 32;
+int hostUid = 20;
+int hostAdditiveData = 21;
+int hostNonAdditiveData = 22;
+
+void extractIntoVector(vector<shared_ptr<LogEvent>> events,
+                      vector<vector<int>>& ret) {
+  ret.clear();
+  status_t err;
+  for (const auto& event : events) {
+    vector<int> vec;
+    vec.push_back(event->GetInt(1, &err));
+    vec.push_back(event->GetInt(2, &err));
+    vec.push_back(event->GetInt(3, &err));
+    ret.push_back(vec);
+  }
+}
+
+TEST(puller_util, MergeNoDimension) {
+  vector<shared_ptr<LogEvent>> inputData;
+  shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  // 30->22->31
+  event->write(isolatedUid);
+  event->write(hostNonAdditiveData);
+  event->write(isolatedAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  // 20->22->21
+  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  event->write(hostUid);
+  event->write(hostNonAdditiveData);
+  event->write(hostAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+  EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
+      .WillRepeatedly(Return(hostUid));
+  EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
+      .WillRepeatedly(ReturnArg<0>());
+  mergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+
+  vector<vector<int>> actual;
+  extractIntoVector(inputData, actual);
+  vector<int> expectedV1 = {20, 22, 52};
+  EXPECT_EQ(1, (int)actual.size());
+  EXPECT_THAT(actual, Contains(expectedV1));
+}
+
+TEST(puller_util, MergeWithDimension) {
+  vector<shared_ptr<LogEvent>> inputData;
+  shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  // 30->32->31
+  event->write(isolatedUid);
+  event->write(isolatedNonAdditiveData);
+  event->write(isolatedAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  // 20->32->21
+  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  event->write(hostUid);
+  event->write(isolatedNonAdditiveData);
+  event->write(hostAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  // 20->22->21
+  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  event->write(hostUid);
+  event->write(hostNonAdditiveData);
+  event->write(hostAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+  EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
+      .WillRepeatedly(Return(hostUid));
+  EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
+      .WillRepeatedly(ReturnArg<0>());
+  mergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+
+  vector<vector<int>> actual;
+  extractIntoVector(inputData, actual);
+  vector<int> expectedV1 = {20, 22, 21};
+  vector<int> expectedV2 = {20, 32, 52};
+  EXPECT_EQ(2, (int)actual.size());
+  EXPECT_THAT(actual, Contains(expectedV1));
+  EXPECT_THAT(actual, Contains(expectedV2));
+}
+
+TEST(puller_util, NoMergeHostUidOnly) {
+  vector<shared_ptr<LogEvent>> inputData;
+  shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  // 20->32->31
+  event->write(hostUid);
+  event->write(isolatedNonAdditiveData);
+  event->write(isolatedAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  // 20->22->21
+  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  event->write(hostUid);
+  event->write(hostNonAdditiveData);
+  event->write(hostAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+  EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
+      .WillRepeatedly(Return(hostUid));
+  EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
+      .WillRepeatedly(ReturnArg<0>());
+  mergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+
+  // 20->32->31
+  // 20->22->21
+  vector<vector<int>> actual;
+  extractIntoVector(inputData, actual);
+  vector<int> expectedV1 = {20, 32, 31};
+  vector<int> expectedV2 = {20, 22, 21};
+  EXPECT_EQ(2, (int)actual.size());
+  EXPECT_THAT(actual, Contains(expectedV1));
+  EXPECT_THAT(actual, Contains(expectedV2));
+}
+
+TEST(puller_util, IsolatedUidOnly) {
+  vector<shared_ptr<LogEvent>> inputData;
+  shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  // 30->32->31
+  event->write(hostUid);
+  event->write(isolatedNonAdditiveData);
+  event->write(isolatedAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  // 30->22->21
+  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  event->write(hostUid);
+  event->write(hostNonAdditiveData);
+  event->write(hostAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+  EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
+      .WillRepeatedly(Return(hostUid));
+  EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
+      .WillRepeatedly(ReturnArg<0>());
+  mergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+
+  // 20->32->31
+  // 20->22->21
+  vector<vector<int>> actual;
+  extractIntoVector(inputData, actual);
+  vector<int> expectedV1 = {20, 32, 31};
+  vector<int> expectedV2 = {20, 22, 21};
+  EXPECT_EQ(2, (int)actual.size());
+  EXPECT_THAT(actual, Contains(expectedV1));
+  EXPECT_THAT(actual, Contains(expectedV2));
+}
+
+TEST(puller_util, MultipleIsolatedUidToOneHostUid) {
+  vector<shared_ptr<LogEvent>> inputData;
+  shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  // 30->32->31
+  event->write(isolatedUid);
+  event->write(isolatedNonAdditiveData);
+  event->write(isolatedAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  // 31->32->21
+  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  event->write(isolatedUid + 1);
+  event->write(isolatedNonAdditiveData);
+  event->write(hostAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  // 20->32->21
+  event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+  event->write(hostUid);
+  event->write(isolatedNonAdditiveData);
+  event->write(hostAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+  EXPECT_CALL(*uidMap, getHostUidOrSelf(_)).WillRepeatedly(Return(hostUid));
+  mergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+
+  vector<vector<int>> actual;
+  extractIntoVector(inputData, actual);
+  vector<int> expectedV1 = {20, 32, 73};
+  EXPECT_EQ(1, (int)actual.size());
+  EXPECT_THAT(actual, Contains(expectedV1));
+}
+
+TEST(puller_util, NoNeedToMerge) {
+  vector<shared_ptr<LogEvent>> inputData;
+  shared_ptr<LogEvent> event =
+      make_shared<LogEvent>(nonUidAtomTagId, timestamp);
+  // 32
+  event->write(isolatedNonAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  event = make_shared<LogEvent>(nonUidAtomTagId, timestamp);
+  // 22
+  event->write(hostNonAdditiveData);
+  event->init();
+  inputData.push_back(event);
+
+  sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+  mergeIsolatedUidsToHostUid(inputData, uidMap, nonUidAtomTagId);
+
+  EXPECT_EQ(2, (int)inputData.size());
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index 0a97456..b48de54 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -15,6 +15,7 @@
 
 #include "src/condition/ConditionWizard.h"
 #include "src/external/StatsPullerManager.h"
+#include "src/packages/UidMap.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -40,6 +41,11 @@
     MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data));
 };
 
+class MockUidMap : public UidMap {
+ public:
+  MOCK_CONST_METHOD1(getHostUidOrSelf, int(int uid));
+};
+
 HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value);
 MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, std::string value);
 
diff --git a/config/hiddenapi-blacklist.txt b/config/hiddenapi-blacklist.txt
deleted file mode 100644
index e69de29..0000000
--- a/config/hiddenapi-blacklist.txt
+++ /dev/null
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 2b648ea..eb26026 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -609,18 +609,19 @@
 
     /**
      * A key was pressed down.
+     * <p>
+     * If the focused view didn't want this event, this method is called.
+     * <p>
+     * Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK}
+     * and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE
+     * KEYCODE_ESCAPE} to later handle them in {@link #onKeyUp}.
      * 
-     * <p>If the focused view didn't want this event, this method is called.
-     *
-     * <p>The default implementation consumed the KEYCODE_BACK to later
-     * handle it in {@link #onKeyUp}.
-     *
      * @see #onKeyUp
      * @see android.view.KeyEvent
      */
     @Override
     public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_BACK) {
+        if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
             event.startTracking();
             return true;
         }
@@ -640,16 +641,18 @@
 
     /**
      * A key was released.
-     * 
-     * <p>The default implementation handles KEYCODE_BACK to close the
-     * dialog.
+     * <p>
+     * Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK}
+     * and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE
+     * KEYCODE_ESCAPE} to close the dialog.
      *
      * @see #onKeyDown
-     * @see KeyEvent
+     * @see android.view.KeyEvent
      */
     @Override
     public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
+        if ((keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE)
+                && event.isTracking()
                 && !event.isCanceled()) {
             onBackPressed();
             return true;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f4836b7..c805658 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4616,10 +4616,15 @@
                 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
                 for (int i=0; i<N; i++) {
                     Action action = mActions.get(i);
-                    validRemoteInput |= hasValidRemoteInput(action);
+                    boolean actionHasValidInput = hasValidRemoteInput(action);
+                    validRemoteInput |= actionHasValidInput;
 
                     final RemoteViews button = generateActionButton(action, emphazisedMode,
                             i % 2 != 0, p.ambient);
+                    if (actionHasValidInput) {
+                        // Clear the drawable
+                        button.setInt(R.id.action0, "setBackgroundResource", 0);
+                    }
                     big.addView(R.id.actions, button);
                 }
             } else {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 87f32b2..aa52cde 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -38,7 +38,6 @@
 import android.content.Context;
 import android.content.IRestrictionsManager;
 import android.content.RestrictionsManager;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.CrossProfileApps;
 import android.content.pm.ICrossProfileApps;
 import android.content.pm.IShortcutService;
@@ -90,7 +89,6 @@
 import android.net.lowpan.LowpanManager;
 import android.net.nsd.INsdManager;
 import android.net.nsd.NsdManager;
-import android.net.wifi.IRttManager;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.IWifiScanner;
 import android.net.wifi.RttManager;
@@ -117,7 +115,6 @@
 import android.os.IUserManager;
 import android.os.IncidentManager;
 import android.os.PowerManager;
-import android.os.Process;
 import android.os.RecoverySystem;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
@@ -639,13 +636,13 @@
 
         registerService(Context.WIFI_RTT_SERVICE, RttManager.class,
                 new CachedServiceFetcher<RttManager>() {
-            @Override
-            public RttManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT_SERVICE);
-                IRttManager service = IRttManager.Stub.asInterface(b);
-                return new RttManager(ctx.getOuterContext(), service,
-                        ConnectivityThread.getInstanceLooper());
-            }});
+                @Override
+                public RttManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+                    IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT_RANGING_SERVICE);
+                    IWifiRttManager service = IWifiRttManager.Stub.asInterface(b);
+                    return new RttManager(ctx.getOuterContext(),
+                            new WifiRttManager(ctx.getOuterContext(), service));
+                }});
 
         registerService(Context.WIFI_RTT_RANGING_SERVICE, WifiRttManager.class,
                 new CachedServiceFetcher<WifiRttManager>() {
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 08effd9..202b894 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -77,6 +77,7 @@
             TAG_KEY_DESTRUCTION,
             TAG_CERT_AUTHORITY_INSTALLED,
             TAG_CERT_AUTHORITY_REMOVED,
+            TAG_CRYPTO_SELF_TEST_COMPLETED,
     })
     public @interface SecurityLogTag {}
 
@@ -400,6 +401,14 @@
             SecurityLogTags.SECURITY_USER_RESTRICTION_REMOVED;
 
     /**
+     * Indicates that cryptographic functionality self test has completed. The log entry contains an
+     * {@code Integer} payload, indicating the result of the test (0 if the test failed, 1 if
+     * succeeded) and accessible via {@link SecurityEvent#getData()}.
+     */
+    public static final int TAG_CRYPTO_SELF_TEST_COMPLETED =
+            SecurityLogTags.SECURITY_CRYPTO_SELF_TEST_COMPLETED;
+
+    /**
      * Event severity level indicating that the event corresponds to normal workflow.
      */
     public static final int LEVEL_INFO = 1;
@@ -529,6 +538,7 @@
                 case TAG_USER_RESTRICTION_REMOVED:
                     return LEVEL_INFO;
                 case TAG_CERT_AUTHORITY_REMOVED:
+                case TAG_CRYPTO_SELF_TEST_COMPLETED:
                     return getSuccess() ? LEVEL_INFO : LEVEL_ERROR;
                 case TAG_CERT_AUTHORITY_INSTALLED:
                 case TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT:
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index be62678..b64b7e3 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -34,4 +34,5 @@
 210027 security_user_restriction_added          (package|3),(admin_user|1),(restriction|3)
 210028 security_user_restriction_removed        (package|3),(admin_user|1),(restriction|3)
 210029 security_cert_authority_installed        (success|1),(subject|3)
-210030 security_cert_authority_removed          (success|1),(subject|3)
\ No newline at end of file
+210030 security_cert_authority_removed          (success|1),(subject|3)
+210031 security_crypto_self_test_completed      (success|1)
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 09a46b8..0342c93 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -468,6 +468,18 @@
         dest.writeBoolean(mOverlayIsStatic);
         dest.writeInt(compileSdkVersion);
         dest.writeString(compileSdkVersionCodename);
+        writeSigningCertificateHistoryToParcel(dest, parcelableFlags);
+    }
+
+    private void writeSigningCertificateHistoryToParcel(Parcel dest, int parcelableFlags) {
+        if (signingCertificateHistory != null) {
+            dest.writeInt(signingCertificateHistory.length);
+            for (int i = 0; i < signingCertificateHistory.length; i++) {
+                dest.writeTypedArray(signingCertificateHistory[i], parcelableFlags);
+            }
+        } else {
+            dest.writeInt(-1);
+        }
     }
 
     public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -523,6 +535,7 @@
         mOverlayIsStatic = source.readBoolean();
         compileSdkVersion = source.readInt();
         compileSdkVersionCodename = source.readString();
+        readSigningCertificateHistoryFromParcel(source);
 
         // The component lists were flattened with the redundant ApplicationInfo
         // instances omitted.  Distribute the canonical one here as appropriate.
@@ -534,6 +547,16 @@
         }
     }
 
+    private void readSigningCertificateHistoryFromParcel(Parcel source) {
+        int len = source.readInt();
+        if (len != -1) {
+            signingCertificateHistory = new Signature[len][];
+            for (int i = 0; i < len; i++) {
+                signingCertificateHistory[i] = source.createTypedArray(Signature.CREATOR);
+            }
+        }
+    }
+
     private void propagateApplicationInfo(ApplicationInfo appInfo, ComponentInfo[] components) {
         if (components != null) {
             for (ComponentInfo ci : components) {
diff --git a/core/java/android/content/pm/VerifierDeviceIdentity.java b/core/java/android/content/pm/VerifierDeviceIdentity.java
index a8cdb6a..90be6f31 100644
--- a/core/java/android/content/pm/VerifierDeviceIdentity.java
+++ b/core/java/android/content/pm/VerifierDeviceIdentity.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.UnsupportedEncodingException;
 import java.security.SecureRandom;
 import java.util.Random;
@@ -86,6 +88,7 @@
      * @return verifier device identity based on the input from the provided
      *         random number generator
      */
+    @VisibleForTesting
     static VerifierDeviceIdentity generate(Random rng) {
         long identity = rng.nextLong();
         return new VerifierDeviceIdentity(identity);
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index fd0e5ae..5d7cf1e 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1537,6 +1537,7 @@
         public static final int STATE2_BLUETOOTH_ON_FLAG = 1<<22;
         public static final int STATE2_CAMERA_FLAG = 1<<21;
         public static final int STATE2_BLUETOOTH_SCAN_FLAG = 1 << 20;
+        public static final int STATE2_CELLULAR_HIGH_TX_POWER_FLAG = 1 << 19;
 
         public static final int MOST_INTERESTING_STATES2 =
                 STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK
@@ -2353,9 +2354,11 @@
                 WIFI_SUPPL_STATE_NAMES, WIFI_SUPPL_STATE_SHORT_NAMES),
         new BitDescription(HistoryItem.STATE2_CAMERA_FLAG, "camera", "ca"),
         new BitDescription(HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG, "ble_scan", "bles"),
+        new BitDescription(HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG,
+                "cellular_high_tx_power", "Chtp"),
         new BitDescription(HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK,
             HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT, "gps_signal_quality", "Gss",
-            new String[] { "poor", "good"}, new String[] { "poor", "good"}),
+            new String[] { "poor", "good"}, new String[] { "poor", "good"})
     };
 
     public static final String[] HISTORY_EVENT_NAMES = new String[] {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7fd75c9..84996e0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7396,7 +7396,8 @@
          */
         public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode";
 
-        private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR = BOOLEAN_VALIDATOR;
+        private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR =
+                new SettingsValidators.InclusiveIntegerRangeValidator(0, 2);
 
         /**
          * Control the color temperature of Night Display, represented in Kelvin.
diff --git a/core/java/android/transition/ArcMotion.java b/core/java/android/transition/ArcMotion.java
index da14834..172c837 100644
--- a/core/java/android/transition/ArcMotion.java
+++ b/core/java/android/transition/ArcMotion.java
@@ -216,7 +216,13 @@
 
         boolean isMovingUpwards = startY > endY;
 
-        if ((Math.abs(deltaX) < Math.abs(deltaY))) {
+        if (deltaY == 0) {
+            ex = dx;
+            ey = dy + (Math.abs(deltaX) * 0.5f * mMinimumHorizontalTangent);
+        } else if (deltaX == 0) {
+            ex = dx + (Math.abs(deltaY) * 0.5f * mMinimumVerticalTangent);
+            ey = dy;
+        } else if ((Math.abs(deltaX) < Math.abs(deltaY))) {
             // Similar triangles bfa and bde mean that (ab/fb = eb/bd)
             // Therefore, eb = ab * bd / fb
             // ab = hypotenuse
@@ -254,7 +260,7 @@
         float maximumArcDist2 = midDist2 * mMaximumTangent * mMaximumTangent;
 
         float newArcDistance2 = 0;
-        if (arcDist2 < minimumArcDist2) {
+        if (arcDist2 != 0 && arcDist2 < minimumArcDist2) {
             newArcDistance2 = minimumArcDist2;
         } else if (arcDist2 > maximumArcDist2) {
             newArcDistance2 = maximumArcDist2;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 410cdc6..1ead0b4 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -37,13 +37,13 @@
     private static final Map<String, String> DEFAULT_FLAGS;
     static {
         DEFAULT_FLAGS = new HashMap<>();
-        DEFAULT_FLAGS.put("device_info_v2", "true");
         DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
         DEFAULT_FLAGS.put("settings_battery_v2", "true");
         DEFAULT_FLAGS.put("settings_battery_display_app_list", "false");
         DEFAULT_FLAGS.put("settings_zone_picker_v2", "true");
-        DEFAULT_FLAGS.put("settings_about_phone_v2", "false");
+        DEFAULT_FLAGS.put("settings_about_phone_v2", "true");
         DEFAULT_FLAGS.put("settings_bluetooth_while_driving", "false");
+        DEFAULT_FLAGS.put("settings_data_usage_v2", "false");
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 79fc134..0910c11 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+
 import static java.lang.Math.max;
 
 import android.animation.AnimatorInflater;
@@ -906,6 +907,13 @@
      */
     private static boolean sThrowOnInvalidFloatProperties;
 
+    /**
+     * Prior to P, {@code #startDragAndDrop} accepts a builder which produces an empty drag shadow.
+     * Currently zero size SurfaceControl cannot be created thus we create a dummy 1x1 surface
+     * instead.
+     */
+    private static boolean sAcceptZeroSizeDragShadow;
+
     /** @hide */
     @IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO})
     @Retention(RetentionPolicy.SOURCE)
@@ -4840,6 +4848,8 @@
 
             sAlwaysAssignFocus = targetSdkVersion < Build.VERSION_CODES.P;
 
+            sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P;
+
             sCompatibilityDone = true;
         }
     }
@@ -8378,7 +8388,7 @@
             AccessibilityNodeProvider provider, AccessibilityNodeInfo info,
             boolean forAutofill) {
         structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
-                null, null, null);
+                null, null, info.getViewIdResourceName());
         Rect rect = structure.getTempRect();
         info.getBoundsInParent(rect);
         structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
@@ -8418,6 +8428,13 @@
         CharSequence cname = info.getClassName();
         structure.setClassName(cname != null ? cname.toString() : null);
         structure.setContentDescription(info.getContentDescription());
+        if (forAutofill) {
+            final int maxTextLength = info.getMaxTextLength();
+            if (maxTextLength != -1) {
+                structure.setMaxTextLength(maxTextLength);
+            }
+            structure.setHint(info.getHintText());
+        }
         if ((info.getText() != null || info.getError() != null)) {
             structure.setText(info.getText(), info.getTextSelectionStart(),
                     info.getTextSelectionEnd());
@@ -8428,7 +8445,8 @@
                     final AutofillValue autofillValue = AutofillValue.forText(structure.getText());
                     structure.setAutofillValue(autofillValue);
                     if (info.isPassword()) {
-                        structure.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
+                        structure.setInputType(InputType.TYPE_CLASS_TEXT
+                                | InputType.TYPE_TEXT_VARIATION_PASSWORD);
                     }
                 } else {
                     structure.setDataIsSensitive(false);
@@ -23619,8 +23637,7 @@
          * constructor variant is only useful when the {@link #onProvideShadowMetrics(Point, Point)}
          * and {@link #onDrawShadow(Canvas)} methods are also overridden in order
          * to supply the drag shadow's dimensions and appearance without
-         * reference to any View object. If they are not overridden, then the result is an
-         * invisible drag shadow.
+         * reference to any View object.
          */
         public DragShadowBuilder() {
             mView = new WeakReference<View>(null);
@@ -23774,6 +23791,9 @@
         // Create 1x1 surface when zero surface size is specified because SurfaceControl.Builder
         // does not accept zero size surface.
         if (shadowSize.x == 0  || shadowSize.y == 0) {
+            if (!sAcceptZeroSizeDragShadow) {
+                throw new IllegalStateException("Drag shadow dimensions must be positive");
+            }
             shadowSize.x = 1;
             shadowSize.y = 1;
         }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 23e7d61..417a725 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3173,6 +3173,15 @@
      */
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        writeToParcelNoRecycle(parcel, flags);
+        // Since instances of this class are fetched via synchronous i.e. blocking
+        // calls in IPCs we always recycle as soon as the instance is marshaled.
+        recycle();
+    }
+
+    /** @hide */
+    @TestApi
+    public void writeToParcelNoRecycle(Parcel parcel, int flags) {
         // Write bit set of indices of fields with values differing from default
         long nonDefaultFields = 0;
         int fieldIndex = 0; // index of the current field
@@ -3406,10 +3415,6 @@
                         + " vs " + fieldIndex);
             }
         }
-
-        // Since instances of this class are fetched via synchronous i.e. blocking
-        // calls in IPCs we always recycle as soon as the instance is marshaled.
-        recycle();
     }
 
     /**
@@ -3557,7 +3562,7 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
             mContentDescription = parcel.readCharSequence();
         }
-        if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readString();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
 
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 63a9990..5131a8a 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,6 +16,10 @@
 
 package android.view.autofill;
 
+import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.view.autofill.Helper.sDebug;
+import static android.view.autofill.Helper.sVerbose;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -47,13 +51,14 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.accessibility.AccessibilityWindowInfo;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
+
 import org.xmlpull.v1.XmlPullParserException;
-import sun.misc.Cleaner;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -66,9 +71,7 @@
 import java.util.List;
 import java.util.Objects;
 
-import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
-import static android.view.autofill.Helper.sDebug;
-import static android.view.autofill.Helper.sVerbose;
+import sun.misc.Cleaner;
 
 // TODO: use java.lang.ref.Cleaner once Android supports Java 9
 
@@ -616,10 +619,8 @@
     /**
      * @hide
      */
-    public boolean isCompatibilityModeEnabled() {
-        synchronized (mLock) {
-            return mCompatibilityBridge != null;
-        }
+    public boolean isCompatibilityModeEnabledLocked() {
+        return mCompatibilityBridge != null;
     }
 
     /**
@@ -1381,7 +1382,8 @@
             @NonNull AutofillValue value, int flags) {
         if (sVerbose) {
             Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
-                    + ", flags=" + flags + ", state=" + getStateAsStringLocked());
+                    + ", flags=" + flags + ", state=" + getStateAsStringLocked()
+                    + ", compatMode=" + isCompatibilityModeEnabledLocked());
         }
         if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
             if (sVerbose) {
@@ -1392,7 +1394,7 @@
         }
         try {
             final AutofillClient client = getClient();
-            if (client == null) return; // NOTE: getClient() already logd it..
+            if (client == null) return; // NOTE: getClient() already logged it..
 
             mSessionId = mService.startSession(client.autofillClientGetActivityToken(),
                     mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
@@ -1939,7 +1941,8 @@
         pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
         pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
         pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
-        pw.print(pfx); pw.print("compat mode enabled: "); pw.println(isCompatibilityModeEnabled());
+        pw.print(pfx); pw.print("compat mode enabled: "); pw.println(
+                isCompatibilityModeEnabledLocked());
         pw.print(pfx); pw.print("debug: "); pw.print(sDebug);
         pw.print(" verbose: "); pw.println(sVerbose);
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index ecab15f..467e2a4 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -678,7 +678,8 @@
     StopwatchTimer mCameraOnTimer;
 
     int mGpsSignalQualityBin = -1;
-    final StopwatchTimer[] mGpsSignalQualityTimer =
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected final StopwatchTimer[] mGpsSignalQualityTimer =
         new StopwatchTimer[GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS];
 
     int mPhoneSignalStrengthBin = -1;
@@ -760,6 +761,8 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected StopwatchTimer mBluetoothScanTimer;
 
+    boolean mIsCellularTxPowerHigh = false;
+
     int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
     long mMobileRadioActiveStartTime;
     StopwatchTimer mMobileRadioActiveTimer;
@@ -11211,6 +11214,9 @@
             Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
         }
 
+        // Add modem tx power to history.
+        addModemTxPowerToHistory(activityInfo);
+
         // Grab a separate lock to acquire the network stats, which may do I/O.
         NetworkStats delta = null;
         synchronized (mModemNetworkLock) {
@@ -11385,6 +11391,44 @@
             new BluetoothActivityEnergyInfo(0, 0, 0, 0, 0, 0);
 
     /**
+     * Add modem tx power to history
+     * Device is said to be in high cellular transmit power when it has spent most of the transmit
+     * time at the highest power level.
+     * @param activityInfo
+     */
+    private void addModemTxPowerToHistory(final ModemActivityInfo activityInfo) {
+        if (activityInfo == null) {
+            return;
+        }
+        int[] txTimeMs = activityInfo.getTxTimeMillis();
+        if (txTimeMs == null || txTimeMs.length != ModemActivityInfo.TX_POWER_LEVELS) {
+            return;
+        }
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        int levelMaxTimeSpent = 0;
+        for (int i = 1; i < txTimeMs.length; i++) {
+            if (txTimeMs[i] > txTimeMs[levelMaxTimeSpent]) {
+                levelMaxTimeSpent = i;
+            }
+        }
+        if (levelMaxTimeSpent == ModemActivityInfo.TX_POWER_LEVELS - 1) {
+            if (!mIsCellularTxPowerHigh) {
+                mHistoryCur.states2 |= HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+                mIsCellularTxPowerHigh = true;
+            }
+            return;
+        }
+        if (mIsCellularTxPowerHigh) {
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mIsCellularTxPowerHigh = false;
+        }
+        return;
+    }
+
+    /**
      * Distribute Bluetooth energy info and network traffic to apps.
      * @param info The energy information from the bluetooth controller.
      */
@@ -13537,6 +13581,7 @@
         mCameraOnTimer.readSummaryFromParcelLocked(in);
         mBluetoothScanNesting = 0;
         mBluetoothScanTimer.readSummaryFromParcelLocked(in);
+        mIsCellularTxPowerHigh = false;
 
         int NRPMS = in.readInt();
         if (NRPMS > 10000) {
@@ -14473,6 +14518,7 @@
         mCameraOnTimer = new StopwatchTimer(mClocks, null, -13, null, mOnBatteryTimeBase, in);
         mBluetoothScanNesting = 0;
         mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase, in);
+        mIsCellularTxPowerHigh = false;
         mDischargeUnplugLevel = in.readInt();
         mDischargePlugLevel = in.readInt();
         mDischargeCurrentLevel = in.readInt();
diff --git a/core/java/com/android/internal/widget/VerifyCredentialResponse.java b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
index ad6020c..7d1c706 100644
--- a/core/java/com/android/internal/widget/VerifyCredentialResponse.java
+++ b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
@@ -98,6 +98,8 @@
             if (mPayload != null) {
                 dest.writeInt(mPayload.length);
                 dest.writeByteArray(mPayload);
+            } else {
+                dest.writeInt(0);
             }
         }
     }
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index ed032c7..f6223fa 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -90,7 +90,7 @@
 }
 
 static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex,
-        jint weight, jint italic) {
+        jint givenWeight, jint givenItalic) {
     uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes;
     for (const auto& axis : builder->axes) {
         skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value});
@@ -114,15 +114,27 @@
     std::shared_ptr<minikin::MinikinFont> minikinFont =
             std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex,
                     builder->axes);
-    minikin::Font::Builder fontBuilder(minikinFont);
 
-    if (weight != RESOLVE_BY_FONT_TABLE) {
-        fontBuilder.setWeight(weight);
+    int weight = givenWeight;
+    bool italic = givenItalic == 1;
+    if (givenWeight == RESOLVE_BY_FONT_TABLE || givenItalic == RESOLVE_BY_FONT_TABLE) {
+        int os2Weight;
+        bool os2Italic;
+        if (!minikin::FontFamily::analyzeStyle(minikinFont, &os2Weight, &os2Italic)) {
+            ALOGE("analyzeStyle failed. Using default style");
+            os2Weight = 400;
+            os2Italic = false;
+        }
+        if (givenWeight == RESOLVE_BY_FONT_TABLE) {
+            weight = os2Weight;
+        }
+        if (givenItalic == RESOLVE_BY_FONT_TABLE) {
+            italic = os2Italic;
+        }
     }
-    if (italic != RESOLVE_BY_FONT_TABLE) {
-        fontBuilder.setSlant(static_cast<minikin::FontStyle::Slant>(italic != 0));
-    }
-    builder->fonts.push_back(fontBuilder.build());
+
+    builder->fonts.push_back(minikin::Font(minikinFont,
+            minikin::FontStyle(weight, static_cast<minikin::FontStyle::Slant>(italic))));
     builder->axes.clear();
     return true;
 }
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 482d028..115d0d5 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -576,7 +576,7 @@
         minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
         float saveSkewX = paint->getTextSkewX();
         bool savefakeBold = paint->isFakeBoldText();
-        MinikinFontSkia::populateSkPaint(paint, baseFont.font->typeface().get(), baseFont.fakery);
+        MinikinFontSkia::populateSkPaint(paint, baseFont.font, baseFont.fakery);
         SkScalar spacing = paint->getFontMetrics(metrics);
         // The populateSkPaint call may have changed fake bold / text skew
         // because we want to measure with those effects applied, so now
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto
index 0342c9c..d1c5db6 100644
--- a/core/proto/android/server/alarmmanagerservice.proto
+++ b/core/proto/android/server/alarmmanagerservice.proto
@@ -104,11 +104,6 @@
 
   repeated InFlightProto outstanding_deliveries = 34;
 
-  // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling. It
-  // should be either CosntantsProto.allow_while_idle_short_duration_ms or
-  // ConstantsProto.allow_while_idle_long_duration_ms.
-  optional int64 allow_while_idle_min_duration_ms = 35;
-
   message LastAllowWhileIdleDispatch {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
@@ -121,7 +116,7 @@
   }
 
   // Whether the short or long while-idle timeout should be used for each UID.
-  repeated int32 use_allow_while_idle_short_time = 42;
+  repeated int32 use_allow_while_idle_short_time = 35;
 
   // For each uid, this is the last time we dispatched an "allow while idle"
   // alarm, used to determine the earliest we can dispatch the next such alarm.
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index b5c3ac0..5cb5319 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -227,7 +227,6 @@
         optional int32 setting_minimum = 1;
         optional int32 setting_maximum = 2;
         optional int32 setting_default = 3;
-        optional int32 setting_for_vr_default = 4;
     }
 
     // True to decouple auto-suspend mode from the display state.
@@ -293,44 +292,27 @@
     // The stay on while plugged in setting.
     // A set of battery conditions under which to make the screen stay on.
     optional StayOnWhilePluggedInProto stay_on_while_plugged_in = 29;
-    // The screen brightness setting, from 0 to 255.
-    // Use -1 if no value has been set.
-    optional sint32 screen_brightness_setting = 30;
-    // The screen auto-brightness adjustment setting, from -1 to 1.
-    // Use 0 if there is no adjustment.
-    optional float screen_auto_brightness_adjustment_setting = 31;
     // The screen brightness mode.
-    optional .android.providers.settings.SettingsProto.ScreenBrightnessMode screen_brightness_mode_setting = 32;
+    optional .android.providers.settings.SettingsProto.ScreenBrightnessMode screen_brightness_mode_setting = 30;
     // The screen brightness setting override from the window manager
     // to allow the current foreground activity to override the brightness.
     // Use -1 to disable.
-    optional sint32 screen_brightness_override_from_window_manager = 33;
+    optional sint32 screen_brightness_override_from_window_manager = 31;
     // The user activity timeout override from the window manager
     // to allow the current foreground activity to override the user activity
     // timeout. Use -1 to disable.
-    optional sint64 user_activity_timeout_override_from_window_manager_ms = 34;
+    optional sint64 user_activity_timeout_override_from_window_manager_ms = 32;
     // The window manager has determined the user to be inactive via other means.
     // Set this to false to disable.
-    optional bool is_user_inactive_override_from_window_manager = 35;
-    // The screen brightness setting override from the settings application
-    // to temporarily adjust the brightness until next updated,
-    // Use -1 to disable.
-    optional sint32 temporary_screen_brightness_setting_override = 36;
-    // The screen brightness adjustment setting override from the settings
-    // application to temporarily adjust the auto-brightness adjustment factor
-    // until next updated, in the range -1..1.
-    // Use NaN to disable.
-    optional float temporary_screen_auto_brightness_adjustment_setting_override = 37;
+    optional bool is_user_inactive_override_from_window_manager = 33;
     // The screen state to use while dozing.
-    optional .android.view.DisplayStateEnum doze_screen_state_override_from_dream_manager = 38;
+    optional .android.view.DisplayStateEnum doze_screen_state_override_from_dream_manager = 34;
     // The screen brightness to use while dozing.
-    optional float dozed_screen_brightness_override_from_dream_manager = 39;
+    optional float dozed_screen_brightness_override_from_dream_manager = 35;
     // Screen brightness settings limits.
-    optional ScreenBrightnessSettingLimitsProto screen_brightness_setting_limits = 40;
-    // The screen brightness setting, from 0 to 255, to be used while in VR Mode.
-    optional int32 screen_brightness_for_vr_setting = 41;
+    optional ScreenBrightnessSettingLimitsProto screen_brightness_setting_limits = 36;
     // True if double tap to wake is enabled
-    optional bool is_double_tap_wake_enabled = 42;
+    optional bool is_double_tap_wake_enabled = 37;
     // True if we are currently in VR Mode.
-    optional bool is_vr_mode_enabled = 43;
+    optional bool is_vr_mode_enabled = 38;
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 660c744..7b239f0 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -20,6 +20,7 @@
 import android.os.Looper;
 import android.util.SparseIntArray;
 
+import com.android.internal.location.gnssmetrics.GnssMetrics;
 import java.util.ArrayList;
 import java.util.concurrent.Future;
 
@@ -40,6 +41,11 @@
         mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
         setExternalStatsSyncLocked(new DummyExternalStatsSync());
 
+        for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
+            mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000-i, null,
+                mOnBatteryTimeBase);
+        }
+
         // A no-op handler.
         mHandler = new Handler(Looper.getMainLooper()) {};
     }
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index 1ef74ba..d0565ca 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -472,11 +472,15 @@
 ### Non-printing keys ###
 
 key ESCAPE {
-    base:                               fallback BACK
+    base:                               none
     alt, meta:                          fallback HOME
     ctrl:                               fallback MENU
 }
 
+key DEL {
+    ctrl+alt:                           fallback BACK
+}
+
 ### Gamepad buttons ###
 
 key BUTTON_A {
diff --git a/data/keyboards/Virtual.kcm b/data/keyboards/Virtual.kcm
index c4647e0..c763cc09 100644
--- a/data/keyboards/Virtual.kcm
+++ b/data/keyboards/Virtual.kcm
@@ -469,11 +469,15 @@
 ### Non-printing keys ###
 
 key ESCAPE {
-    base:                               fallback BACK
+    base:                               none
     alt, meta:                          fallback HOME
     ctrl:                               fallback MENU
 }
 
+key DEL {
+    ctrl+alt:                           fallback BACK
+}
+
 ### Gamepad buttons ###
 
 key BUTTON_A {
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index f5a6f49..8b5114c 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -825,6 +825,14 @@
         mFillPaint.setXfermode(mode);
     }
 
+    /**
+     * @param aa to draw this drawable with
+     * @hide
+     */
+    public void setAntiAlias(boolean aa) {
+        mFillPaint.setAntiAlias(aa);
+    }
+
     private void buildPathIfDirty() {
         final GradientState st = mGradientState;
         if (mPathIsDirty) {
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index dca9ef5..091b526 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -132,8 +132,8 @@
         bool italicFromFont;
 
         const minikin::FontStyle defaultStyle;
-        const minikin::MinikinFont* mf = families.empty() ? nullptr
-                : families[0]->getClosestMatch(defaultStyle).font->typeface().get();
+        const minikin::MinikinFont* mf =
+                families.empty() ? nullptr : families[0]->getClosestMatch(defaultStyle).font;
         if (mf != nullptr) {
             SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface();
             const SkFontStyle& style = skTypeface->fontStyle();
@@ -183,7 +183,7 @@
     std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
             std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>());
     std::vector<minikin::Font> fonts;
-    fonts.push_back(minikin::Font::Builder(font).build());
+    fonts.push_back(minikin::Font(std::move(font), minikin::FontStyle()));
 
     std::shared_ptr<minikin::FontCollection> collection = std::make_shared<minikin::FontCollection>(
             std::make_shared<minikin::FontFamily>(std::move(fonts)));
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index e424a26..2232c25 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -57,7 +57,7 @@
     std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
             std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>());
     std::vector<minikin::Font> fonts;
-    fonts.push_back(minikin::Font::Builder(font).build());
+    fonts.push_back(minikin::Font(std::move(font), minikin::FontStyle()));
     return std::make_shared<minikin::FontFamily>(std::move(fonts));
 }
 
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d3c6edd..d194796 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -1271,6 +1271,9 @@
         final String allowedProviders = Settings.Secure.getStringForUser(
                 mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                 userHandle.getIdentifier());
+        if (allowedProviders == null) {
+            return false;
+        }
         final List<String> providerList = Arrays.asList(allowedProviders.split(","));
         for(String provider : getAllProviders()) {
             if (provider.equals(PASSIVE_PROVIDER)) {
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index d0963cb..3847530 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -1416,6 +1416,7 @@
     /*
      * Call BEFORE adding a routing callback handler.
      */
+    @GuardedBy("mRoutingChangeListeners")
     private void testEnableNativeRoutingCallbacksLocked() {
         if (mRoutingChangeListeners.size() == 0) {
             native_enableDeviceCallback();
@@ -1425,6 +1426,7 @@
     /*
      * Call AFTER removing a routing callback handler.
      */
+    @GuardedBy("mRoutingChangeListeners")
     private void testDisableNativeRoutingCallbacksLocked() {
         if (mRoutingChangeListeners.size() == 0) {
             native_disableDeviceCallback();
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 8e822a5..2d5fad5 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2821,6 +2821,7 @@
     /*
      * Call BEFORE adding a routing callback handler.
      */
+    @GuardedBy("mRoutingChangeListeners")
     private void testEnableNativeRoutingCallbacksLocked() {
         if (mRoutingChangeListeners.size() == 0) {
             native_enableDeviceCallback();
@@ -2830,6 +2831,7 @@
     /*
      * Call AFTER removing a routing callback handler.
      */
+    @GuardedBy("mRoutingChangeListeners")
     private void testDisableNativeRoutingCallbacksLocked() {
         if (mRoutingChangeListeners.size() == 0) {
             native_disableDeviceCallback();
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index e9ffe60..bd6c7e6 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -30,7 +30,7 @@
 import android.media.session.MediaSessionManager;
 import android.media.update.ApiLoader;
 import android.media.update.MediaController2Provider;
-import android.media.update.PlaybackInfoProvider;
+import android.media.update.MediaController2Provider.PlaybackInfoProvider;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.ResultReceiver;
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 1bc3dfa..fe5e822 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1484,6 +1484,7 @@
     /*
      * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
      */
+    @GuardedBy("mRoutingChangeListeners")
     private void enableNativeRoutingCallbacksLocked(boolean enabled) {
         if (mRoutingChangeListeners.size() == 0) {
             native_enableDeviceCallback(enabled);
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
index e3d5ac0..d4e9aac 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -1417,6 +1417,7 @@
     /*
      * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
      */
+    @GuardedBy("mRoutingChangeListeners")
     private void enableNativeRoutingCallbacksLocked(boolean enabled) {
         if (mRoutingChangeListeners.size() == 0) {
             native_enableDeviceCallback(enabled);
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 62240ce..823410f 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -1353,6 +1353,7 @@
     /*
      * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
      */
+    @GuardedBy("mRoutingChangeListeners")
     private void enableNativeRoutingCallbacksLocked(boolean enabled) {
         if (mRoutingChangeListeners.size() == 0) {
             native_enableDeviceCallback(enabled);
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
index 71bc64a..c492d307 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
+import android.media.AudioAttributes;
 import android.media.MediaController2.PlaybackInfo;
 import android.media.MediaItem2;
 import android.media.MediaSession2.Command;
@@ -65,4 +66,12 @@
     PlaylistParams getPlaylistParams_impl();
     void setPlaylistParams_impl(PlaylistParams params);
     PlaybackState2 getPlaybackState_impl();
+
+    interface PlaybackInfoProvider {
+        int getPlaybackType_impl();
+        AudioAttributes getAudioAttributes_impl();
+        int getControlType_impl();
+        int getMaxVolume_impl();
+        int getCurrentVolume_impl();
+    }
 }
diff --git a/media/java/android/media/update/PlaybackInfoProvider.java b/media/java/android/media/update/PlaybackInfoProvider.java
deleted file mode 100644
index 36eb58a..0000000
--- a/media/java/android/media/update/PlaybackInfoProvider.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.
- */
-
-package android.media.update;
-
-import android.media.AudioAttributes;
-
-/**
- * @hide
- */
-// TODO(jaewan): @SystemApi
-public interface PlaybackInfoProvider {
-    int getPlaybackType_impl();
-    AudioAttributes getAudioAttributes_impl();
-    int getControlType_impl();
-    int getMaxVolume_impl();
-    int getCurrentVolume_impl();
-}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 6ef3fac..1cff59c 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -865,30 +865,49 @@
     <!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] -->
     <string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
 
-    <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging -->
-    <string name="power_remaining_duration_only">About <xliff:g id="time">^1</xliff:g> left</string>
-    <!-- [CHAR_LIMIT=60] Label for estimated remaining duration of battery discharging -->
-    <string name="power_remaining_duration_only_enhanced">About <xliff:g id="time">^1</xliff:g> left based on your usage</string>
-    <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging -->
-    <string name="power_remaining_charging_duration_only"><xliff:g id="time">^1</xliff:g> left until fully charged</string>
+  <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging -->
+  <string name="power_remaining_duration_only">About <xliff:g id="time">%1$s</xliff:g> left</string>
+  <!-- [CHAR_LIMIT=60] Label for estimated remaining duration of battery discharging -->
+  <string name="power_remaining_duration_only_enhanced">About <xliff:g id="time">%1$s</xliff:g> left based on your usage</string>
+  <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging -->
+  <string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until fully charged</string>
 
-    <!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging -->
-    <string name="power_remaining_duration_only_short"><xliff:g id="time">^1</xliff:g> left</string>
-    <!-- [CHAR_LIMIT=60] Short label for estimated remaining duration of battery charging/discharging -->
-    <string name="power_remaining_duration_only_short_enhanced"><xliff:g id="time">^1</xliff:g> left based on your usage</string>
+  <!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging -->
+  <string name="power_remaining_duration_only_short"><xliff:g id="time">%1$s</xliff:g> left</string>
 
-    <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
-    <string name="power_discharging_duration"><xliff:g id="level">^1</xliff:g> - about <xliff:g id="time">^2</xliff:g> left</string>
-    <!-- [CHAR_LIMIT=60] Label for battery level chart when discharging with duration and using enhanced estimate -->
-    <string name="power_discharging_duration_enhanced"><xliff:g id="level">^1</xliff:g> - about <xliff:g id="time">^2</xliff:g> left based on your usage</string>
+  <!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount -->
+  <string name="power_remaining_less_than_duration_only">Less than <xliff:g id="threshold">%1$s</xliff:g> remaining</string>
+  <!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount with the percentage -->
+  <string name="power_remaining_less_than_duration"><xliff:g id="level">%1$s</xliff:g> - Less than <xliff:g id="threshold">%2$s</xliff:g> remaining</string>
 
-    <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
-    <string name="power_discharging_duration_short"><xliff:g id="level">^1</xliff:g> - <xliff:g id="time">^2</xliff:g> left</string>
+  <!-- Used to let users know that they have more than some amount of battery life remaining with percentage. ex: 75% - more than 1 day remaining [CHAR LIMIT = 80] -->
+  <string name="power_remaining_more_than_subtext"><xliff:g id="level">%1$s</xliff:g>more than <xliff:g id="time_remaining">%2$s</xliff:g> remaining</string>
+  <!-- Used to let users know that they have more than some amount of battery life remaining. ex: more than 1 day remaining [CHAR LIMIT = 40] -->
+  <string name="power_remaining_only_more_than_subtext">more than <xliff:g id="time_remaining">%1$s</xliff:g> remaining</string>
+
+  <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
+  <string name="power_remaining_duration_only_shutdown_imminent" product="default">phone may shutdown soon</string>
+  <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
+  <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">tablet may shutdown soon</string>
+  <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
+  <string name="power_remaining_duration_only_shutdown_imminent" product="device">device may shutdown soon</string>
+
+  <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
+  <string name="power_discharging_duration"><xliff:g id="level">%1$s</xliff:g> - about <xliff:g id="time">%2$s</xliff:g> left</string>
+  <!-- [CHAR_LIMIT=60] Label for battery level chart when discharging with duration and using enhanced estimate -->
+  <string name="power_discharging_duration_enhanced"><xliff:g id="level">%1$s</xliff:g> - about <xliff:g id="time">%2$s</xliff:g> left based on your usage</string>
+
+  <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
+  <string name="power_remaining_duration_shutdown_imminent" product="default"><xliff:g id="level">%1$s</xliff:g> - phone may shutdown soon</string>
+  <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
+  <string name="power_remaining_duration_shutdown_imminent" product="tablet"><xliff:g id="level">%1$s</xliff:g> - tablet may shutdown soon</string>
+  <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
+  <string name="power_remaining_duration_shutdown_imminent" product="device"><xliff:g id="level">%1$s</xliff:g> - device may shutdown soon</string>
 
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging -->
     <string name="power_charging"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="state">%2$s</xliff:g></string>
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
-    <string name="power_charging_duration"><xliff:g id="level">^1</xliff:g> - <xliff:g id="time">^2</xliff:g> until fully charged</string>
+    <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> until fully charged</string>
 
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 190f5e6..68ead09 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -17,7 +17,6 @@
 
 import android.annotation.LayoutRes;
 import android.annotation.Nullable;
-import android.app.ActionBar;
 import android.app.Activity;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -72,9 +71,9 @@
             requestWindowFeature(Window.FEATURE_NO_TITLE);
         }
         super.setContentView(R.layout.settings_with_drawer);
-        mContentHeaderContainer = (FrameLayout) findViewById(R.id.content_header_container);
+        mContentHeaderContainer = findViewById(R.id.content_header_container);
 
-        Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
+        Toolbar toolbar = findViewById(R.id.action_bar);
         if (theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
             toolbar.setVisibility(View.GONE);
             return;
@@ -89,7 +88,9 @@
 
     @Override
     public boolean onNavigateUp() {
-        finish();
+        if (!super.onNavigateUp()) {
+            finish();
+        }
         return true;
     }
 
@@ -104,11 +105,6 @@
         registerReceiver(mPackageReceiver, filter);
 
         new CategoriesUpdateTask().execute();
-        final Intent intent = getIntent();
-        if (intent != null && intent.getBooleanExtra(EXTRA_SHOW_MENU, false)) {
-            // Intent explicitly set to show menu.
-            showMenuIcon();
-        }
     }
 
     @Override
@@ -125,13 +121,6 @@
         mCategoryListeners.remove(listener);
     }
 
-    public void setContentHeaderView(View headerView) {
-        mContentHeaderContainer.removeAllViews();
-        if (headerView != null) {
-            mContentHeaderContainer.addView(headerView);
-        }
-    }
-
     @Override
     public void setContentView(@LayoutRes int layoutResID) {
         final ViewGroup parent = findViewById(R.id.content_frame);
@@ -151,13 +140,6 @@
         ((ViewGroup) findViewById(R.id.content_frame)).addView(view, params);
     }
 
-    private void showMenuIcon() {
-        final ActionBar actionBar = getActionBar();
-        if (actionBar != null) {
-            actionBar.setDisplayHomeAsUpEnabled(true);
-        }
-    }
-
     private void onCategoriesChanged() {
         final int N = mCategoryListeners.size();
         for (int i = 0; i < N; i++) {
@@ -165,10 +147,6 @@
         }
     }
 
-    public void onProfileTileOpen() {
-        finish();
-    }
-
     /**
      * @return whether or not the enabled state actually changed.
      */
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
new file mode 100644
index 0000000..346ca66
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
@@ -0,0 +1,143 @@
+/*
+ * 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.settingslib.utils;
+
+import android.content.Context;
+import android.icu.text.MeasureFormat;
+import android.icu.text.MeasureFormat.FormatWidth;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import com.android.settingslib.R;
+import com.android.settingslib.utils.StringUtil;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+/** Utility class for keeping power related strings consistent**/
+public class PowerUtil {
+    private static final long SEVEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(7);
+    private static final long FIFTEEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(15);
+    private static final long ONE_DAY_MILLIS = TimeUnit.DAYS.toMillis(1);
+
+    /**
+     * This method produces the text used in various places throughout the system to describe the
+     * remaining battery life of the phone in a consistent manner.
+     *
+     * @param context
+     * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
+     * @param percentageString An optional percentage of battery remaining string.
+     * @param basedOnUsage Whether this estimate is based on usage or simple extrapolation.
+     * @return a properly formatted and localized string describing how much time remains
+     * before the battery runs out.
+     */
+    public static String getBatteryRemainingStringFormatted(Context context, long drainTimeMs,
+            @Nullable String percentageString, boolean basedOnUsage) {
+        if (drainTimeMs > 0) {
+            if (drainTimeMs <= SEVEN_MINUTES_MILLIS) {
+                // show a imminent shutdown warning if less than 7 minutes remain
+                return getShutdownImminentString(context, percentageString);
+            } else if (drainTimeMs <= FIFTEEN_MINUTES_MILLIS) {
+                // show a less than 15 min remaining warning if appropriate
+                CharSequence timeString = StringUtil.formatElapsedTime(context,
+                        FIFTEEN_MINUTES_MILLIS,
+                        false /* withSeconds */);
+                return getUnderFifteenString(context, timeString, percentageString);
+            } else if (drainTimeMs >= ONE_DAY_MILLIS) {
+                // just say more than one day if over 24 hours
+                return getMoreThanOneDayString(context, percentageString);
+            } else {
+                // show a regular time remaining string
+                return getRegularTimeRemainingString(context, drainTimeMs,
+                        percentageString, basedOnUsage);
+            }
+        }
+        return null;
+    }
+
+    private static String getShutdownImminentString(Context context, String percentageString) {
+        return TextUtils.isEmpty(percentageString)
+                ? context.getString(R.string.power_remaining_duration_only_shutdown_imminent)
+                : context.getString(
+                        R.string.power_remaining_duration_shutdown_imminent,
+                        percentageString);
+    }
+
+    private static String getUnderFifteenString(Context context, CharSequence timeString,
+            String percentageString) {
+        return TextUtils.isEmpty(percentageString)
+                ? context.getString(R.string.power_remaining_less_than_duration_only, timeString)
+                : context.getString(
+                        R.string.power_remaining_less_than_duration,
+                        percentageString,
+                        timeString);
+
+    }
+
+    private static String getMoreThanOneDayString(Context context, String percentageString) {
+        final Locale currentLocale = context.getResources().getConfiguration().getLocales().get(0);
+        final MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.SHORT);
+
+        final Measure daysMeasure = new Measure(1, MeasureUnit.DAY);
+
+        return TextUtils.isEmpty(percentageString)
+                ? context.getString(R.string.power_remaining_only_more_than_subtext,
+                        frmt.formatMeasures(daysMeasure))
+                : context.getString(
+                        R.string.power_remaining_more_than_subtext,
+                        percentageString,
+                        frmt.formatMeasures(daysMeasure));
+    }
+
+    private static String getRegularTimeRemainingString(Context context, long drainTimeMs,
+            String percentageString, boolean basedOnUsage) {
+        // round to the nearest 15 min to not appear oversly precise
+        final long roundedTimeMs = roundToNearestThreshold(drainTimeMs,
+                FIFTEEN_MINUTES_MILLIS);
+        CharSequence timeString = StringUtil.formatElapsedTime(context,
+                roundedTimeMs,
+                false /* withSeconds */);
+        if (TextUtils.isEmpty(percentageString)) {
+            int id = basedOnUsage
+                    ? R.string.power_remaining_duration_only_enhanced
+                    : R.string.power_remaining_duration_only;
+            return context.getString(id, timeString);
+        } else {
+            int id = basedOnUsage
+                    ? R.string.power_discharging_duration_enhanced
+                    : R.string.power_discharging_duration;
+            return context.getString(id, percentageString, timeString);
+        }
+    }
+
+    public static long convertUsToMs(long timeUs) {
+        return timeUs / 1000;
+    }
+
+    public static long convertMsToUs(long timeMs) {
+        return timeMs * 1000;
+    }
+
+    private static long roundToNearestThreshold(long drainTime, long threshold) {
+        final long remainder = drainTime % threshold;
+        if (remainder < threshold / 2) {
+            return drainTime - remainder;
+        } else {
+            return drainTime - remainder + threshold;
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java
new file mode 100644
index 0000000..45fdd78
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java
@@ -0,0 +1,149 @@
+/*
+ * 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.settingslib.utils;
+
+import android.content.Context;
+import android.icu.text.MeasureFormat;
+import android.icu.text.MeasureFormat.FormatWidth;
+import android.icu.text.RelativeDateTimeFormatter;
+import android.icu.text.RelativeDateTimeFormatter.RelativeUnit;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
+import android.icu.util.ULocale;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.TtsSpan;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/** Utility class for generally useful string methods **/
+public class StringUtil {
+
+  public static final int SECONDS_PER_MINUTE = 60;
+  public static final int SECONDS_PER_HOUR = 60 * 60;
+  public static final int SECONDS_PER_DAY = 24 * 60 * 60;
+
+  /**
+   * Returns elapsed time for the given millis, in the following format:
+   * 2d 5h 40m 29s
+   * @param context the application context
+   * @param millis the elapsed time in milli seconds
+   * @param withSeconds include seconds?
+   * @return the formatted elapsed time
+   */
+  public static CharSequence formatElapsedTime(Context context, double millis,
+          boolean withSeconds) {
+      SpannableStringBuilder sb = new SpannableStringBuilder();
+      int seconds = (int) Math.floor(millis / 1000);
+      if (!withSeconds) {
+          // Round up.
+          seconds += 30;
+      }
+
+      int days = 0, hours = 0, minutes = 0;
+      if (seconds >= SECONDS_PER_DAY) {
+          days = seconds / SECONDS_PER_DAY;
+          seconds -= days * SECONDS_PER_DAY;
+      }
+      if (seconds >= SECONDS_PER_HOUR) {
+          hours = seconds / SECONDS_PER_HOUR;
+          seconds -= hours * SECONDS_PER_HOUR;
+      }
+      if (seconds >= SECONDS_PER_MINUTE) {
+          minutes = seconds / SECONDS_PER_MINUTE;
+          seconds -= minutes * SECONDS_PER_MINUTE;
+      }
+
+      final ArrayList<Measure> measureList = new ArrayList(4);
+      if (days > 0) {
+          measureList.add(new Measure(days, MeasureUnit.DAY));
+      }
+      if (hours > 0) {
+          measureList.add(new Measure(hours, MeasureUnit.HOUR));
+      }
+      if (minutes > 0) {
+          measureList.add(new Measure(minutes, MeasureUnit.MINUTE));
+      }
+      if (withSeconds && seconds > 0) {
+          measureList.add(new Measure(seconds, MeasureUnit.SECOND));
+      }
+      if (measureList.size() == 0) {
+          // Everything addable was zero, so nothing was added. We add a zero.
+          measureList.add(new Measure(0, withSeconds ? MeasureUnit.SECOND : MeasureUnit.MINUTE));
+      }
+      final Measure[] measureArray = measureList.toArray(new Measure[measureList.size()]);
+
+      final Locale locale = context.getResources().getConfiguration().locale;
+      final MeasureFormat measureFormat = MeasureFormat.getInstance(
+              locale, FormatWidth.NARROW);
+      sb.append(measureFormat.formatMeasures(measureArray));
+
+      if (measureArray.length == 1 && MeasureUnit.MINUTE.equals(measureArray[0].getUnit())) {
+          // Add ttsSpan if it only have minute value, because it will be read as "meters"
+          final TtsSpan ttsSpan = new TtsSpan.MeasureBuilder().setNumber(minutes)
+                  .setUnit("minute").build();
+          sb.setSpan(ttsSpan, 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+      }
+
+      return sb;
+  }
+
+    /**
+     * Returns relative time for the given millis in the past, in a short format such as "2 days
+     * ago", "5 hr. ago", "40 min. ago", or "29 sec. ago".
+     *
+     * <p>The unit is chosen to have good information value while only using one unit. So 27 hours
+     * and 50 minutes would be formatted as "28 hr. ago", while 50 hours would be formatted as
+     * "2 days ago".
+     *
+     * @param context the application context
+     * @param millis the elapsed time in milli seconds
+     * @param withSeconds include seconds?
+     * @return the formatted elapsed time
+     */
+    public static CharSequence formatRelativeTime(Context context, double millis,
+            boolean withSeconds) {
+        final int seconds = (int) Math.floor(millis / 1000);
+        final RelativeUnit unit;
+        final int value;
+        if (withSeconds && seconds < 2 * SECONDS_PER_MINUTE) {
+            unit = RelativeUnit.SECONDS;
+            value = seconds;
+        } else if (seconds < 2 * SECONDS_PER_HOUR) {
+            unit = RelativeUnit.MINUTES;
+            value = (seconds + SECONDS_PER_MINUTE / 2)
+                    / SECONDS_PER_MINUTE;
+        } else if (seconds < 2 * SECONDS_PER_DAY) {
+            unit = RelativeUnit.HOURS;
+            value = (seconds + SECONDS_PER_HOUR / 2)
+                    / SECONDS_PER_HOUR;
+        } else {
+            unit = RelativeUnit.DAYS;
+            value = (seconds + SECONDS_PER_DAY / 2)
+                    / SECONDS_PER_DAY;
+        }
+
+        final Locale locale = context.getResources().getConfiguration().locale;
+        final RelativeDateTimeFormatter formatter = RelativeDateTimeFormatter.getInstance(
+                ULocale.forLocale(locale),
+                null /* default NumberFormat */,
+                RelativeDateTimeFormatter.Style.SHORT,
+                android.icu.text.DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE);
+
+        return formatter.format(value, RelativeDateTimeFormatter.Direction.LAST, unit);
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
index 003f905..2f417ad 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
@@ -18,8 +18,6 @@
 
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
-import static android.support.test.espresso.assertion.ViewAssertions.matches;
-import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
 
 import android.app.Instrumentation;
@@ -49,42 +47,22 @@
     }
 
     @Test
-    public void startActivityWithNoExtra_showNoNavUp() {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        instrumentation.startActivitySync(new Intent(instrumentation.getTargetContext(),
-                TestActivity.class));
-
-        onView(withContentDescription(com.android.internal.R.string.action_bar_up_description))
-                .check(doesNotExist());
-    }
-
-    @Test
-    public void startActivityWithExtraToHideMenu_showNavUp() {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
-                .putExtra(TestActivity.EXTRA_SHOW_MENU, false);
+    public void startActivity_doNotShowNavUp() {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         instrumentation.startActivitySync(intent);
 
         onView(withContentDescription(com.android.internal.R.string.action_bar_up_description))
                 .check(doesNotExist());
     }
 
-    @Test
-    public void startActivityWithExtraToShowMenu_showNavUp() {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
-                .putExtra(TestActivity.EXTRA_SHOW_MENU, true);
-        instrumentation.startActivitySync(intent);
-
-        onView(withContentDescription(com.android.internal.R.string.action_bar_up_description))
-                .check(matches(isDisplayed()));
-    }
-
     /**
      * Test Activity in this test.
      *
      * Use this activity because SettingsDrawerActivity hasn't been registered in its
      * AndroidManifest.xml
      */
-    public static class TestActivity extends SettingsDrawerActivity {}
+    public static class TestActivity extends SettingsDrawerActivity {
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
new file mode 100644
index 0000000..f93210f
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.settingslib.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import com.android.settingslib.R;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.utils.PowerUtil;
+import java.time.Duration;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PowerUtilTest {
+    public static final String TEST_BATTERY_LEVEL_10 = "10%";
+    public static final String FIFTEEN_MIN_FORMATTED = "15m";
+    public static final long SEVENTEEN_MIN_MILLIS = Duration.ofMinutes(17).toMillis();
+    public static final long FIVE_MINUTES_MILLIS = Duration.ofMinutes(5).toMillis();
+    public static final long TEN_MINUTES_MILLIS = Duration.ofMinutes(10).toMillis();
+    public static final long TWO_DAYS_MILLIS = Duration.ofDays(2).toMillis();
+    public static final String ONE_DAY_FORMATTED = "1 day";
+
+    private Context mContext;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+    }
+
+    @Test
+    public void testGetBatteryRemainingStringFormatted_moreThanFifteenMinutes_withPercentage() {
+        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+                SEVENTEEN_MIN_MILLIS,
+                TEST_BATTERY_LEVEL_10,
+                true /* basedOnUsage */);
+        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+                SEVENTEEN_MIN_MILLIS,
+                TEST_BATTERY_LEVEL_10,
+                false /* basedOnUsage */);
+
+        // We only add special mention for the long string
+        assertThat(info).isEqualTo(mContext.getString(
+                R.string.power_discharging_duration_enhanced,
+                TEST_BATTERY_LEVEL_10,
+                FIFTEEN_MIN_FORMATTED));
+        // shortened string should not have extra text
+        assertThat(info2).isEqualTo(mContext.getString(
+                R.string.power_discharging_duration,
+                TEST_BATTERY_LEVEL_10,
+                FIFTEEN_MIN_FORMATTED));
+    }
+
+    @Test
+    public void testGetBatteryRemainingStringFormatted_moreThanFifteenMinutes_noPercentage() {
+        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+                SEVENTEEN_MIN_MILLIS,
+                null /* percentageString */,
+                true /* basedOnUsage */);
+        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+                SEVENTEEN_MIN_MILLIS,
+                null /* percentageString */,
+                false /* basedOnUsage */);
+
+        // We only add special mention for the long string
+        assertThat(info).isEqualTo(mContext.getString(
+                R.string.power_remaining_duration_only_enhanced,
+                FIFTEEN_MIN_FORMATTED));
+        // shortened string should not have extra text
+        assertThat(info2).isEqualTo(mContext.getString(
+                R.string.power_remaining_duration_only,
+                FIFTEEN_MIN_FORMATTED));
+    }
+
+
+    @Test
+    public void testGetBatteryRemainingStringFormatted_lessThanSevenMinutes_usesCorrectString() {
+        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+                FIVE_MINUTES_MILLIS,
+                TEST_BATTERY_LEVEL_10 /* percentageString */,
+                true /* basedOnUsage */);
+        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+                FIVE_MINUTES_MILLIS,
+                null /* percentageString */,
+                true /* basedOnUsage */);
+
+        // additional battery percentage in this string
+        assertThat(info).isEqualTo(mContext.getString(
+                R.string.power_remaining_duration_shutdown_imminent,
+                TEST_BATTERY_LEVEL_10));
+        // shortened string should not have percentage
+        assertThat(info2).isEqualTo(mContext.getString(
+                R.string.power_remaining_duration_only_shutdown_imminent));
+    }
+
+    @Test
+    public void testGetBatteryRemainingStringFormatted_betweenSevenAndFifteenMinutes_usesCorrectString() {
+        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+                TEN_MINUTES_MILLIS,
+                null /* percentageString */,
+                true /* basedOnUsage */);
+        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+                TEN_MINUTES_MILLIS,
+                TEST_BATTERY_LEVEL_10 /* percentageString */,
+                true /* basedOnUsage */);
+
+        // shortened string should not have percentage
+        assertThat(info).isEqualTo(mContext.getString(
+                R.string.power_remaining_less_than_duration_only,
+                FIFTEEN_MIN_FORMATTED));
+        // Add percentage to string when provided
+        assertThat(info2).isEqualTo(mContext.getString(
+                R.string.power_remaining_less_than_duration,
+                TEST_BATTERY_LEVEL_10,
+                FIFTEEN_MIN_FORMATTED));
+    }
+
+    @Test
+    public void testGetBatteryRemainingStringFormatted_moreThanOneDay_usesCorrectString() {
+        String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+                TWO_DAYS_MILLIS,
+                null /* percentageString */,
+                true /* basedOnUsage */);
+        String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+                TWO_DAYS_MILLIS,
+                TEST_BATTERY_LEVEL_10 /* percentageString */,
+                true /* basedOnUsage */);
+
+        // shortened string should not have percentage
+        assertThat(info).isEqualTo(mContext.getString(
+                R.string.power_remaining_only_more_than_subtext,
+                ONE_DAY_FORMATTED));
+        // Add percentage to string when provided
+        assertThat(info2).isEqualTo(mContext.getString(
+                R.string.power_remaining_more_than_subtext,
+                TEST_BATTERY_LEVEL_10,
+                ONE_DAY_FORMATTED));
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/StringUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/StringUtilTest.java
new file mode 100644
index 0000000..d5e3cdb
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/StringUtilTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.settingslib.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.text.SpannableStringBuilder;
+import android.text.format.DateUtils;
+import android.text.style.TtsSpan;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class StringUtilTest {
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = spy(RuntimeEnvironment.application);
+    }
+
+    @Test
+    public void testFormatElapsedTime_WithSeconds_ShowSeconds() {
+        final double testMillis = 5 * DateUtils.MINUTE_IN_MILLIS + 30 * DateUtils.SECOND_IN_MILLIS;
+        final String expectedTime = "5m 30s";
+
+        assertThat(StringUtil.formatElapsedTime(mContext, testMillis, true).toString())
+                .isEqualTo(expectedTime);
+    }
+
+    @Test
+    public void testFormatElapsedTime_NoSeconds_DoNotShowSeconds() {
+        final double testMillis = 5 * DateUtils.MINUTE_IN_MILLIS + 30 * DateUtils.SECOND_IN_MILLIS;
+        final String expectedTime = "6m";
+
+        assertThat(StringUtil.formatElapsedTime(mContext, testMillis, false).toString())
+                .isEqualTo(expectedTime);
+    }
+
+    @Test
+    public void testFormatElapsedTime_TimeMoreThanOneDay_ShowCorrectly() {
+        final double testMillis = 2 * DateUtils.DAY_IN_MILLIS
+                + 4 * DateUtils.HOUR_IN_MILLIS + 15 * DateUtils.MINUTE_IN_MILLIS;
+        final String expectedTime = "2d 4h 15m";
+
+        assertThat(StringUtil.formatElapsedTime(mContext, testMillis, false).toString())
+                .isEqualTo(expectedTime);
+    }
+
+    @Test
+    public void testFormatElapsedTime_ZeroFieldsInTheMiddleDontShow() {
+        final double testMillis = 2 * DateUtils.DAY_IN_MILLIS + 15 * DateUtils.MINUTE_IN_MILLIS;
+        final String expectedTime = "2d 15m";
+
+        assertThat(StringUtil.formatElapsedTime(mContext, testMillis, false).toString())
+                .isEqualTo(expectedTime);
+    }
+
+    @Test
+    public void testFormatElapsedTime_FormatZero_WithSeconds() {
+        final double testMillis = 0;
+        final String expectedTime = "0s";
+
+        assertThat(StringUtil.formatElapsedTime(mContext, testMillis, true).toString())
+                .isEqualTo(expectedTime);
+    }
+
+    @Test
+    public void testFormatElapsedTime_FormatZero_NoSeconds() {
+        final double testMillis = 0;
+        final String expectedTime = "0m";
+
+        assertThat(StringUtil.formatElapsedTime(mContext, testMillis, false).toString())
+                .isEqualTo(expectedTime);
+    }
+
+    @Test
+    public void testFormatElapsedTime_onlyContainsMinute_hasTtsSpan() {
+        final double testMillis = 15 * DateUtils.MINUTE_IN_MILLIS;
+
+        final CharSequence charSequence =
+                StringUtil.formatElapsedTime(mContext, testMillis, false);
+        assertThat(charSequence).isInstanceOf(SpannableStringBuilder.class);
+
+        final SpannableStringBuilder expectedString = (SpannableStringBuilder) charSequence;
+        final TtsSpan[] ttsSpans = expectedString.getSpans(0, expectedString.length(),
+                TtsSpan.class);
+
+        assertThat(ttsSpans).asList().hasSize(1);
+        assertThat(ttsSpans[0].getType()).isEqualTo(TtsSpan.TYPE_MEASURE);
+    }
+
+    @Test
+    public void testFormatRelativeTime_WithSeconds_ShowSeconds() {
+        final double testMillis = 40 * DateUtils.SECOND_IN_MILLIS;
+        final String expectedTime = "40 sec. ago";
+
+        assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+                expectedTime);
+    }
+
+    @Test
+    public void testFormatRelativeTime_NoSeconds_DoNotShowSeconds() {
+        final double testMillis = 40 * DateUtils.SECOND_IN_MILLIS;
+        final String expectedTime = "1 min. ago";
+
+        assertThat(StringUtil.formatRelativeTime(mContext, testMillis, false).toString()).isEqualTo(
+                expectedTime);
+    }
+
+    @Test
+    public void testFormatRelativeTime_LessThanTwoMinutes_withSeconds() {
+        final double testMillis = 119 * DateUtils.SECOND_IN_MILLIS;
+        final String expectedTime = "119 sec. ago";
+
+        assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+                expectedTime);
+    }
+
+    @Test
+    public void testFormatRelativeTime_LessThanTwoMinutes_NoSeconds() {
+        final double testMillis = 119 * DateUtils.SECOND_IN_MILLIS;
+        final String expectedTime = "2 min. ago";
+
+        assertThat(StringUtil.formatRelativeTime(mContext, testMillis, false).toString()).isEqualTo(
+                expectedTime);
+    }
+
+    @Test
+    public void testFormatRelativeTime_TwoMinutes_withSeconds() {
+        final double testMillis = 2 * DateUtils.MINUTE_IN_MILLIS;
+        final String expectedTime = "2 min. ago";
+
+        assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+                expectedTime);
+    }
+
+    @Test
+    public void testFormatRelativeTime_LessThanTwoHours_withSeconds() {
+        final double testMillis = 119 * DateUtils.MINUTE_IN_MILLIS;
+        final String expectedTime = "119 min. ago";
+
+        assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+                expectedTime);
+    }
+
+    @Test
+    public void testFormatRelativeTime_TwoHours_withSeconds() {
+        final double testMillis = 2 * DateUtils.HOUR_IN_MILLIS;
+        final String expectedTime = "2 hr. ago";
+
+        assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+                expectedTime);
+    }
+
+    @Test
+    public void testFormatRelativeTime_LessThanTwoDays_withSeconds() {
+        final double testMillis = 47 * DateUtils.HOUR_IN_MILLIS;
+        final String expectedTime = "47 hr. ago";
+
+        assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+                expectedTime);
+    }
+
+    @Test
+    public void testFormatRelativeTime_TwoDays_withSeconds() {
+        final double testMillis = 2 * DateUtils.DAY_IN_MILLIS;
+        final String expectedTime = "2 days ago";
+
+        assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+                expectedTime);
+    }
+
+    @Test
+    public void testFormatRelativeTime_FormatZero_WithSeconds() {
+        final double testMillis = 0;
+        final String expectedTime = "0 sec. ago";
+
+        assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+                expectedTime);
+    }
+
+    @Test
+    public void testFormatRelativeTime_FormatZero_NoSeconds() {
+        final double testMillis = 0;
+        final String expectedTime = "0 min. ago";
+
+        assertThat(StringUtil.formatRelativeTime(mContext, testMillis, false).toString()).isEqualTo(
+                expectedTime);
+    }
+}
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 2bcf4ef..1e48213 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -56,7 +56,6 @@
     SystemUI-proto
 
 LOCAL_JAVA_LIBRARIES := telephony-common
-LOCAL_JAVA_LIBRARIES += android.car
 
 LOCAL_PACKAGE_NAME := SystemUI
 LOCAL_CERTIFICATE := platform
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9613a6a..cbb3e8f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -176,9 +176,6 @@
     <!-- It's like, reality, but, you know, virtual -->
     <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
 
-    <!-- To control car audio module volume -->
-    <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
-
     <!-- the ability to rename notifications posted by other apps -->
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
 
diff --git a/packages/SystemUI/shared/tests/Android.mk b/packages/SystemUI/shared/tests/Android.mk
index 239a4e3..1715983 100644
--- a/packages/SystemUI/shared/tests/Android.mk
+++ b/packages/SystemUI/shared/tests/Android.mk
@@ -41,7 +41,7 @@
     testables \
     truth-prebuilt \
 
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
 
 # sign this with platform cert, so this test is allowed to inject key events into
 # UI it doesn't own. This is necessary to allow screenshots to be taken
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index aa56694..3a2b12f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -26,10 +26,6 @@
 import android.content.DialogInterface.OnDismissListener;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.icu.text.MeasureFormat;
-import android.icu.text.MeasureFormat.FormatWidth;
-import android.icu.util.Measure;
-import android.icu.util.MeasureUnit;
 import android.media.AudioAttributes;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -37,11 +33,11 @@
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.support.annotation.VisibleForTesting;
-import android.text.format.DateUtils;
 import android.util.Slog;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.settingslib.Utils;
+import com.android.settingslib.utils.PowerUtil;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -49,8 +45,6 @@
 
 import java.io.PrintWriter;
 import java.text.NumberFormat;
-import java.util.Locale;
-import java.util.concurrent.TimeUnit;
 
 public class PowerNotificationWarnings implements PowerUI.WarningsUI {
     private static final String TAG = PowerUI.TAG + ".Notification";
@@ -200,12 +194,7 @@
         // override notification copy if hybrid notification enabled
         if (mEstimate != null) {
             title = mContext.getString(R.string.battery_low_title_hybrid);
-            contentText = mContext.getString(
-                    mEstimate.isBasedOnUsage
-                            ? R.string.battery_low_percent_format_hybrid
-                            : R.string.battery_low_percent_format_hybrid_short,
-                    percentage,
-                    getTimeRemainingFormatted());
+            contentText = getHybridContentString(percentage);
         }
 
         final Notification.Builder nb =
@@ -239,21 +228,12 @@
         mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL);
     }
 
-    @VisibleForTesting
-    String getTimeRemainingFormatted() {
-        final Locale currentLocale = mContext.getResources().getConfiguration().getLocales().get(0);
-        MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.NARROW);
-
-        final long remainder = mEstimate.estimateMillis % DateUtils.HOUR_IN_MILLIS;
-        final long hours = TimeUnit.MILLISECONDS.toHours(
-                mEstimate.estimateMillis - remainder);
-        // round down to the nearest 15 min for now to not appear overly precise
-        final long minutes = TimeUnit.MILLISECONDS.toMinutes(
-                remainder - (remainder % TimeUnit.MINUTES.toMillis(15)));
-        final Measure hoursMeasure = new Measure(hours, MeasureUnit.HOUR);
-        final Measure minutesMeasure = new Measure(minutes, MeasureUnit.MINUTE);
-
-        return frmt.formatMeasures(hoursMeasure, minutesMeasure);
+    private String getHybridContentString(String percentage) {
+        return PowerUtil.getBatteryRemainingStringFormatted(
+            mContext,
+            mEstimate.estimateMillis,
+            percentage,
+            mEstimate.isBasedOnUsage);
     }
 
     private PendingIntent pendingBroadcast(String action) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
index a44fd9a..b8f6784 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
@@ -14,8 +14,11 @@
 
 package com.android.systemui.qs;
 
+import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.graphics.Canvas;
 import android.support.v4.widget.NestedScrollView;
+import android.util.Property;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -23,6 +26,8 @@
 import android.widget.LinearLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.qs.touch.OverScroll;
+import com.android.systemui.qs.touch.SwipeDetector;
 
 /**
  * Quick setting scroll view containing the brightness slider and the QS tiles.
@@ -35,6 +40,9 @@
     private final int mTouchSlop;
     private final int mFooterHeight;
     private int mLastMotionY;
+    private final SwipeDetector mSwipeDetector;
+    private final OverScrollHelper mOverScrollHelper;
+    private float mContentTranslationY;
 
     public QSScrollLayout(Context context, View... children) {
         super(context);
@@ -49,15 +57,19 @@
             linearLayout.addView(view);
         }
         addView(linearLayout);
+        setOverScrollMode(OVER_SCROLL_NEVER);
+        mOverScrollHelper = new OverScrollHelper();
+        mSwipeDetector = new SwipeDetector(context, mOverScrollHelper, SwipeDetector.VERTICAL);
+        mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, true);
     }
 
-
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (canScrollVertically(1) || canScrollVertically(-1)) {
             return super.onInterceptTouchEvent(ev);
         }
-        return false;
+        mSwipeDetector.onTouchEvent(ev);
+        return super.onInterceptTouchEvent(ev) || mOverScrollHelper.isInOverScroll();
     }
 
     @Override
@@ -65,7 +77,15 @@
         if (canScrollVertically(1) || canScrollVertically(-1)) {
             return super.onTouchEvent(ev);
         }
-        return false;
+        mSwipeDetector.onTouchEvent(ev);
+        return super.onTouchEvent(ev);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        canvas.translate(0, mContentTranslationY);
+        super.dispatchDraw(canvas);
+        canvas.translate(0, -mContentTranslationY);
     }
 
     public boolean shouldIntercept(MotionEvent ev) {
@@ -98,4 +118,81 @@
             parent.requestDisallowInterceptTouchEvent(disallowIntercept);
         }
     }
+
+    private void setContentTranslationY(float contentTranslationY) {
+        mContentTranslationY = contentTranslationY;
+        invalidate();
+    }
+
+    private static final Property<QSScrollLayout, Float> CONTENT_TRANS_Y =
+            new Property<QSScrollLayout, Float>(Float.class, "qsScrollLayoutContentTransY") {
+                @Override
+                public Float get(QSScrollLayout qsScrollLayout) {
+                    return qsScrollLayout.mContentTranslationY;
+                }
+
+                @Override
+                public void set(QSScrollLayout qsScrollLayout, Float y) {
+                    qsScrollLayout.setContentTranslationY(y);
+                }
+            };
+
+    private class OverScrollHelper implements SwipeDetector.Listener {
+        private boolean mIsInOverScroll;
+
+        // We use this value to calculate the actual amount the user has overscrolled.
+        private float mFirstDisplacement = 0;
+
+        @Override
+        public void onDragStart(boolean start) {}
+
+        @Override
+        public boolean onDrag(float displacement, float velocity) {
+            // Only overscroll if the user is scrolling down when they're already at the bottom
+            // or scrolling up when they're already at the top.
+            boolean wasInOverScroll = mIsInOverScroll;
+            mIsInOverScroll = (!canScrollVertically(1) && displacement < 0) ||
+                    (!canScrollVertically(-1) && displacement > 0);
+
+            if (wasInOverScroll && !mIsInOverScroll) {
+                // Exit overscroll. This can happen when the user is in overscroll and then
+                // scrolls the opposite way. Note that this causes the reset translation animation
+                // to run while the user is dragging, which feels a bit unnatural.
+                reset();
+            } else if (mIsInOverScroll) {
+                if (Float.compare(mFirstDisplacement, 0) == 0) {
+                    // Because users can scroll before entering overscroll, we need to
+                    // subtract the amount where the user was not in overscroll.
+                    mFirstDisplacement = displacement;
+                }
+                float overscrollY = displacement - mFirstDisplacement;
+                setContentTranslationY(getDampedOverScroll(overscrollY));
+            }
+
+            return mIsInOverScroll;
+        }
+
+        @Override
+        public void onDragEnd(float velocity, boolean fling) {
+            reset();
+        }
+
+        private void reset() {
+            if (Float.compare(mContentTranslationY, 0) != 0) {
+                ObjectAnimator.ofFloat(QSScrollLayout.this, CONTENT_TRANS_Y, 0)
+                        .setDuration(100)
+                        .start();
+            }
+            mIsInOverScroll = false;
+            mFirstDisplacement = 0;
+        }
+
+        public boolean isInOverScroll() {
+            return mIsInOverScroll;
+        }
+
+        private float getDampedOverScroll(float y) {
+            return OverScroll.dampedScroll(y, getHeight());
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 3b9e7bc..65135ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -104,6 +104,11 @@
         setMeasuredDimension(width, height);
     }
 
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
     private static int exactly(int size) {
         return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java
new file mode 100644
index 0000000..0464886
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java
@@ -0,0 +1,57 @@
+/*
+ * 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.systemui.qs.touch;
+
+/**
+ * Utility methods for overscroll damping and related effect.
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/OverScroll.java
+ */
+public class OverScroll {
+
+    private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
+
+    /**
+     * This curve determines how the effect of scrolling over the limits of the page diminishes
+     * as the user pulls further and further from the bounds
+     *
+     * @param f The percentage of how much the user has overscrolled.
+     * @return A transformed percentage based on the influence curve.
+     */
+    private static float overScrollInfluenceCurve(float f) {
+        f -= 1.0f;
+        return f * f * f + 1.0f;
+    }
+
+    /**
+     * @param amount The original amount overscrolled.
+     * @param max The maximum amount that the View can overscroll.
+     * @return The dampened overscroll amount.
+     */
+    public static int dampedScroll(float amount, int max) {
+        if (Float.compare(amount, 0) == 0) return 0;
+
+        float f = amount / max;
+        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+        // Clamp this factor, f, to -1 < f < 1
+        if (Math.abs(f) >= 1) {
+            f /= Math.abs(f);
+        }
+
+        return Math.round(OVERSCROLL_DAMP_FACTOR * f * max);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
new file mode 100644
index 0000000..2522052
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
@@ -0,0 +1,356 @@
+/*
+ * 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.systemui.qs.touch;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * One dimensional scroll/drag/swipe gesture detector.
+ *
+ * Definition of swipe is different from android system in that this detector handles
+ * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
+ * swipe action happens
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/SwipeDetector.java
+ */
+public class SwipeDetector {
+
+    private static final boolean DBG = false;
+    private static final String TAG = "SwipeDetector";
+
+    private int mScrollConditions;
+    public static final int DIRECTION_POSITIVE = 1 << 0;
+    public static final int DIRECTION_NEGATIVE = 1 << 1;
+    public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
+
+    private static final float ANIMATION_DURATION = 1200;
+
+    protected int mActivePointerId = INVALID_POINTER_ID;
+
+    /**
+     * The minimum release velocity in pixels per millisecond that triggers fling..
+     */
+    public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
+
+    /**
+     * The time constant used to calculate dampening in the low-pass filter of scroll velocity.
+     * Cutoff frequency is set at 10 Hz.
+     */
+    public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
+
+    /* Scroll state, this is set to true during dragging and animation. */
+    private ScrollState mState = ScrollState.IDLE;
+
+    enum ScrollState {
+        IDLE,
+        DRAGGING,      // onDragStart, onDrag
+        SETTLING       // onDragEnd
+    }
+
+    public static abstract class Direction {
+
+        abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint);
+
+        /**
+         * Distance in pixels a touch can wander before we think the user is scrolling.
+         */
+        abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
+    }
+
+    public static final Direction VERTICAL = new Direction() {
+
+        @Override
+        float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
+            return ev.getY(pointerIndex) - refPoint.y;
+        }
+
+        @Override
+        float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
+            return Math.abs(ev.getX(pointerIndex) - downPos.x);
+        }
+    };
+
+    public static final Direction HORIZONTAL = new Direction() {
+
+        @Override
+        float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
+            return ev.getX(pointerIndex) - refPoint.x;
+        }
+
+        @Override
+        float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
+            return Math.abs(ev.getY(pointerIndex) - downPos.y);
+        }
+    };
+
+    //------------------- ScrollState transition diagram -----------------------------------
+    //
+    // IDLE ->      (mDisplacement > mTouchSlop) -> DRAGGING
+    // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
+    // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
+    // SETTLING -> (View settled) -> IDLE
+
+    private void setState(ScrollState newState) {
+        if (DBG) {
+            Log.d(TAG, "setState:" + mState + "->" + newState);
+        }
+        // onDragStart and onDragEnd is reported ONLY on state transition
+        if (newState == ScrollState.DRAGGING) {
+            initializeDragging();
+            if (mState == ScrollState.IDLE) {
+                reportDragStart(false /* recatch */);
+            } else if (mState == ScrollState.SETTLING) {
+                reportDragStart(true /* recatch */);
+            }
+        }
+        if (newState == ScrollState.SETTLING) {
+            reportDragEnd();
+        }
+
+        mState = newState;
+    }
+
+    public boolean isDraggingOrSettling() {
+        return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
+    }
+
+    /**
+     * There's no touch and there's no animation.
+     */
+    public boolean isIdleState() {
+        return mState == ScrollState.IDLE;
+    }
+
+    public boolean isSettlingState() {
+        return mState == ScrollState.SETTLING;
+    }
+
+    public boolean isDraggingState() {
+        return mState == ScrollState.DRAGGING;
+    }
+
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+    private final Direction mDir;
+
+    private final float mTouchSlop;
+
+    /* Client of this gesture detector can register a callback. */
+    private final Listener mListener;
+
+    private long mCurrentMillis;
+
+    private float mVelocity;
+    private float mLastDisplacement;
+    private float mDisplacement;
+
+    private float mSubtractDisplacement;
+    private boolean mIgnoreSlopWhenSettling;
+
+    public interface Listener {
+        void onDragStart(boolean start);
+
+        boolean onDrag(float displacement, float velocity);
+
+        void onDragEnd(float velocity, boolean fling);
+    }
+
+    public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
+        this(ViewConfiguration.get(context).getScaledTouchSlop(), l, dir);
+    }
+
+    @VisibleForTesting
+    protected SwipeDetector(float touchSlope, @NonNull Listener l, @NonNull Direction dir) {
+        mTouchSlop = touchSlope;
+        mListener = l;
+        mDir = dir;
+    }
+
+    public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+        mScrollConditions = scrollDirectionFlags;
+        mIgnoreSlopWhenSettling = ignoreSlop;
+    }
+
+    private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
+        // reject cases where the angle or slop condition is not met.
+        if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
+                > Math.abs(mDisplacement)) {
+            return false;
+        }
+
+        // Check if the client is interested in scroll in current direction.
+        if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDisplacement > 0) ||
+                ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDisplacement < 0)) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean onTouchEvent(MotionEvent ev) {
+        switch (ev.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mActivePointerId = ev.getPointerId(0);
+                mDownPos.set(ev.getX(), ev.getY());
+                mLastPos.set(mDownPos);
+                mLastDisplacement = 0;
+                mDisplacement = 0;
+                mVelocity = 0;
+
+                if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+                    setState(ScrollState.DRAGGING);
+                }
+                break;
+            //case MotionEvent.ACTION_POINTER_DOWN:
+            case MotionEvent.ACTION_POINTER_UP:
+                int ptrIdx = ev.getActionIndex();
+                int ptrId = ev.getPointerId(ptrIdx);
+                if (ptrId == mActivePointerId) {
+                    final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+                    mDownPos.set(
+                            ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+                            ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+                    mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+                    mActivePointerId = ev.getPointerId(newPointerIdx);
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == INVALID_POINTER_ID) {
+                    break;
+                }
+                mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos);
+                computeVelocity(mDir.getDisplacement(ev, pointerIndex, mLastPos),
+                        ev.getEventTime());
+
+                // handle state and listener calls.
+                if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
+                    setState(ScrollState.DRAGGING);
+                }
+                if (mState == ScrollState.DRAGGING) {
+                    reportDragging();
+                }
+                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                // These are synthetic events and there is no need to update internal values.
+                if (mState == ScrollState.DRAGGING) {
+                    setState(ScrollState.SETTLING);
+                }
+                break;
+            default:
+                break;
+        }
+        return true;
+    }
+
+    public void finishedScrolling() {
+        setState(ScrollState.IDLE);
+    }
+
+    private boolean reportDragStart(boolean recatch) {
+        mListener.onDragStart(!recatch);
+        if (DBG) {
+            Log.d(TAG, "onDragStart recatch:" + recatch);
+        }
+        return true;
+    }
+
+    private void initializeDragging() {
+        if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+            mSubtractDisplacement = 0;
+        }
+        if (mDisplacement > 0) {
+            mSubtractDisplacement = mTouchSlop;
+        } else {
+            mSubtractDisplacement = -mTouchSlop;
+        }
+    }
+
+    private boolean reportDragging() {
+        if (mDisplacement != mLastDisplacement) {
+            if (DBG) {
+                Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f",
+                        mDisplacement, mVelocity));
+            }
+
+            mLastDisplacement = mDisplacement;
+            return mListener.onDrag(mDisplacement - mSubtractDisplacement, mVelocity);
+        }
+        return true;
+    }
+
+    private void reportDragEnd() {
+        if (DBG) {
+            Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
+                    mDisplacement, mVelocity));
+        }
+        mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
+
+    }
+
+    /**
+     * Computes the damped velocity.
+     */
+    public float computeVelocity(float delta, long currentMillis) {
+        long previousMillis = mCurrentMillis;
+        mCurrentMillis = currentMillis;
+
+        float deltaTimeMillis = mCurrentMillis - previousMillis;
+        float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0;
+        if (Math.abs(mVelocity) < 0.001f) {
+            mVelocity = velocity;
+        } else {
+            float alpha = computeDampeningFactor(deltaTimeMillis);
+            mVelocity = interpolate(mVelocity, velocity, alpha);
+        }
+        return mVelocity;
+    }
+
+    /**
+     * Returns a time-dependent dampening factor using delta time.
+     */
+    private static float computeDampeningFactor(float deltaTime) {
+        return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime);
+    }
+
+    /**
+     * Returns the linear interpolation between two values
+     */
+    private static float interpolate(float from, float to, float alpha) {
+        return (1.0f - alpha) * from + alpha * to;
+    }
+
+    public static long calculateDuration(float velocity, float progressNeeded) {
+        // TODO: make these values constants after tuning.
+        float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+        float travelDistance = Math.max(0.2f, progressNeeded);
+        long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+        if (DBG) {
+            Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+        }
+        return duration;
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index d6beb7f..ab89a52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -249,6 +249,9 @@
                     (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
             gradientDrawable.setXfermode(
                     running ? new PorterDuffXfermode(PorterDuff.Mode.SRC) : null);
+            // Speed optimization: disable AA if transfer mode is not SRC_OVER. AA is not easy to
+            // spot during animation anyways.
+            gradientDrawable.setAntiAlias(!running);
         }
         if (!mExpandAnimationRunning) {
             setDrawableAlpha(mDrawableAlpha);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 3ebeb4d..3dfb913 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -379,7 +379,7 @@
         // Because space is usually constrained in the auto use-case, there should not be a
         // pinned notification when the shade has been expanded. Ensure this by removing all heads-
         // up notifications.
-        mHeadsUpManager.removeAllHeadsUpEntries();
+        mHeadsUpManager.releaseAllImmediately();
         super.animateExpandNotificationsPanel();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
new file mode 100644
index 0000000..aba5cdf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -0,0 +1,446 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v4.util.ArraySet;
+import android.util.Log;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Stack;
+
+/**
+ * A implementation of HeadsUpManager for phone and car.
+ */
+public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
+       ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback,
+       OnHeadsUpChangedListener {
+    private static final String TAG = "HeadsUpManagerPhone";
+    private static final boolean DEBUG = false;
+
+    private final View mStatusBarWindowView;
+    private final int mStatusBarHeight;
+    private final NotificationGroupManager mGroupManager;
+    private final StatusBar mBar;
+    private final VisualStabilityManager mVisualStabilityManager;
+
+    private boolean mReleaseOnExpandFinish;
+    private boolean mTrackingHeadsUp;
+    private HashSet<String> mSwipedOutKeys = new HashSet<>();
+    private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
+    private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
+            = new ArraySet<>();
+    private boolean mIsExpanded;
+    private int[] mTmpTwoArray = new int[2];
+    private boolean mHeadsUpGoingAway;
+    private boolean mWaitingOnCollapseWhenGoingAway;
+    private boolean mIsObserving;
+    private int mStatusBarState;
+
+    private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
+        private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
+
+        @Override
+        public HeadsUpEntryPhone acquire() {
+            if (!mPoolObjects.isEmpty()) {
+                return mPoolObjects.pop();
+            }
+            return new HeadsUpEntryPhone();
+        }
+
+        @Override
+        public boolean release(@NonNull HeadsUpEntryPhone instance) {
+            mPoolObjects.push(instance);
+            return true;
+        }
+    };
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    //  Constructor:
+
+    public HeadsUpManagerPhone(@NonNull final Context context, @NonNull View statusBarWindowView,
+            @NonNull NotificationGroupManager groupManager, @NonNull StatusBar bar,
+            @NonNull VisualStabilityManager visualStabilityManager) {
+        super(context);
+
+        mStatusBarWindowView = statusBarWindowView;
+        mGroupManager = groupManager;
+        mBar = bar;
+        mVisualStabilityManager = visualStabilityManager;
+
+        Resources resources = mContext.getResources();
+        mStatusBarHeight = resources.getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
+
+        addListener(new OnHeadsUpChangedListener() {
+            @Override
+            public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) {
+                if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged");
+                updateTouchableRegionListener();
+            }
+        });
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    //  Public methods:
+
+    /**
+     * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
+     * that a user might have consciously clicked on it.
+     *
+     * @param key the key of the touched notification
+     * @return whether the touch is invalid and should be discarded
+     */
+    public boolean shouldSwallowClick(@NonNull String key) {
+        HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
+        return entry != null && mClock.currentTimeMillis() < entry.postTime;
+    }
+
+    public void onExpandingFinished() {
+        if (mReleaseOnExpandFinish) {
+            releaseAllImmediately();
+            mReleaseOnExpandFinish = false;
+        } else {
+            for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
+                if (isHeadsUp(entry.key)) {
+                    // Maybe the heads-up was removed already
+                    removeHeadsUpEntry(entry);
+                }
+            }
+        }
+        mEntriesToRemoveAfterExpand.clear();
+    }
+
+    /**
+     * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
+     * from the list even after a Heads Up Notification is gone.
+     */
+    public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+        mTrackingHeadsUp = trackingHeadsUp;
+    }
+
+    /**
+     * Notify that the status bar panel gets expanded or collapsed.
+     *
+     * @param isExpanded True to notify expanded, false to notify collapsed.
+     */
+    public void setIsPanelExpanded(boolean isExpanded) {
+        if (isExpanded != mIsExpanded) {
+            mIsExpanded = isExpanded;
+            if (isExpanded) {
+                // make sure our state is sane
+                mWaitingOnCollapseWhenGoingAway = false;
+                mHeadsUpGoingAway = false;
+                updateTouchableRegionListener();
+            }
+        }
+    }
+
+    /**
+     * Set the current state of the statusbar.
+     */
+    public void setStatusBarState(int statusBarState) {
+        mStatusBarState = statusBarState;
+    }
+
+    /**
+     * Set that we are exiting the headsUp pinned mode, but some notifications might still be
+     * animating out. This is used to keep the touchable regions in a sane state.
+     */
+    public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
+        if (headsUpGoingAway != mHeadsUpGoingAway) {
+            mHeadsUpGoingAway = headsUpGoingAway;
+            if (!headsUpGoingAway) {
+                waitForStatusBarLayout();
+            }
+            updateTouchableRegionListener();
+        }
+    }
+
+    /**
+     * Notifies that a remote input textbox in notification gets active or inactive.
+     * @param entry The entry of the target notification.
+     * @param remoteInputActive True to notify active, False to notify inactive.
+     */
+    public void setRemoteInputActive(
+            @NonNull NotificationData.Entry entry, boolean remoteInputActive) {
+        HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key);
+        if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
+            headsUpEntry.remoteInputActive = remoteInputActive;
+            if (remoteInputActive) {
+                headsUpEntry.removeAutoRemovalCallbacks();
+            } else {
+                headsUpEntry.updateEntry(false /* updatePostTime */);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public void removeMinimumDisplayTimeForTesting() {
+        mMinimumDisplayTime = 0;
+        mHeadsUpNotificationDecay = 0;
+        mTouchAcceptanceDelay = 0;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    //  HeadsUpManager public methods overrides:
+
+    @Override
+    public boolean isTrackingHeadsUp() {
+        return mTrackingHeadsUp;
+    }
+
+    @Override
+    public void snooze() {
+        super.snooze();
+        mReleaseOnExpandFinish = true;
+    }
+
+    /**
+     * React to the removal of the notification in the heads up.
+     *
+     * @return true if the notification was removed and false if it still needs to be kept around
+     * for a bit since it wasn't shown long enough
+     */
+    @Override
+    public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
+        if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
+            return super.removeNotification(key, ignoreEarliestRemovalTime);
+        } else {
+            HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key);
+            entry.removeAsSoonAsPossible();
+            return false;
+        }
+    }
+
+    public void addSwipedOutNotification(@NonNull String key) {
+        mSwipedOutKeys.add(key);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    //  Dumpable overrides:
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("HeadsUpManagerPhone state:");
+        dumpInternal(fd, pw, args);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    //  ViewTreeObserver.OnComputeInternalInsetsListener overrides:
+
+    /**
+     * Overridden from TreeObserver.
+     */
+    @Override
+    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+        if (mIsExpanded || mBar.isBouncerShowing()) {
+            // The touchable region is always the full area when expanded
+            return;
+        }
+        if (hasPinnedHeadsUp()) {
+            ExpandableNotificationRow topEntry = getTopEntry().row;
+            if (topEntry.isChildInGroup()) {
+                final ExpandableNotificationRow groupSummary
+                        = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
+                if (groupSummary != null) {
+                    topEntry = groupSummary;
+                }
+            }
+            topEntry.getLocationOnScreen(mTmpTwoArray);
+            int minX = mTmpTwoArray[0];
+            int maxX = mTmpTwoArray[0] + topEntry.getWidth();
+            int maxY = topEntry.getIntrinsicHeight();
+
+            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+            info.touchableRegion.set(minX, 0, maxX, maxY);
+        } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
+            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+            info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    //  VisualStabilityManager.Callback overrides:
+
+    @Override
+    public void onReorderingAllowed() {
+        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
+        for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
+            if (isHeadsUp(entry.key)) {
+                // Maybe the heads-up was removed already
+                removeHeadsUpEntry(entry);
+            }
+        }
+        mEntriesToRemoveWhenReorderingAllowed.clear();
+        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    //  HeadsUpManager utility (protected) methods overrides:
+
+    @Override
+    protected HeadsUpEntry createHeadsUpEntry() {
+        return mEntryPool.acquire();
+    }
+
+    @Override
+    protected void releaseHeadsUpEntry(HeadsUpEntry entry) {
+        entry.reset();
+        mEntryPool.release((HeadsUpEntryPhone) entry);
+    }
+
+    @Override
+    protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
+          return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded
+                  || super.shouldHeadsUpBecomePinned(entry);
+    }
+
+    @Override
+    protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dumpInternal(fd, pw, args);
+        pw.print("  mStatusBarState="); pw.println(mStatusBarState);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    //  Private utility methods:
+
+    @Nullable
+    private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
+        return (HeadsUpEntryPhone) getHeadsUpEntry(key);
+    }
+
+    @Nullable
+    private HeadsUpEntryPhone getTopHeadsUpEntryPhone() {
+        return (HeadsUpEntryPhone) getTopHeadsUpEntry();
+    }
+
+    private boolean wasShownLongEnough(@NonNull String key) {
+        if (mSwipedOutKeys.contains(key)) {
+            // We always instantly dismiss views being manually swiped out.
+            mSwipedOutKeys.remove(key);
+            return true;
+        }
+
+        HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key);
+        HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
+        return headsUpEntry != topEntry || headsUpEntry.wasShownLongEnough();
+    }
+
+    /**
+     * We need to wait on the whole panel to collapse, before we can remove the touchable region
+     * listener.
+     */
+    private void waitForStatusBarLayout() {
+        mWaitingOnCollapseWhenGoingAway = true;
+        mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                    int oldLeft,
+                    int oldTop, int oldRight, int oldBottom) {
+                if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
+                    mStatusBarWindowView.removeOnLayoutChangeListener(this);
+                    mWaitingOnCollapseWhenGoingAway = false;
+                    updateTouchableRegionListener();
+                }
+            }
+        });
+    }
+
+    private void updateTouchableRegionListener() {
+        boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway
+                || mWaitingOnCollapseWhenGoingAway;
+        if (shouldObserve == mIsObserving) {
+            return;
+        }
+        if (shouldObserve) {
+            mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+            mStatusBarWindowView.requestLayout();
+        } else {
+            mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+        }
+        mIsObserving = shouldObserve;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    //  HeadsUpEntryPhone:
+
+    protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry {
+        public void setEntry(@NonNull final NotificationData.Entry entry) {
+           Runnable removeHeadsUpRunnable = () -> {
+                if (!mVisualStabilityManager.isReorderingAllowed()) {
+                    mEntriesToRemoveWhenReorderingAllowed.add(entry);
+                    mVisualStabilityManager.addReorderingAllowedCallback(
+                            HeadsUpManagerPhone.this);
+                } else if (!mTrackingHeadsUp) {
+                    removeHeadsUpEntry(entry);
+                } else {
+                    mEntriesToRemoveAfterExpand.add(entry);
+                }
+            };
+
+            super.setEntry(entry, removeHeadsUpRunnable);
+        }
+
+        public boolean wasShownLongEnough() {
+            return earliestRemovaltime < mClock.currentTimeMillis();
+        }
+
+        @Override
+        public void updateEntry(boolean updatePostTime) {
+            super.updateEntry(updatePostTime);
+
+            if (mEntriesToRemoveAfterExpand.contains(entry)) {
+                mEntriesToRemoveAfterExpand.remove(entry);
+            }
+            if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
+                mEntriesToRemoveWhenReorderingAllowed.remove(entry);
+            }
+        }
+
+        @Override
+        public void expanded(boolean expanded) {
+            if (this.expanded == expanded) {
+                return;
+            }
+
+            this.expanded = expanded;
+            if (expanded) {
+                removeAutoRemovalCallbacks();
+            } else {
+                updateEntry(false /* updatePostTime */);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index c85571c..2bfdefe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -23,7 +23,7 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 /**
@@ -31,7 +31,7 @@
  */
 public class HeadsUpTouchHelper implements Gefingerpoken {
 
-    private HeadsUpManager mHeadsUpManager;
+    private HeadsUpManagerPhone mHeadsUpManager;
     private NotificationStackScrollLayout mStackScroller;
     private int mTrackingPointer;
     private float mTouchSlop;
@@ -43,7 +43,7 @@
     private NotificationPanelView mPanel;
     private ExpandableNotificationRow mPickedChild;
 
-    public HeadsUpTouchHelper(HeadsUpManager headsUpManager,
+    public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
             NotificationStackScrollLayout stackScroller,
             NotificationPanelView notificationPanelView) {
         mHeadsUpManager = headsUpManager;
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 cd2e77a..52d005c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -68,14 +68,12 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 import java.util.List;
-import java.util.Collection;
 
 public class NotificationPanelView extends PanelView implements
         ExpandableView.OnHeightChangedListener,
@@ -1571,7 +1569,7 @@
     private void updatePanelExpanded() {
         boolean isExpanded = !isFullyCollapsed();
         if (mPanelExpanded != isExpanded) {
-            mHeadsUpManager.setIsExpanded(isExpanded);
+            mHeadsUpManager.setIsPanelExpanded(isExpanded);
             mStatusBar.setPanelExpanded(isExpanded);
             mPanelExpanded = isExpanded;
         }
@@ -2338,7 +2336,7 @@
     }
 
     @Override
-    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
         super.setHeadsUpManager(headsUpManager);
         mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller,
                 this);
@@ -2630,8 +2628,8 @@
         }
     }
 
-    public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
-        mKeyguardStatusView.setPulsing(pulsing != null);
+    public void setPulsing(boolean pulsing) {
+        mKeyguardStatusView.setPulsing(pulsing);
         positionClockAndNotifications();
         mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1]
                 + mKeyguardStatusView.getClockBottom());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 2b7e474..6daabed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -50,7 +50,7 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -75,7 +75,7 @@
     }
 
     protected StatusBar mStatusBar;
-    protected HeadsUpManager mHeadsUpManager;
+    protected HeadsUpManagerPhone mHeadsUpManager;
 
     private float mPeekHeight;
     private float mHintDistance;
@@ -1252,7 +1252,7 @@
      */
     protected abstract int getClearAllHeight();
 
-    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
         mHeadsUpManager = headsUpManager;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 1bf719a..3777a6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -208,6 +208,7 @@
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -219,6 +220,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
@@ -809,15 +811,14 @@
                 .commit();
         mIconController = Dependency.get(StatusBarIconController.class);
 
-        mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager);
-        mHeadsUpManager.setBar(this);
+        mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this,
+                mVisualStabilityManager);
         mHeadsUpManager.addListener(this);
         mHeadsUpManager.addListener(mNotificationPanel);
         mHeadsUpManager.addListener(mGroupManager);
         mHeadsUpManager.addListener(mVisualStabilityManager);
         mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
-        mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
         putComponent(HeadsUpManager.class, mHeadsUpManager);
 
         mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
@@ -1348,7 +1349,8 @@
 
     @Override
     public void onPerformRemoveNotification(StatusBarNotification n) {
-        if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
+        if (mStackScroller.hasPulsingNotifications() &&
+                    !mHeadsUpManager.hasHeadsUpNotifications()) {
             // We were showing a pulse for a notification, but no notifications are pulsing anymore.
             // Finish the pulse.
             mDozeScrimController.pulseOutNow();
@@ -2097,9 +2099,8 @@
     }
 
     public void maybeEscalateHeadsUp() {
-        Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries();
-        for (HeadsUpManager.HeadsUpEntry entry : entries) {
-            final StatusBarNotification sbn = entry.entry.notification;
+        mHeadsUpManager.getAllEntries().forEach(entry -> {
+            final StatusBarNotification sbn = entry.notification;
             final Notification notification = sbn.getNotification();
             if (notification.fullScreenIntent != null) {
                 if (DEBUG) {
@@ -2109,11 +2110,11 @@
                     EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
                             sbn.getKey());
                     notification.fullScreenIntent.send();
-                    entry.entry.notifyFullScreenIntentLaunched();
+                    entry.notifyFullScreenIntentLaunched();
                 } catch (PendingIntent.CanceledException e) {
                 }
             }
-        }
+        });
         mHeadsUpManager.releaseAllImmediately();
     }
 
@@ -4658,24 +4659,22 @@
                 @Override
                 public void onPulseStarted() {
                     callback.onPulseStarted();
-                    Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries =
-                            mHeadsUpManager.getAllEntries();
-                    if (!pulsingEntries.isEmpty()) {
+                    if (mHeadsUpManager.hasHeadsUpNotifications()) {
                         // Only pulse the stack scroller if there's actually something to show.
                         // Otherwise just show the always-on screen.
-                        setPulsing(pulsingEntries);
+                        setPulsing(true);
                     }
                 }
 
                 @Override
                 public void onPulseFinished() {
                     callback.onPulseFinished();
-                    setPulsing(null);
+                    setPulsing(false);
                 }
 
-                private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
+                private void setPulsing(boolean pulsing) {
                     mNotificationPanel.setPulsing(pulsing);
-                    mVisualStabilityManager.setPulsing(pulsing != null);
+                    mVisualStabilityManager.setPulsing(pulsing);
                     mIgnoreTouchWhilePulsing = false;
                 }
             }, reason);
@@ -4823,7 +4822,7 @@
 
 
     // for heads up notifications
-    protected HeadsUpManager mHeadsUpManager;
+    protected HeadsUpManagerPhone mHeadsUpManager;
 
     private AboveShelfObserver mAboveShelfObserver;
 
@@ -4926,7 +4925,7 @@
                 // Release the HUN notification to the shade.
 
                 if (isPresenterFullyCollapsed()) {
-                    HeadsUpManager.setIsClickedNotification(row, true);
+                    HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
                 }
                 //
                 // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 53dfb24..040d7ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -16,119 +16,69 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.os.SystemClock;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.support.v4.util.ArraySet;
 import android.util.ArrayMap;
+import android.provider.Settings;
 import android.util.Log;
-import android.util.Pools;
-import android.view.View;
-import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Iterator;
+import java.util.stream.Stream;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Stack;
 
 /**
  * A manager which handles heads up notifications which is a special mode where
  * they simply peek from the top of the screen.
  */
-public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener,
-        VisualStabilityManager.Callback {
+public class HeadsUpManager {
     private static final String TAG = "HeadsUpManager";
     private static final boolean DEBUG = false;
     private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
-    private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
 
-    private final int mHeadsUpNotificationDecay;
-    private final int mMinimumDisplayTime;
+    protected final Clock mClock = new Clock();
+    protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
+    protected final Handler mHandler = new Handler(Looper.getMainLooper());
 
-    private final int mTouchAcceptanceDelay;
+    protected final Context mContext;
+
+    protected int mHeadsUpNotificationDecay;
+    protected int mMinimumDisplayTime;
+    protected int mTouchAcceptanceDelay;
+    protected int mSnoozeLengthMs;
+    protected boolean mHasPinnedNotification;
+    protected int mUser;
+
+    private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
     private final ArrayMap<String, Long> mSnoozedPackages;
-    private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
-    private final int mDefaultSnoozeLengthMs;
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-    private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
 
-        private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
-
-        @Override
-        public HeadsUpEntry acquire() {
-            if (!mPoolObjects.isEmpty()) {
-                return mPoolObjects.pop();
-            }
-            return new HeadsUpEntry();
-        }
-
-        @Override
-        public boolean release(HeadsUpEntry instance) {
-            instance.reset();
-            mPoolObjects.push(instance);
-            return true;
-        }
-    };
-
-    private final View mStatusBarWindowView;
-    private final int mStatusBarHeight;
-    private final Context mContext;
-    private final NotificationGroupManager mGroupManager;
-    private StatusBar mBar;
-    private int mSnoozeLengthMs;
-    private ContentObserver mSettingsObserver;
-    private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
-    private HashSet<String> mSwipedOutKeys = new HashSet<>();
-    private int mUser;
-    private Clock mClock;
-    private boolean mReleaseOnExpandFinish;
-    private boolean mTrackingHeadsUp;
-    private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
-    private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
-            = new ArraySet<>();
-    private boolean mIsExpanded;
-    private boolean mHasPinnedNotification;
-    private int[] mTmpTwoArray = new int[2];
-    private boolean mHeadsUpGoingAway;
-    private boolean mWaitingOnCollapseWhenGoingAway;
-    private boolean mIsObserving;
-    private boolean mRemoteInputActive;
-    private float mExpandedHeight;
-    private VisualStabilityManager mVisualStabilityManager;
-    private int mStatusBarState;
-
-    public HeadsUpManager(final Context context, View statusBarWindowView,
-                          NotificationGroupManager groupManager) {
+    public HeadsUpManager(@NonNull final Context context) {
         mContext = context;
-        Resources resources = mContext.getResources();
-        mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
-        mSnoozedPackages = new ArrayMap<>();
-        mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
-        mSnoozeLengthMs = mDefaultSnoozeLengthMs;
+        Resources resources = context.getResources();
         mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
         mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
-        mClock = new Clock();
+        mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
+        mSnoozedPackages = new ArrayMap<>();
+        int defaultSnoozeLengthMs =
+                resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
 
         mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
-                SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
-        mSettingsObserver = new ContentObserver(mHandler) {
+                SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs);
+        ContentObserver settingsObserver = new ContentObserver(mHandler) {
             @Override
             public void onChange(boolean selfChange) {
                 final int packageSnoozeLengthMs = Settings.Global.getInt(
@@ -141,48 +91,27 @@
         };
         context.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
-                mSettingsObserver);
-        mStatusBarWindowView = statusBarWindowView;
-        mGroupManager = groupManager;
-        mStatusBarHeight = resources.getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_height);
+                settingsObserver);
     }
 
-    private void updateTouchableRegionListener() {
-        boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway
-                || mWaitingOnCollapseWhenGoingAway;
-        if (shouldObserve == mIsObserving) {
-            return;
-        }
-        if (shouldObserve) {
-            mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
-            mStatusBarWindowView.requestLayout();
-        } else {
-            mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
-        }
-        mIsObserving = shouldObserve;
-    }
-
-    public void setBar(StatusBar bar) {
-        mBar = bar;
-    }
-
-    public void addListener(OnHeadsUpChangedListener listener) {
+    /**
+     * Adds an OnHeadUpChangedListener to observe events.
+     */
+    public void addListener(@NonNull OnHeadsUpChangedListener listener) {
         mListeners.add(listener);
     }
 
-    public void removeListener(OnHeadsUpChangedListener listener) {
+    /**
+     * Removes the OnHeadUpChangedListener from the observer list.
+     */
+    public void removeListener(@NonNull OnHeadsUpChangedListener listener) {
         mListeners.remove(listener);
     }
 
-    public StatusBar getBar() {
-        return mBar;
-    }
-
     /**
      * Called when posting a new notification to the heads up.
      */
-    public void showNotification(NotificationData.Entry headsUp) {
+    public void showNotification(@NonNull NotificationData.Entry headsUp) {
         if (DEBUG) Log.v(TAG, "showNotification");
         addHeadsUpEntry(headsUp);
         updateNotification(headsUp, true);
@@ -192,7 +121,7 @@
     /**
      * Called when updating or posting a notification to the heads up.
      */
-    public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
+    public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) {
         if (DEBUG) Log.v(TAG, "updateNotification");
 
         headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
@@ -204,14 +133,13 @@
                 // with the groupmanager
                 return;
             }
-            headsUpEntry.updateEntry();
+            headsUpEntry.updateEntry(true /* updatePostTime */);
             setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
         }
     }
 
-    private void addHeadsUpEntry(NotificationData.Entry entry) {
-        HeadsUpEntry headsUpEntry = mEntryPool.acquire();
-
+    private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) {
+        HeadsUpEntry headsUpEntry = createHeadsUpEntry();
         // This will also add the entry to the sortedList
         headsUpEntry.setEntry(entry);
         mHeadsUpEntries.put(entry.key, headsUpEntry);
@@ -223,16 +151,17 @@
         entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
     }
 
-    private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
-        return mStatusBarState != StatusBarState.KEYGUARD
-                && !mIsExpanded || hasFullScreenIntent(entry);
+    protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) {
+        return hasFullScreenIntent(entry);
     }
 
-    private boolean hasFullScreenIntent(NotificationData.Entry entry) {
+    protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) {
         return entry.notification.getNotification().fullScreenIntent != null;
     }
 
-    private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) {
+    protected void setEntryPinned(
+            @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) {
+        if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned);
         ExpandableNotificationRow row = headsUpEntry.entry.row;
         if (row.isPinned() != isPinned) {
             row.setPinned(isPinned);
@@ -247,33 +176,35 @@
         }
     }
 
-    private void removeHeadsUpEntry(NotificationData.Entry entry) {
+    protected void removeHeadsUpEntry(@NonNull NotificationData.Entry entry) {
         HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
+        onHeadsUpEntryRemoved(remove);
+    }
+
+    protected void onHeadsUpEntryRemoved(@NonNull HeadsUpEntry remove) {
+        NotificationData.Entry entry = remove.entry;
         entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
         entry.row.setHeadsUp(false);
         setEntryPinned(remove, false /* isPinned */);
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpStateChanged(entry, false);
         }
-        mEntryPool.release(remove);
+        releaseHeadsUpEntry(remove);
     }
 
-    public void removeAllHeadsUpEntries() {
-        for (String key : mHeadsUpEntries.keySet()) {
-            removeHeadsUpEntry(mHeadsUpEntries.get(key).entry);
-        }
-    }
-
-    private void updatePinnedMode() {
+    protected void updatePinnedMode() {
         boolean hasPinnedNotification = hasPinnedNotificationInternal();
         if (hasPinnedNotification == mHasPinnedNotification) {
             return;
         }
+        if (DEBUG) {
+            Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " +
+                       hasPinnedNotification);
+        }
         mHasPinnedNotification = hasPinnedNotification;
         if (mHasPinnedNotification) {
             MetricsLogger.count(mContext, "note_peek", 1);
         }
-        updateTouchableRegionListener();
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
         }
@@ -285,47 +216,36 @@
      * @return true if the notification was removed and false if it still needs to be kept around
      * for a bit since it wasn't shown long enough
      */
-    public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) {
-        if (DEBUG) Log.v(TAG, "remove");
-        if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
-            releaseImmediately(key);
-            return true;
-        } else {
-            getHeadsUpEntry(key).removeAsSoonAsPossible();
-            return false;
-        }
+    public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
+        if (DEBUG) Log.v(TAG, "removeNotification");
+        releaseImmediately(key);
+        return true;
     }
 
-    private boolean wasShownLongEnough(String key) {
-        HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
-        HeadsUpEntry topEntry = getTopEntry();
-        if (mSwipedOutKeys.contains(key)) {
-            // We always instantly dismiss views being manually swiped out.
-            mSwipedOutKeys.remove(key);
-            return true;
-        }
-        if (headsUpEntry != topEntry) {
-            return true;
-        }
-        return headsUpEntry.wasShownLongEnough();
-    }
-
-    public boolean isHeadsUp(String key) {
+    /**
+     * Returns if the given notification is in the Heads Up Notification list or not.
+     */
+    public boolean isHeadsUp(@NonNull String key) {
         return mHeadsUpEntries.containsKey(key);
     }
 
     /**
-     * Push any current Heads Up notification down into the shade.
+     * Pushes any current Heads Up notification down into the shade.
      */
     public void releaseAllImmediately() {
         if (DEBUG) Log.v(TAG, "releaseAllImmediately");
-        ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet());
-        for (String key : keys) {
-            releaseImmediately(key);
+        Iterator<HeadsUpEntry> iterator = mHeadsUpEntries.values().iterator();
+        while (iterator.hasNext()) {
+            HeadsUpEntry entry = iterator.next();
+            iterator.remove();
+            onHeadsUpEntryRemoved(entry);
         }
     }
 
-    public void releaseImmediately(String key) {
+    /**
+     * Pushes the given Heads Up notification down into the shade.
+     */
+    public void releaseImmediately(@NonNull String key) {
         HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
         if (headsUpEntry == null) {
             return;
@@ -334,11 +254,14 @@
         removeHeadsUpEntry(shadeEntry);
     }
 
-    public boolean isSnoozed(String packageName) {
+    /**
+     * Returns if the given notification is snoozed or not.
+     */
+    public boolean isSnoozed(@NonNull String packageName) {
         final String key = snoozeKey(packageName, mUser);
         Long snoozedUntil = mSnoozedPackages.get(key);
         if (snoozedUntil != null) {
-            if (snoozedUntil > SystemClock.elapsedRealtime()) {
+            if (snoozedUntil > mClock.currentTimeMillis()) {
                 if (DEBUG) Log.v(TAG, key + " snoozed");
                 return true;
             }
@@ -347,39 +270,71 @@
         return false;
     }
 
+    /**
+     * Snoozes all current Heads Up Notifications.
+     */
     public void snooze() {
         for (String key : mHeadsUpEntries.keySet()) {
             HeadsUpEntry entry = mHeadsUpEntries.get(key);
             String packageName = entry.entry.notification.getPackageName();
             mSnoozedPackages.put(snoozeKey(packageName, mUser),
-                    SystemClock.elapsedRealtime() + mSnoozeLengthMs);
+                    mClock.currentTimeMillis() + mSnoozeLengthMs);
         }
-        mReleaseOnExpandFinish = true;
     }
 
-    private static String snoozeKey(String packageName, int user) {
+    @NonNull
+    private static String snoozeKey(@NonNull String packageName, int user) {
         return user + "," + packageName;
     }
 
-    private HeadsUpEntry getHeadsUpEntry(String key) {
+    @Nullable
+    protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
         return mHeadsUpEntries.get(key);
     }
 
-    public NotificationData.Entry getEntry(String key) {
-        return mHeadsUpEntries.get(key).entry;
+    /**
+     * Returns the entry of given Heads Up Notification.
+     *
+     * @param key Key of heads up notification
+     */
+    @Nullable
+    public NotificationData.Entry getEntry(@NonNull String key) {
+        HeadsUpEntry entry = mHeadsUpEntries.get(key);
+        return entry != null ? entry.entry : null;
     }
 
-    public Collection<HeadsUpEntry> getAllEntries() {
-        return mHeadsUpEntries.values();
+    /**
+     * Returns the stream of all current Heads Up Notifications.
+     */
+    @NonNull
+    public Stream<NotificationData.Entry> getAllEntries() {
+        return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry);
     }
 
-    public HeadsUpEntry getTopEntry() {
+    /**
+     * Returns the top Heads Up Notification, which appeares to show at first.
+     */
+    @Nullable
+    public NotificationData.Entry getTopEntry() {
+        HeadsUpEntry topEntry = getTopHeadsUpEntry();
+        return (topEntry != null) ? topEntry.entry : null;
+    }
+
+    /**
+     * Returns if any heads up notification is available or not.
+     */
+    public boolean hasHeadsUpNotifications() {
+        return !mHeadsUpEntries.isEmpty();
+    }
+
+    @Nullable
+    protected HeadsUpEntry getTopHeadsUpEntry() {
         if (mHeadsUpEntries.isEmpty()) {
             return null;
         }
         HeadsUpEntry topEntry = null;
         for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
-            if (topEntry == null || entry.compareTo(topEntry) == -1) {
+            if (topEntry == null || entry.compareTo(topEntry) < 0) {
                 topEntry = entry;
             }
         }
@@ -387,56 +342,22 @@
     }
 
     /**
-     * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
-     * that a user might have consciously clicked on it.
-     *
-     * @param key the key of the touched notification
-     * @return whether the touch is invalid and should be discarded
+     * Sets the current user.
      */
-    public boolean shouldSwallowClick(String key) {
-        HeadsUpEntry entry = mHeadsUpEntries.get(key);
-        if (entry != null && mClock.currentTimeMillis() < entry.postTime) {
-            return true;
-        }
-        return false;
-    }
-
-    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
-        if (mIsExpanded || mBar.isBouncerShowing()) {
-            // The touchable region is always the full area when expanded
-            return;
-        }
-        if (mHasPinnedNotification) {
-            ExpandableNotificationRow topEntry = getTopEntry().entry.row;
-            if (topEntry.isChildInGroup()) {
-                final ExpandableNotificationRow groupSummary
-                        = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
-                if (groupSummary != null) {
-                    topEntry = groupSummary;
-                }
-            }
-            topEntry.getLocationOnScreen(mTmpTwoArray);
-            int minX = mTmpTwoArray[0];
-            int maxX = mTmpTwoArray[0] + topEntry.getWidth();
-            int maxY = topEntry.getIntrinsicHeight();
-
-            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-            info.touchableRegion.set(minX, 0, maxX, maxY);
-        } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
-            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-            info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
-        }
-    }
-
     public void setUser(int user) {
         mUser = user;
     }
 
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("HeadsUpManager state:");
+        dumpInternal(fd, pw, args);
+    }
+
+    protected void dumpInternal(
+            @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.print("  mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
         pw.print("  mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
-        pw.print("  now="); pw.println(SystemClock.elapsedRealtime());
+        pw.print("  now="); pw.println(mClock.currentTimeMillis());
         pw.print("  mUser="); pw.println(mUser);
         for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
             pw.print("  HeadsUpEntry="); pw.println(entry.entry);
@@ -449,6 +370,9 @@
         }
     }
 
+    /**
+     * Returns if there are any pinned Heads Up Notifications or not.
+     */
     public boolean hasPinnedHeadsUp() {
         return mHasPinnedNotification;
     }
@@ -464,14 +388,8 @@
     }
 
     /**
-     * Notifies that a notification was swiped out and will be removed.
-     *
-     * @param key the notification key
+     * Unpins all pinned Heads Up Notifications.
      */
-    public void addSwipedOutNotification(String key) {
-        mSwipedOutKeys.add(key);
-    }
-
     public void unpinAll() {
         for (String key : mHeadsUpEntries.keySet()) {
             HeadsUpEntry entry = mHeadsUpEntries.get(key);
@@ -481,60 +399,13 @@
         }
     }
 
-    public void onExpandingFinished() {
-        if (mReleaseOnExpandFinish) {
-            releaseAllImmediately();
-            mReleaseOnExpandFinish = false;
-        } else {
-            for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
-                if (isHeadsUp(entry.key)) {
-                    // Maybe the heads-up was removed already
-                    removeHeadsUpEntry(entry);
-                }
-            }
-        }
-        mEntriesToRemoveAfterExpand.clear();
-    }
-
-    public void setTrackingHeadsUp(boolean trackingHeadsUp) {
-        mTrackingHeadsUp = trackingHeadsUp;
-    }
-
-    public boolean isTrackingHeadsUp() {
-        return mTrackingHeadsUp;
-    }
-
-    public void setIsExpanded(boolean isExpanded) {
-        if (isExpanded != mIsExpanded) {
-            mIsExpanded = isExpanded;
-            if (isExpanded) {
-                // make sure our state is sane
-                mWaitingOnCollapseWhenGoingAway = false;
-                mHeadsUpGoingAway = false;
-                updateTouchableRegionListener();
-            }
-        }
-    }
-
     /**
-     * @return the height of the top heads up notification when pinned. This is different from the
-     *         intrinsic height, which also includes whether the notification is system expanded and
-     *         is mainly used when dragging down from a heads up notification.
+     * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
+     * well.
      */
-    public int getTopHeadsUpPinnedHeight() {
-        HeadsUpEntry topEntry = getTopEntry();
-        if (topEntry == null || topEntry.entry == null) {
-            return 0;
-        }
-        ExpandableNotificationRow row = topEntry.entry.row;
-        if (row.isChildInGroup()) {
-            final ExpandableNotificationRow groupSummary
-                    = mGroupManager.getGroupSummary(row.getStatusBarNotification());
-            if (groupSummary != null) {
-                row = groupSummary;
-            }
-        }
-        return row.getPinnedHeadsUpHeight();
+    public boolean isTrackingHeadsUp() {
+        // Might be implemented in subclass.
+        return false;
     }
 
     /**
@@ -543,7 +414,7 @@
      * @return -1 if the first argument should be ranked higher than the second, 1 if the second
      * one should be ranked higher and 0 if they are equal.
      */
-    public int compare(NotificationData.Entry a, NotificationData.Entry b) {
+    public int compare(@NonNull NotificationData.Entry a, @NonNull NotificationData.Entry b) {
         HeadsUpEntry aEntry = getHeadsUpEntry(a.key);
         HeadsUpEntry bEntry = getHeadsUpEntry(b.key);
         if (aEntry == null || bEntry == null) {
@@ -553,147 +424,62 @@
     }
 
     /**
-     * Set that we are exiting the headsUp pinned mode, but some notifications might still be
-     * animating out. This is used to keep the touchable regions in a sane state.
-     */
-    public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
-        if (headsUpGoingAway != mHeadsUpGoingAway) {
-            mHeadsUpGoingAway = headsUpGoingAway;
-            if (!headsUpGoingAway) {
-                waitForStatusBarLayout();
-            }
-            updateTouchableRegionListener();
-        }
-    }
-
-    /**
-     * We need to wait on the whole panel to collapse, before we can remove the touchable region
-     * listener.
-     */
-    private void waitForStatusBarLayout() {
-        mWaitingOnCollapseWhenGoingAway = true;
-        mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
-            @Override
-            public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                    int oldLeft,
-                    int oldTop, int oldRight, int oldBottom) {
-                if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
-                    mStatusBarWindowView.removeOnLayoutChangeListener(this);
-                    mWaitingOnCollapseWhenGoingAway = false;
-                    updateTouchableRegionListener();
-                }
-            }
-        });
-    }
-
-    public static void setIsClickedNotification(View child, boolean clicked) {
-        child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
-    }
-
-    public static boolean isClickedHeadsUpNotification(View child) {
-        Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION);
-        return clicked != null && clicked;
-    }
-
-    public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) {
-        HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
-        if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
-            headsUpEntry.remoteInputActive = remoteInputActive;
-            if (remoteInputActive) {
-                headsUpEntry.removeAutoRemovalCallbacks();
-            } else {
-                headsUpEntry.updateEntry(false /* updatePostTime */);
-            }
-        }
-    }
-
-    /**
      * Set an entry to be expanded and therefore stick in the heads up area if it's pinned
      * until it's collapsed again.
      */
-    public void setExpanded(NotificationData.Entry entry, boolean expanded) {
-        HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
-        if (headsUpEntry != null && headsUpEntry.expanded != expanded && entry.row.isPinned()) {
-            headsUpEntry.expanded = expanded;
-            if (expanded) {
-                headsUpEntry.removeAutoRemovalCallbacks();
-            } else {
-                headsUpEntry.updateEntry(false /* updatePostTime */);
-            }
+    public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) {
+        HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
+        if (headsUpEntry != null && entry.row.isPinned()) {
+            headsUpEntry.expanded(expanded);
         }
     }
 
-    @Override
-    public void onReorderingAllowed() {
-        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
-        for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
-            if (isHeadsUp(entry.key)) {
-                // Maybe the heads-up was removed already
-                removeHeadsUpEntry(entry);
-            }
-        }
-        mEntriesToRemoveWhenReorderingAllowed.clear();
-        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
+    @NonNull
+    protected HeadsUpEntry createHeadsUpEntry() {
+        return new HeadsUpEntry();
     }
 
-    public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) {
-        mVisualStabilityManager = visualStabilityManager;
-    }
-
-    public void setStatusBarState(int statusBarState) {
-        mStatusBarState = statusBarState;
+    protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) {
+        entry.reset();
     }
 
     /**
      * This represents a notification and how long it is in a heads up mode. It also manages its
      * lifecycle automatically when created.
      */
-    public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
-        public NotificationData.Entry entry;
+    protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+        @Nullable public NotificationData.Entry entry;
         public long postTime;
-        public long earliestRemovaltime;
-        private Runnable mRemoveHeadsUpRunnable;
         public boolean remoteInputActive;
+        public long earliestRemovaltime;
         public boolean expanded;
 
-        public void setEntry(final NotificationData.Entry entry) {
+        @Nullable private Runnable mRemoveHeadsUpRunnable;
+
+        public void setEntry(@Nullable final NotificationData.Entry entry) {
+            setEntry(entry, null);
+        }
+
+        public void setEntry(@Nullable final NotificationData.Entry entry,
+                @Nullable Runnable removeHeadsUpRunnable) {
             this.entry = entry;
+            this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable;
 
             // The actual post time will be just after the heads-up really slided in
             postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay;
-            mRemoveHeadsUpRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    if (!mVisualStabilityManager.isReorderingAllowed()) {
-                        mEntriesToRemoveWhenReorderingAllowed.add(entry);
-                        mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this);
-                    } else if (!mTrackingHeadsUp) {
-                        removeHeadsUpEntry(entry);
-                    } else {
-                        mEntriesToRemoveAfterExpand.add(entry);
-                    }
-                }
-            };
-            updateEntry();
-        }
-
-        public void updateEntry() {
-            updateEntry(true);
+            updateEntry(true /* updatePostTime */);
         }
 
         public void updateEntry(boolean updatePostTime) {
+            if (DEBUG) Log.v(TAG, "updateEntry");
+
             long currentTime = mClock.currentTimeMillis();
             earliestRemovaltime = currentTime + mMinimumDisplayTime;
             if (updatePostTime) {
                 postTime = Math.max(postTime, currentTime);
             }
             removeAutoRemovalCallbacks();
-            if (mEntriesToRemoveAfterExpand.contains(entry)) {
-                mEntriesToRemoveAfterExpand.remove(entry);
-            }
-            if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
-                mEntriesToRemoveWhenReorderingAllowed.remove(entry);
-            }
+
             if (!isSticky()) {
                 long finishTime = postTime + mHeadsUpNotificationDecay;
                 long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
@@ -707,7 +493,7 @@
         }
 
         @Override
-        public int compareTo(HeadsUpEntry o) {
+        public int compareTo(@NonNull HeadsUpEntry o) {
             boolean isPinned = entry.row.isPinned();
             boolean otherPinned = o.entry.row.isPinned();
             if (isPinned && !otherPinned) {
@@ -734,26 +520,29 @@
                             : -1;
         }
 
-        public void removeAutoRemovalCallbacks() {
-            mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
-        }
-
-        public boolean wasShownLongEnough() {
-            return earliestRemovaltime < mClock.currentTimeMillis();
-        }
-
-        public void removeAsSoonAsPossible() {
-            removeAutoRemovalCallbacks();
-            mHandler.postDelayed(mRemoveHeadsUpRunnable,
-                    earliestRemovaltime - mClock.currentTimeMillis());
+        public void expanded(boolean expanded) {
+            this.expanded = expanded;
         }
 
         public void reset() {
-            removeAutoRemovalCallbacks();
             entry = null;
-            mRemoveHeadsUpRunnable = null;
             expanded = false;
             remoteInputActive = false;
+            removeAutoRemovalCallbacks();
+            mRemoveHeadsUpRunnable = null;
+        }
+
+        public void removeAutoRemovalCallbacks() {
+            if (mRemoveHeadsUpRunnable != null)
+                mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
+        }
+
+        public void removeAsSoonAsPossible() {
+            if (mRemoveHeadsUpRunnable != null) {
+                removeAutoRemovalCallbacks();
+                mHandler.postDelayed(mRemoveHeadsUpRunnable,
+                        earliestRemovaltime - mClock.currentTimeMillis());
+            }
         }
     }
 
@@ -762,5 +551,4 @@
             return SystemClock.elapsedRealtime();
         }
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
new file mode 100644
index 0000000..1e3c123c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * A class of utility static methods for heads up notifications.
+ */
+public final class HeadsUpUtil {
+    private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
+
+    /**
+     * Set the given view as clicked or not-clicked.
+     * @param view The view to be set the flag to.
+     * @param clicked True to set as clicked. False to not-clicked.
+     */
+    public static void setIsClickedHeadsUpNotification(View view, boolean clicked) {
+        view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
+    }
+
+    /**
+     * Check if the given view has the flag of "clicked notification"
+     * @param view The view to be checked.
+     * @return True if the view has clicked. False othrewise.
+     */
+    public static boolean isClickedHeadsUpNotification(View view) {
+        Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION);
+        return clicked != null && clicked;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 077c6c3..ea449c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -28,6 +28,8 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.HapticFeedbackConstants;
@@ -45,6 +47,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
 
@@ -58,12 +61,16 @@
     private long mDownTime;
     private int mCode;
     private int mTouchSlop;
+    private int mTouchDownX;
+    private int mTouchDownY;
     private boolean mSupportsLongpress = true;
     private AudioManager mAudioManager;
     private boolean mGestureAborted;
     private boolean mLongClicked;
     private OnClickListener mOnClickListener;
     private final KeyButtonRipple mRipple;
+    private final OverviewProxyService mOverviewProxyService;
+    private final Vibrator mVibrator;
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     private final Runnable mCheckLongPress = new Runnable() {
@@ -110,6 +117,8 @@
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
 
         mRipple = new KeyButtonRipple(context, this);
+        mVibrator = mContext.getSystemService(Vibrator.class);
+        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
         setBackground(mRipple);
     }
 
@@ -189,6 +198,7 @@
     }
 
     public boolean onTouchEvent(MotionEvent ev) {
+        final boolean isProxyConnected = mOverviewProxyService.getProxy() != null;
         final int action = ev.getAction();
         int x, y;
         if (action == MotionEvent.ACTION_DOWN) {
@@ -203,23 +213,34 @@
                 mDownTime = SystemClock.uptimeMillis();
                 mLongClicked = false;
                 setPressed(true);
+                mTouchDownX = (int) ev.getX();
+                mTouchDownY = (int) ev.getY();
                 if (mCode != 0) {
                     sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
                 } else {
                     // Provide the same haptic feedback that the system offers for virtual keys.
                     performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
                 }
-                playSoundEffect(SoundEffectConstants.CLICK);
+                if (isProxyConnected) {
+                    // Provide small vibration for quick step or immediate down feedback
+                    AsyncTask.execute(() ->
+                            mVibrator.vibrate(VibrationEffect
+                                    .get(VibrationEffect.EFFECT_TICK, false)));
+                } else {
+                    playSoundEffect(SoundEffectConstants.CLICK);
+                }
                 removeCallbacks(mCheckLongPress);
                 postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
                 break;
             case MotionEvent.ACTION_MOVE:
                 x = (int)ev.getX();
                 y = (int)ev.getY();
-                setPressed(x >= -mTouchSlop
-                        && x < getWidth() + mTouchSlop
-                        && y >= -mTouchSlop
-                        && y < getHeight() + mTouchSlop);
+                boolean exceededTouchSlopX = Math.abs(x - mTouchDownX) > mTouchSlop;
+                boolean exceededTouchSlopY = Math.abs(y - mTouchDownY) > mTouchSlop;
+                if (exceededTouchSlopX || exceededTouchSlopY) {
+                    setPressed(false);
+                    removeCallbacks(mCheckLongPress);
+                }
                 break;
             case MotionEvent.ACTION_CANCEL:
                 setPressed(false);
@@ -231,9 +252,14 @@
             case MotionEvent.ACTION_UP:
                 final boolean doIt = isPressed() && !mLongClicked;
                 setPressed(false);
-                // Always send a release ourselves because it doesn't seem to be sent elsewhere
-                // and it feels weird to sometimes get a release haptic and other times not.
-                if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) {
+                if (isProxyConnected) {
+                    if (doIt) {
+                        performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+                        playSoundEffect(SoundEffectConstants.CLICK);
+                    }
+                } else if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) {
+                    // Always send a release ourselves because it doesn't seem to be sent elsewhere
+                    // and it feels weird to sometimes get a release haptic and other times not.
                     performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
                 }
                 if (mCode != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 424858a..d7a810e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -64,7 +64,7 @@
     private boolean mPanelTracking;
     private boolean mExpansionChanging;
     private boolean mPanelFullWidth;
-    private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
+    private boolean mPulsing;
     private boolean mUnlockHintRunning;
     private boolean mQsCustomizerShowing;
     private int mIntrinsicPadding;
@@ -315,23 +315,18 @@
     }
 
     public boolean hasPulsingNotifications() {
-        return mPulsing != null;
+        return mPulsing;
     }
 
-    public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> hasPulsing) {
+    public void setPulsing(boolean hasPulsing) {
         mPulsing = hasPulsing;
     }
 
     public boolean isPulsing(NotificationData.Entry entry) {
-        if (mPulsing == null) {
+        if (!mPulsing || mHeadsUpManager == null) {
             return false;
         }
-        for (HeadsUpManager.HeadsUpEntry e : mPulsing) {
-            if (e.entry == entry) {
-                return true;
-            }
-        }
-        return false;
+        return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry));
     }
 
     public boolean isPanelTracking() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index c114a6f..1b55a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -92,10 +92,11 @@
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 
 import android.support.v4.graphics.ColorUtils;
@@ -288,7 +289,7 @@
     private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
     private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
             = new HashSet<>();
-    private HeadsUpManager mHeadsUpManager;
+    private HeadsUpManagerPhone mHeadsUpManager;
     private boolean mTrackingHeadsUp;
     private ScrimController mScrimController;
     private boolean mForceNoOverlappingRendering;
@@ -358,7 +359,7 @@
         }
     };
     private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
-    private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
+    private boolean mPulsing;
     private boolean mDrawBackgroundAsSrc;
     private boolean mFadingOut;
     private boolean mParentNotFullyVisible;
@@ -690,7 +691,7 @@
     }
 
     private void updateAlgorithmHeightAndPadding() {
-        if (mPulsing != null) {
+        if (mPulsing) {
             mTopPadding = mClockBottom;
         } else {
             mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding;
@@ -920,6 +921,27 @@
     }
 
     /**
+     * @return the height of the top heads up notification when pinned. This is different from the
+     *         intrinsic height, which also includes whether the notification is system expanded and
+     *         is mainly used when dragging down from a heads up notification.
+     */
+    private int getTopHeadsUpPinnedHeight() {
+        NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
+        if (topEntry == null) {
+            return 0;
+        }
+        ExpandableNotificationRow row = topEntry.row;
+        if (row.isChildInGroup()) {
+            final ExpandableNotificationRow groupSummary
+                    = mGroupManager.getGroupSummary(row.getStatusBarNotification());
+            if (groupSummary != null) {
+                row = groupSummary;
+            }
+        }
+        return row.getPinnedHeadsUpHeight();
+    }
+
+    /**
      * @return the position from where the appear transition ends when expanding.
      *         Measured in absolute height.
      */
@@ -930,7 +952,7 @@
             int minNotificationsForShelf = 1;
             if (mTrackingHeadsUp
                     || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) {
-                appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight();
+                appearPosition = getTopHeadsUpPinnedHeight();
                 minNotificationsForShelf = 2;
             } else {
                 appearPosition = 0;
@@ -1198,9 +1220,9 @@
                 if (slidingChild instanceof ExpandableNotificationRow) {
                     ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
                     if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
-                            && mHeadsUpManager.getTopEntry().entry.row != row
+                            && mHeadsUpManager.getTopEntry().row != row
                             && mGroupManager.getGroupSummary(
-                                mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
+                                mHeadsUpManager.getTopEntry().row.getStatusBarNotification())
                                 != row) {
                         continue;
                     }
@@ -2120,7 +2142,7 @@
 
     @Override
     public boolean hasPulsingNotifications() {
-        return mPulsing != null;
+        return mPulsing;
     }
 
     private void updateScrollability() {
@@ -2753,7 +2775,7 @@
     }
 
     private boolean isClickedHeadsUp(View child) {
-        return HeadsUpManager.isClickedHeadsUpNotification(child);
+        return HeadsUpUtil.isClickedHeadsUpNotification(child);
     }
 
     /**
@@ -4258,7 +4280,7 @@
         mAnimationFinishedRunnables.add(runnable);
     }
 
-    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
         mHeadsUpManager = headsUpManager;
         mAmbientState.setHeadsUpManager(headsUpManager);
     }
@@ -4326,8 +4348,8 @@
         return mIsExpanded;
     }
 
-    public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing, int clockBottom) {
-        if (mPulsing == null && pulsing == null) {
+    public void setPulsing(boolean pulsing, int clockBottom) {
+        if (!mPulsing && !pulsing) {
             return;
         }
         mPulsing = pulsing;
@@ -4466,7 +4488,7 @@
         pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
                         + " alpha:%f scrollY:%d]",
                 this.getClass().getSimpleName(),
-                mPulsing != null ?"T":"f",
+                mPulsing ? "T":"f",
                 mAmbientState.isQsCustomizerShowing() ? "T":"f",
                 getVisibility() == View.VISIBLE ? "visible"
                         : getVisibility() == View.GONE ? "gone"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 682b849..04a7bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -30,7 +30,7 @@
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
 
 /**
  * A state of a view. This can be used to apply a set of view properties to a view with
@@ -582,7 +582,7 @@
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                HeadsUpManager.setIsClickedNotification(child, false);
+                HeadsUpUtil.setIsClickedHeadsUpNotification(child, false);
                 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
                 child.setTag(TAG_START_TRANSLATION_Y, null);
                 child.setTag(TAG_END_TRANSLATION_Y, null);
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 936ff51..59a7da6 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -65,7 +65,6 @@
 LOCAL_JAVA_LIBRARIES := \
     android.test.runner \
     telephony-common \
-    android.car \
     android.test.base \
 
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 3e37cfe..bf6cc53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -151,22 +151,4 @@
         verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(),
                 eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any());
     }
-
-    @Test
-    public void testGetTimeRemainingFormatted_roundsDownTo15() {
-        mPowerNotificationWarnings.updateEstimate(
-                new Estimate(TimeUnit.MINUTES.toMillis(57), true));
-        String time = mPowerNotificationWarnings.getTimeRemainingFormatted();
-
-        assertTrue("time:" + time + ", expected: " + FORMATTED_45M, time.equals(FORMATTED_45M));
-    }
-
-    @Test
-    public void testGetTimeRemainingFormatted_keepsMinutesWhenZero() {
-        mPowerNotificationWarnings.updateEstimate(
-                new Estimate(TimeUnit.MINUTES.toMillis(65), true));
-        String time = mPowerNotificationWarnings.getTimeRemainingFormatted();
-
-        assertTrue("time:" + time + ", expected: " + FORMATTED_HOUR, time.equals(FORMATTED_HOUR));
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 6e7477f..f3c1171 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.NotificationInflaterTest;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -51,7 +52,7 @@
     public NotificationTestHelper(Context context) {
         mContext = context;
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mHeadsUpManager = new HeadsUpManager(mContext, null, mGroupManager);
+        mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null);
     }
 
     public ExpandableNotificationRow createRow() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
new file mode 100644
index 0000000..aa991cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.app.Notification;
+import android.os.UserHandle;
+import android.view.View;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class HeadsUpManagerPhoneTest extends SysuiTestCase {
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    private static final String TEST_PACKAGE_NAME = "test";
+    private static final int TEST_UID = 0;
+
+    private HeadsUpManagerPhone mHeadsUpManager;
+
+    private NotificationData.Entry mEntry;
+    private StatusBarNotification mSbn;
+
+    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private View mStatusBarWindowView;
+    @Mock private StatusBar mBar;
+    @Mock private ExpandableNotificationRow mRow;
+    @Mock private VisualStabilityManager mVSManager;
+
+    @Before
+    public void setUp() {
+        when(mVSManager.isReorderingAllowed()).thenReturn(true);
+
+        mHeadsUpManager = new HeadsUpManagerPhone(
+                mContext, mStatusBarWindowView, mGroupManager, mBar, mVSManager);
+
+        Notification.Builder n = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text");
+        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
+             0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0);
+
+        mEntry = new NotificationData.Entry(mSbn);
+        mEntry.row = mRow;
+        mEntry.expandedIcon = mock(StatusBarIconView.class);
+    }
+
+    @Test
+    public void testBasicOperations() {
+        // Check the initial state.
+        assertNull(mHeadsUpManager.getEntry(mEntry.key));
+        assertNull(mHeadsUpManager.getTopEntry());
+        assertEquals(0, mHeadsUpManager.getAllEntries().count());
+        assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+        // Add a notification.
+        mHeadsUpManager.showNotification(mEntry);
+
+        assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+        assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+        assertEquals(1, mHeadsUpManager.getAllEntries().count());
+        assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+        // Update the notification.
+        mHeadsUpManager.updateNotification(mEntry, false);
+
+        assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+        assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+        assertEquals(1, mHeadsUpManager.getAllEntries().count());
+        assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+        // Try to remove but defer, since the notification is currenlt visible on display.
+        mHeadsUpManager.removeNotification(mEntry.key, false /* ignoreEarliestRemovalTime */);
+
+        assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+        assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+        assertEquals(1, mHeadsUpManager.getAllEntries().count());
+        assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+        // Remove forcibly with ignoreEarliestRemovalTime = true.
+        mHeadsUpManager.removeNotification(mEntry.key, true /* ignoreEarliestRemovalTime */);
+
+        // Check the initial state.
+        assertNull(mHeadsUpManager.getEntry(mEntry.key));
+        assertNull(mHeadsUpManager.getTopEntry());
+        assertEquals(0, mHeadsUpManager.getAllEntries().count());
+        assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+    }
+
+    @Test
+    public void testsTimeoutRemoval() {
+        mHeadsUpManager.removeMinimumDisplayTimeForTesting();
+
+        // Check the initial state.
+        assertNull(mHeadsUpManager.getEntry(mEntry.key));
+        assertNull(mHeadsUpManager.getTopEntry());
+        assertEquals(0, mHeadsUpManager.getAllEntries().count());
+        assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        // Run the code on the main thready, not to run an async operations.
+        instrumentation.runOnMainSync(() -> {
+            // Add a notification.
+            mHeadsUpManager.showNotification(mEntry);
+
+            // Ensure the head up is visible before timeout.
+            assertNotNull(mHeadsUpManager.getEntry(mEntry.key));
+            assertNotNull(mHeadsUpManager.getTopEntry());
+            assertEquals(1, mHeadsUpManager.getAllEntries().count());
+            assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+        });
+        // Wait for the async operations, which removes the heads up notification.
+        waitForIdleSync();
+
+        assertNull(mHeadsUpManager.getEntry(mEntry.key));
+        assertNull(mHeadsUpManager.getTopEntry());
+        assertEquals(0, mHeadsUpManager.getAllEntries().count());
+        assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+    }
+
+    @Test
+    public void releaseImmediately() {
+        // Check the initial state.
+        assertNull(mHeadsUpManager.getEntry(mEntry.key));
+        assertNull(mHeadsUpManager.getTopEntry());
+        assertEquals(0, mHeadsUpManager.getAllEntries().count());
+        assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+        // Add a notification.
+        mHeadsUpManager.showNotification(mEntry);
+
+        assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+        assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+        assertEquals(1, mHeadsUpManager.getAllEntries().count());
+        assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+        // Remove but defer, since the notification is visible on display.
+        mHeadsUpManager.releaseImmediately(mEntry.key);
+
+        assertNull(mHeadsUpManager.getEntry(mEntry.key));
+        assertNull(mHeadsUpManager.getTopEntry());
+        assertEquals(0, mHeadsUpManager.getAllEntries().count());
+        assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+    }
+
+    @Test
+    public void releaseAllImmediately() {
+        // Check the initial state.
+        assertNull(mHeadsUpManager.getEntry(mEntry.key));
+        assertNull(mHeadsUpManager.getTopEntry());
+        assertEquals(0, mHeadsUpManager.getAllEntries().count());
+        assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+        // Add a notification.
+        mHeadsUpManager.showNotification(mEntry);
+
+        assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+        assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+        assertEquals(1, mHeadsUpManager.getAllEntries().count());
+        assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+        // Remove but defer, since the notification is visible on display.
+        mHeadsUpManager.releaseAllImmediately();
+
+        assertNull(mHeadsUpManager.getEntry(mEntry.key));
+        assertNull(mHeadsUpManager.getTopEntry());
+        assertEquals(0, mHeadsUpManager.getAllEntries().count());
+        assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index bdf9b1f..31442af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -86,8 +86,8 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -110,7 +110,7 @@
     @Mock private UnlockMethodCache mUnlockMethodCache;
     @Mock private KeyguardIndicationController mKeyguardIndicationController;
     @Mock private NotificationStackScrollLayout mStackScroller;
-    @Mock private HeadsUpManager mHeadsUpManager;
+    @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private SystemServicesProxy mSystemServicesProxy;
     @Mock private NotificationPanelView mNotificationPanelView;
     @Mock private IStatusBarService mBarService;
@@ -588,7 +588,7 @@
     static class TestableStatusBar extends StatusBar {
         public TestableStatusBar(StatusBarKeyguardViewManager man,
                 UnlockMethodCache unlock, KeyguardIndicationController key,
-                NotificationStackScrollLayout stack, HeadsUpManager hum,
+                NotificationStackScrollLayout stack, HeadsUpManagerPhone hum,
                 PowerManager pm, NotificationPanelView panelView,
                 IStatusBarService barService, NotificationListener notificationListener,
                 NotificationLogger notificationLogger,
@@ -650,7 +650,7 @@
         public void setUpForTest(NotificationPresenter presenter,
                 NotificationListContainer listContainer,
                 Callback callback,
-                HeadsUpManager headsUpManager,
+                HeadsUpManagerPhone headsUpManager,
                 NotificationData notificationData) {
             super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager);
             mNotificationData = notificationData;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index a10bebf..e1b97bda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -29,12 +29,14 @@
 import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
+@Ignore
 public class LocationControllerImplTest extends SysuiTestCase {
 
     private LocationControllerImpl mLocationController;
@@ -79,4 +81,4 @@
 
         TestableLooper.get(this).processAllMessages();
     }
-}
\ No newline at end of file
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 72c3c94..d5a722b 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -306,6 +306,7 @@
      *
      * @return service instance.
      */
+    @GuardedBy("mLock")
     @NonNull
     AutofillManagerServiceImpl getServiceForUserLocked(int userId) {
         final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
@@ -325,6 +326,7 @@
      *
      * @return service instance or {@code null} if not already present
      */
+    @GuardedBy("mLock")
     @Nullable
     AutofillManagerServiceImpl peekServiceForUserLocked(int userId) {
         final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
@@ -474,6 +476,7 @@
     /**
      * Removes a cached service for a given user.
      */
+    @GuardedBy("mLock")
     private void removeCachedServiceLocked(int userId) {
         final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
         if (service != null) {
@@ -485,6 +488,7 @@
     /**
      * Updates a cached service for a given user.
      */
+    @GuardedBy("mLock")
     private void updateCachedServiceLocked(int userId) {
         updateCachedServiceLocked(userId, mDisabledUsers.get(userId));
     }
@@ -492,6 +496,7 @@
     /**
      * Updates a cached service for a given user.
      */
+    @GuardedBy("mLock")
     private void updateCachedServiceLocked(int userId, boolean disabled) {
         AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
         if (service != null) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 989a7b5..2dcc6da 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -211,6 +211,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private int getServiceUidLocked() {
         if (mInfo == null) {
             Slog.w(TAG,  "getServiceUidLocked(): no mInfo");
@@ -248,6 +249,7 @@
                 mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
     }
 
+    @GuardedBy("mLock")
     void updateLocked(boolean disabled) {
         final boolean wasEnabled = isEnabledLocked();
         if (sVerbose) {
@@ -300,6 +302,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     boolean addClientLocked(IAutoFillManagerClient client) {
         if (mClients == null) {
             mClients = new RemoteCallbackList<>();
@@ -308,12 +311,14 @@
         return isEnabledLocked();
     }
 
+    @GuardedBy("mLock")
     void removeClientLocked(IAutoFillManagerClient client) {
         if (mClients != null) {
             mClients.unregister(client);
         }
     }
 
+    @GuardedBy("mLock")
     void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) {
         if (!isEnabledLocked()) {
             return;
@@ -336,6 +341,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     int startSessionLocked(@NonNull IBinder activityToken, int uid,
             @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
             @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
@@ -389,6 +395,7 @@
     /**
      * Remove abandoned sessions if needed.
      */
+    @GuardedBy("mLock")
     private void pruneAbandonedSessionsLocked() {
         long now = System.currentTimeMillis();
         if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) {
@@ -400,6 +407,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     void finishSessionLocked(int sessionId, int uid) {
         if (!isEnabledLocked()) {
             return;
@@ -423,6 +431,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     void cancelSessionLocked(int sessionId, int uid) {
         if (!isEnabledLocked()) {
             return;
@@ -436,6 +445,7 @@
         session.removeSelfLocked();
     }
 
+    @GuardedBy("mLock")
     void disableOwnedAutofillServicesLocked(int uid) {
         Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo);
         if (mInfo == null) return;
@@ -468,6 +478,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
             @NonNull IBinder appCallbackToken, boolean hasCallback,
             @NonNull ComponentName componentName, int flags) {
@@ -546,6 +557,7 @@
     /**
      * Updates a session and returns whether it should be restarted.
      */
+    @GuardedBy("mLock")
     boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds,
             AutofillValue value, int action, int flags) {
         final Session session = mSessions.get(sessionId);
@@ -568,6 +580,7 @@
         return false;
     }
 
+    @GuardedBy("mLock")
     void removeSessionLocked(int sessionId) {
         mSessions.remove(sessionId);
     }
@@ -603,6 +616,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     void destroyLocked() {
         if (sVerbose) Slog.v(TAG, "destroyLocked()");
 
@@ -655,6 +669,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private boolean isValidEventLocked(String method, int sessionId) {
         if (mEventHistory == null) {
             Slog.w(TAG, method + ": not logging event because history is null");
@@ -726,6 +741,7 @@
     /**
      * Updates the last fill response when an autofill context is committed.
      */
+    @GuardedBy("mLock")
     void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState,
             @Nullable ArrayList<String> selectedDatasets,
             @Nullable ArraySet<String> ignoredDatasets,
@@ -739,6 +755,7 @@
                 manuallyFilledDatasetIds, null, null, appPackageName);
     }
 
+    @GuardedBy("mLock")
     void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState,
             @Nullable ArrayList<String> selectedDatasets,
             @Nullable ArraySet<String> ignoredDatasets,
@@ -847,6 +864,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private boolean isCalledByServiceLocked(String methodName, int callingUid) {
         if (getServiceUidLocked() != callingUid) {
             Slog.w(TAG, methodName + "() called by UID " + callingUid
@@ -856,6 +874,7 @@
         return true;
     }
 
+    @GuardedBy("mLock")
     void dumpLocked(String prefix, PrintWriter pw) {
         final String prefix2 = prefix + "  ";
 
@@ -965,6 +984,7 @@
         mFieldClassificationStrategy.dump(prefix2, pw);
     }
 
+    @GuardedBy("mLock")
     void destroySessionsLocked() {
         if (mSessions.size() == 0) {
             mUi.destroyAll(null, null, false);
@@ -976,6 +996,7 @@
     }
 
     // TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
+    @GuardedBy("mLock")
     void destroyFinishedSessionsLocked() {
         final int sessionCount = mSessions.size();
         for (int i = sessionCount - 1; i >= 0; i--) {
@@ -987,6 +1008,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     void listSessionsLocked(ArrayList<String> output) {
         final int numSessions = mSessions.size();
         for (int i = 0; i < numSessions; i++) {
@@ -995,6 +1017,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     boolean isCompatibilityModeRequestedLocked(@NonNull String packageName,
             long versionCode) {
         if (mInfo == null || !mInfo.isCompatibilityModeRequested(packageName, versionCode)) {
@@ -1060,6 +1083,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) {
         final int sessionCount = mSessions.size();
         for (int i = 0; i < sessionCount; i++) {
@@ -1071,6 +1095,7 @@
         return true;
     }
 
+    @GuardedBy("mLock")
     boolean isEnabledLocked() {
         return mSetupComplete && mInfo != null && !mDisabled;
     }
@@ -1123,6 +1148,7 @@
     /**
      * Checks if autofill is disabled by service to the given activity.
      */
+    @GuardedBy("mLock")
     private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
         // Check activities first.
         long elapsedTime = 0;
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index aea9ad0..fe6d4c4 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -475,6 +475,7 @@
             return true;
         }
 
+        @GuardedBy("mLock")
         protected boolean isCancelledLocked() {
             return mCancelled;
         }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 18f49ec..4a24704 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -300,6 +300,7 @@
     /**
      * Returns the ids of all entries in {@link #mViewStates} in the same order.
      */
+    @GuardedBy("mLock")
     private AutofillId[] getIdsOfAllViewStatesLocked() {
         final int numViewState = mViewStates.size();
         final AutofillId[] ids = new AutofillId[numViewState];
@@ -346,6 +347,7 @@
      * <p>Gets the value of a field, using either the {@code viewStates} or the {@code mContexts},
      * or {@code null} when not found on either of them.
      */
+    @GuardedBy("mLock")
     private AutofillValue findValueLocked(@NonNull AutofillId id) {
         final ViewState state = mViewStates.get(id);
         if (state == null) {
@@ -369,6 +371,7 @@
      * @param fillContext The context to be filled
      * @param flags The flags that started the session
      */
+    @GuardedBy("mLock")
     private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) {
         final ViewNode[] nodes = fillContext
                 .findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
@@ -409,6 +412,7 @@
     /**
      * Cancels the last request sent to the {@link #mRemoteFillService}.
      */
+    @GuardedBy("mLock")
     private void cancelCurrentRequestLocked() {
         final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
 
@@ -430,6 +434,7 @@
     /**
      * Reads a new structure and then request a new fill response from the fill service.
      */
+    @GuardedBy("mLock")
     private void requestNewFillResponseLocked(int flags) {
         int requestId;
 
@@ -497,6 +502,7 @@
      *
      * @return The activity token
      */
+    @GuardedBy("mLock")
     @NonNull IBinder getActivityTokenLocked() {
         return mActivityToken;
     }
@@ -663,6 +669,7 @@
      *
      * @return The context or {@code null} if there is no context
      */
+    @GuardedBy("mLock")
     @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) {
         if (mContexts == null) {
             return null;
@@ -820,6 +827,7 @@
         });
     }
 
+    @GuardedBy("mLock")
     void setAuthenticationResultLocked(Bundle data, int authenticationId) {
         if (mDestroyed) {
             Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: "
@@ -882,6 +890,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     void setHasCallbackLocked(boolean hasIt) {
         if (mDestroyed) {
             Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: "
@@ -891,6 +900,7 @@
         mHasCallback = hasIt;
     }
 
+    @GuardedBy("mLock")
     @Nullable
     private FillResponse getLastResponseLocked(@Nullable String logPrefix) {
         if (mContexts == null) {
@@ -923,6 +933,7 @@
         return response;
     }
 
+    @GuardedBy("mLock")
     @Nullable
     private SaveInfo getSaveInfoLocked() {
         final FillResponse response = getLastResponseLocked(null);
@@ -941,6 +952,7 @@
         });
     }
 
+    @GuardedBy("mLock")
     private void logContextCommittedLocked() {
         final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
         if (lastResponse == null) return;
@@ -1241,6 +1253,7 @@
      *
      * @return {@code true} if session is done, or {@code false} if it's pending user action.
      */
+    @GuardedBy("mLock")
     public boolean showSaveLocked() {
         if (mDestroyed) {
             Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: "
@@ -1510,6 +1523,7 @@
     /**
      * Returns whether the session is currently showing the save UI
      */
+    @GuardedBy("mLock")
     boolean isSavingLocked() {
         return mIsSaving;
     }
@@ -1517,6 +1531,7 @@
     /**
      * Gets the latest non-empty value for the given id in the autofill contexts.
      */
+    @GuardedBy("mLock")
     @Nullable
     private AutofillValue getValueFromContextsLocked(AutofillId id) {
         final int numContexts = mContexts.size();
@@ -1539,6 +1554,7 @@
     /**
      * Gets the latest autofill options for the given id in the autofill contexts.
      */
+    @GuardedBy("mLock")
     @Nullable
     private CharSequence[] getAutofillOptionsFromContextsLocked(AutofillId id) {
         final int numContexts = mContexts.size();
@@ -1556,6 +1572,7 @@
     /**
      * Calls service when user requested save.
      */
+    @GuardedBy("mLock")
     void callSaveLocked() {
         if (mDestroyed) {
             Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: "
@@ -1646,6 +1663,7 @@
      * @param viewState The view that is entered.
      * @param flags The flag that was passed by the AutofillManager.
      */
+    @GuardedBy("mLock")
     private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
             @NonNull ViewState viewState, int flags) {
         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
@@ -1673,6 +1691,7 @@
      *
      * @return {@code true} iff a new partition should be started
      */
+    @GuardedBy("mLock")
     private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
         if (mResponses == null) {
             return true;
@@ -1721,6 +1740,7 @@
         return true;
     }
 
+    @GuardedBy("mLock")
     void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action,
             int flags) {
         if (mDestroyed) {
@@ -1830,6 +1850,7 @@
     /**
      * Checks whether a view should be ignored.
      */
+    @GuardedBy("mLock")
     private boolean isIgnoredLocked(AutofillId id) {
         // Always check the latest response only
         final FillResponse response = getLastResponseLocked(null);
@@ -1910,6 +1931,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void updateTrackedIdsLocked() {
         // Only track the views of the last response as only those are reported back to the
         // service, see #showSaveLocked
@@ -1982,6 +2004,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void replaceResponseLocked(@NonNull FillResponse oldResponse,
             @NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
         // Disassociate view states with the old response
@@ -2005,6 +2028,7 @@
         removeSelf();
     }
 
+    @GuardedBy("mLock")
     private void processResponseLocked(@NonNull FillResponse newResponse,
             @Nullable Bundle newClientState, int flags) {
         // Make sure we are hiding the UI which will be shown
@@ -2042,6 +2066,7 @@
     /**
      * Sets the state of all views in the given response.
      */
+    @GuardedBy("mLock")
     private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse) {
         final List<Dataset> datasets = response.getDatasets();
         if (datasets != null) {
@@ -2090,6 +2115,7 @@
     /**
      * Sets the state of all views in the given dataset and response.
      */
+    @GuardedBy("mLock")
     private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset,
             int state, boolean clearResponse) {
         final ArrayList<AutofillId> ids = dataset.getFieldIds();
@@ -2110,6 +2136,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state,
             @Nullable AutofillValue value) {
         ViewState viewState = mViewStates.get(id);
@@ -2171,6 +2198,7 @@
     }
 
     // TODO: this should never be null, but we got at least one occurrence, probably due to a race.
+    @GuardedBy("mLock")
     @Nullable
     private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
         final Intent fillInIntent = new Intent();
@@ -2203,6 +2231,7 @@
         return "Session: [id=" + id + ", component=" + mComponentName + "]";
     }
 
+    @GuardedBy("mLock")
     void dumpLocked(String prefix, PrintWriter pw) {
         final String prefix2 = prefix + "  ";
         pw.print(prefix); pw.print("id: "); pw.println(id);
@@ -2332,6 +2361,7 @@
      *       disabled it).
      * </ul>
      */
+    @GuardedBy("mLock")
     RemoteFillService destroyLocked() {
         if (mDestroyed) {
             return null;
@@ -2347,6 +2377,7 @@
      * Cleans up this session and remove it from the service always, even if it does have a pending
      * Save UI.
      */
+    @GuardedBy("mLock")
     void forceRemoveSelfLocked() {
         if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
 
@@ -2376,6 +2407,7 @@
      * Cleans up this session and remove it from the service, but but only if it does not have a
      * pending Save UI.
      */
+    @GuardedBy("mLock")
     void removeSelfLocked() {
         if (sVerbose) Slog.v(TAG, "removeSelfLocked(): " + mPendingSaveUi);
         if (mDestroyed) {
@@ -2404,6 +2436,7 @@
      * a specific {@code token} created by
      * {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}.
      */
+    @GuardedBy("mLock")
     boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) {
         return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken());
     }
@@ -2411,10 +2444,12 @@
     /**
      * Checks whether this session is hiding the Save UI to handle a custom description link.
      */
+    @GuardedBy("mLock")
     private boolean isSaveUiPendingLocked() {
         return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
     }
 
+    @GuardedBy("mLock")
     private int getLastResponseIndexLocked() {
         // The response ids are monotonically increasing so
         // we just find the largest id which is the last. We
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 17c617c..45b2118 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -88,6 +88,7 @@
 import java.util.function.Predicate;
 
 import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
 import static android.app.AlarmManager.RTC_WAKEUP;
 import static android.app.AlarmManager.RTC;
 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
@@ -1011,7 +1012,7 @@
             // Recurring alarms may have passed several alarm intervals while the
             // alarm was kept pending. Send the appropriate trigger count.
             if (alarm.repeatInterval > 0) {
-                alarm.count += (nowELAPSED - alarm.requestedWhenElapsed) / alarm.repeatInterval;
+                alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
                 // Also schedule its next recurrence
                 final long delta = alarm.count * alarm.repeatInterval;
                 final long nextElapsed = alarm.whenElapsed + delta;
@@ -1507,24 +1508,19 @@
      * Adjusts the alarm delivery time based on the current app standby bucket.
      * @param alarm The alarm to adjust
      * @return true if the alarm delivery time was updated.
-     * TODO: Reduce the number of calls to getAppStandbyBucket by batching the calls per
-     * {package, user} pairs
      */
     private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) {
-        if (alarm.alarmClock != null || UserHandle.isCore(alarm.creatorUid)) {
-            return false;
-        }
-        // TODO: short term fix for b/72816079, remove after a proper fix is in place
-        if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+        if (isExemptFromAppStandby(alarm)) {
             return false;
         }
         if (mAppStandbyParole) {
-            if (alarm.whenElapsed > alarm.requestedWhenElapsed) {
+            if (alarm.whenElapsed > alarm.expectedWhenElapsed) {
                 // We did defer this alarm earlier, restore original requirements
-                alarm.whenElapsed = alarm.requestedWhenElapsed;
-                alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed;
+                alarm.whenElapsed = alarm.expectedWhenElapsed;
+                alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
+                return true;
             }
-            return true;
+            return false;
         }
         final long oldWhenElapsed = alarm.whenElapsed;
         final long oldMaxWhenElapsed = alarm.maxWhenElapsed;
@@ -1538,13 +1534,13 @@
         final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L);
         if (lastElapsed > 0) {
             final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
-            if (alarm.requestedWhenElapsed < minElapsed) {
+            if (alarm.expectedWhenElapsed < minElapsed) {
                 alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
             } else {
                 // app is now eligible to run alarms at the originally requested window.
                 // Restore original requirements in case they were changed earlier.
-                alarm.whenElapsed = alarm.requestedWhenElapsed;
-                alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed;
+                alarm.whenElapsed = alarm.expectedWhenElapsed;
+                alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
             }
         }
         return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed);
@@ -1728,7 +1724,7 @@
             // This means we will allow these alarms to go off as normal even while idle, with no
             // timing restrictions.
             } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
-                    || callingUid == mSystemUiUid
+                    || UserHandle.isSameApp(callingUid, mSystemUiUid)
                     || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
                 flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
                 flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
@@ -3000,10 +2996,11 @@
                         // Whoops, it hasn't been long enough since the last ALLOW_WHILE_IDLE
                         // alarm went off for this app.  Reschedule the alarm to be in the
                         // correct time period.
-                        alarm.whenElapsed = minTime;
+                        alarm.expectedWhenElapsed = alarm.whenElapsed = minTime;
                         if (alarm.maxWhenElapsed < minTime) {
                             alarm.maxWhenElapsed = minTime;
                         }
+                        alarm.expectedMaxWhenElapsed = alarm.maxWhenElapsed;
                         if (RECORD_DEVICE_IDLE_ALARMS) {
                             IdleDispatchEntry ent = new IdleDispatchEntry();
                             ent.uid = alarm.uid;
@@ -3053,7 +3050,7 @@
                 if (alarm.repeatInterval > 0) {
                     // this adjustment will be zero if we're late by
                     // less than one full repeat interval
-                    alarm.count += (nowELAPSED - alarm.requestedWhenElapsed) / alarm.repeatInterval;
+                    alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
 
                     // Also schedule its next recurrence
                     final long delta = alarm.count * alarm.repeatInterval;
@@ -3128,8 +3125,9 @@
         public long windowLength;
         public long whenElapsed;    // 'when' in the elapsed time base
         public long maxWhenElapsed; // also in the elapsed time base
-        public final long requestedWhenElapsed; // original expiry time requested by the app
-        public final long requestedMaxWhenElapsed;
+        // Expected alarm expiry time before app standby deferring is applied.
+        public long expectedWhenElapsed;
+        public long expectedMaxWhenElapsed;
         public long repeatInterval;
         public PriorityClass priorityClass;
 
@@ -3143,10 +3141,10 @@
                     || _type == AlarmManager.RTC_WAKEUP;
             when = _when;
             whenElapsed = _whenElapsed;
-            requestedWhenElapsed = _whenElapsed;
+            expectedWhenElapsed = _whenElapsed;
             windowLength = _windowLength;
             maxWhenElapsed = _maxWhen;
-            requestedMaxWhenElapsed = _maxWhen;
+            expectedMaxWhenElapsed = _maxWhen;
             repeatInterval = _interval;
             operation = _op;
             listener = _rec;
@@ -3205,8 +3203,10 @@
             final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
             pw.print(prefix); pw.print("tag="); pw.println(statsTag);
             pw.print(prefix); pw.print("type="); pw.print(type);
-                    pw.print(" requestedWhenElapsed="); TimeUtils.formatDuration(
-                            requestedWhenElapsed, nowELAPSED, pw);
+                    pw.print(" expectedWhenElapsed="); TimeUtils.formatDuration(
+                    expectedWhenElapsed, nowELAPSED, pw);
+                    pw.print(" expectedMaxWhenElapsed="); TimeUtils.formatDuration(
+                    expectedMaxWhenElapsed, nowELAPSED, pw);
                     pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed,
                             nowELAPSED, pw);
                     pw.print(" when=");
@@ -3344,6 +3344,11 @@
         }
     }
 
+    private boolean isExemptFromAppStandby(Alarm a) {
+        return a.alarmClock != null || UserHandle.isCore(a.creatorUid)
+                || (a.flags & FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED) != 0;
+    }
+
     private class AlarmThread extends Thread
     {
         public AlarmThread()
@@ -3462,7 +3467,7 @@
                                     new ArraySet<>();
                             for (int i = 0; i < triggerList.size(); i++) {
                                 final Alarm a = triggerList.get(i);
-                                if (!UserHandle.isCore(a.creatorUid)) {
+                                if (!isExemptFromAppStandby(a)) {
                                     triggerPackages.add(Pair.create(
                                             a.sourcePackage, UserHandle.getUserId(a.creatorUid)));
                                 }
@@ -4148,7 +4153,7 @@
                     mAllowWhileIdleDispatches.add(ent);
                 }
             }
-            if (!UserHandle.isCore(alarm.creatorUid)) {
+            if (!isExemptFromAppStandby(alarm)) {
                 final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage,
                         UserHandle.getUserId(alarm.creatorUid));
                 mLastAlarmDeliveredForPackage.put(packageUser, nowELAPSED);
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 48b5a58..8d22eca 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -55,6 +55,7 @@
 import android.view.InputDevice;
 import android.media.AudioAttributes;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.DumpUtils;
@@ -111,6 +112,7 @@
     private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
     private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
 
+    @GuardedBy("mLock")
     private Vibration mCurrentVibration;
     private int mCurVibUid = -1;
     private boolean mLowPowerMode;
@@ -435,45 +437,45 @@
 
         // If our current vibration is longer than the new vibration and is the same amplitude,
         // then just let the current one finish.
-        if (effect instanceof VibrationEffect.OneShot
-                && mCurrentVibration != null
-                && mCurrentVibration.effect instanceof VibrationEffect.OneShot) {
-            VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
-            VibrationEffect.OneShot currentOneShot =
-                    (VibrationEffect.OneShot) mCurrentVibration.effect;
-            if (mCurrentVibration.hasTimeoutLongerThan(newOneShot.getDuration())
-                    && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+        synchronized (mLock) {
+            if (effect instanceof VibrationEffect.OneShot
+                    && mCurrentVibration != null
+                    && mCurrentVibration.effect instanceof VibrationEffect.OneShot) {
+                VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
+                VibrationEffect.OneShot currentOneShot =
+                        (VibrationEffect.OneShot) mCurrentVibration.effect;
+                if (mCurrentVibration.hasTimeoutLongerThan(newOneShot.getDuration())
+                        && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration");
+                    }
+                    return;
+                }
+            }
+
+            // If the current vibration is repeating and the incoming one is non-repeating, then
+            // ignore the non-repeating vibration. This is so that we don't cancel vibrations that
+            // are meant to grab the attention of the user, like ringtones and alarms, in favor of
+            // one-shot vibrations that are likely quite short.
+            if (!isRepeatingVibration(effect)
+                    && mCurrentVibration != null
+                    && isRepeatingVibration(mCurrentVibration.effect)) {
                 if (DEBUG) {
-                    Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration");
+                    Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
                 }
                 return;
             }
-        }
 
-        // If the current vibration is repeating and the incoming one is non-repeating, then ignore
-        // the non-repeating vibration. This is so that we don't cancel vibrations that are meant
-        // to grab the attention of the user, like ringtones and alarms, in favor of one-shot
-        // vibrations that are likely quite short.
-        if (!isRepeatingVibration(effect)
-                && mCurrentVibration != null && isRepeatingVibration(mCurrentVibration.effect)) {
-            if (DEBUG) {
-                Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
-            }
-            return;
-        }
-
-        Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
-        linkVibration(vib);
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
+            Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
+            linkVibration(vib);
+            long ident = Binder.clearCallingIdentity();
+            try {
                 doCancelVibrateLocked();
                 startVibrationLocked(vib);
                 addToPreviousVibrationsLocked(vib);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
             }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -516,6 +518,7 @@
         }
     };
 
+    @GuardedBy("mLock")
     private void doCancelVibrateLocked() {
         mH.removeCallbacks(mVibrationEndRunnable);
         if (mThread != null) {
@@ -538,6 +541,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void startVibrationLocked(final Vibration vib) {
         if (!isAllowedToVibrateLocked(vib)) {
             return;
@@ -568,6 +572,7 @@
         startVibrationInnerLocked(vib);
     }
 
+    @GuardedBy("mLock")
     private void startVibrationInnerLocked(Vibration vib) {
         mCurrentVibration = vib;
         if (vib.effect instanceof VibrationEffect.OneShot) {
@@ -701,6 +706,7 @@
         return mode;
     }
 
+    @GuardedBy("mLock")
     private void reportFinishVibrationLocked() {
         if (mCurrentVibration != null) {
             try {
@@ -880,6 +886,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private long doVibratorPrebakedEffectLocked(Vibration vib) {
         final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
         final boolean usingInputDeviceVibrators;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a6c4fc9..cee1ff9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1854,7 +1854,6 @@
     static final int REPORT_MEM_USAGE_MSG = 33;
     static final int IMMERSIVE_MODE_LOCK_MSG = 37;
     static final int PERSIST_URI_GRANTS_MSG = 38;
-    static final int REQUEST_ALL_PSS_MSG = 39;
     static final int UPDATE_TIME_PREFERENCE_MSG = 41;
     static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
     static final int FINISH_BOOTING_MSG = 45;
@@ -2320,12 +2319,6 @@
                 writeGrantedUriPermissions();
                 break;
             }
-            case REQUEST_ALL_PSS_MSG: {
-                synchronized (ActivityManagerService.this) {
-                    requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
-                }
-                break;
-            }
             case UPDATE_TIME_PREFERENCE_MSG: {
                 // The user's time format preference might have changed.
                 // For convenience we re-use the Intent extra values.
@@ -2615,11 +2608,17 @@
                         procState = proc.pssProcState;
                         statType = proc.pssStatType;
                         lastPssTime = proc.lastPssTime;
+                        long now = SystemClock.uptimeMillis();
                         if (proc.thread != null && procState == proc.setProcState
                                 && (lastPssTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE)
-                                        < SystemClock.uptimeMillis()) {
+                                        < now) {
                             pid = proc.pid;
                         } else {
+                            ProcessList.abortNextPssTime(proc.procStateMemTracker);
+                            if (DEBUG_PSS) Slog.d(TAG_PSS, "Skipped pss collection of " + pid +
+                                    ": still need " +
+                                    (lastPssTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE-now) +
+                                    "ms until safe");
                             proc = null;
                             pid = 0;
                         }
@@ -3005,6 +3004,16 @@
 
         Watchdog.getInstance().addMonitor(this);
         Watchdog.getInstance().addThread(mHandler);
+
+        // bind background thread to little cores
+        // this is expected to fail inside of framework tests because apps can't touch cpusets directly
+        try {
+            Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
+                    Process.THREAD_GROUP_BG_NONINTERACTIVE);
+        } catch (Exception e) {
+            Slog.w(TAG, "Setting background thread cpuset failed");
+        }
+
     }
 
     protected ActivityStackSupervisor createStackSupervisor() {
@@ -4075,9 +4084,9 @@
                 runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
             }
 
-            if (app.info.isAllowedToUseHiddenApi()) {
-                // This app is allowed to use undocumented and private APIs. Set
-                // up its runtime with the appropriate flag.
+            if (app.info.isAllowedToUseHiddenApi() || app.instr != null) {
+                // This app is allowed to use undocumented and private APIs or is
+                // being instrumented. Set up its runtime with the appropriate flag.
                 runtimeFlags |= Zygote.DISABLE_HIDDEN_API_CHECKS;
             }
 
@@ -7209,7 +7218,7 @@
             handleAppDiedLocked(app, willRestart, allowRestart);
             if (willRestart) {
                 removeLruProcessLocked(app);
-                addAppLocked(app.info, null, false, null /* ABI override */);
+                addAppLocked(app.info, null, false, null /* ABI override */, app.instr);
             }
         } else {
             mRemovedProcesses.add(app);
@@ -12481,7 +12490,8 @@
                         .getPersistentApplications(STOCK_PM_FLAGS | matchFlags).getList();
                 for (ApplicationInfo app : apps) {
                     if (!"android".equals(app.packageName)) {
-                        addAppLocked(app, null, false, null /* ABI override */);
+                        addAppLocked(app, null, false, null /* ABI override */,
+                                null /* instrumentation */);
                     }
                 }
             } catch (RemoteException ex) {
@@ -12651,6 +12661,9 @@
         if (!mBooted && !mBooting
                 && userId == UserHandle.USER_SYSTEM
                 && (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
+            // The system process is initialized to SCHED_GROUP_DEFAULT in init.rc.
+            r.curSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+            r.setSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
             r.persistent = true;
             r.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
         }
@@ -12694,7 +12707,7 @@
     }
 
     final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
-            String abiOverride) {
+            String abiOverride, ActiveInstrumentation instrumentation) {
         ProcessRecord app;
         if (!isolated) {
             app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
@@ -12723,6 +12736,9 @@
             app.persistent = true;
             app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
         }
+
+        app.instr = instrumentation;
+
         if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
             mPersistentStartingProcesses.add(app);
             startProcessLocked(app, "added application",
@@ -14480,9 +14496,6 @@
                             mTestPssMode, isSleepingLocked(), now);
                 }
             }
-
-            mHandler.removeMessages(REQUEST_ALL_PSS_MSG);
-            mHandler.sendEmptyMessageDelayed(REQUEST_ALL_PSS_MSG, 2*60*1000);
         }
     }
 
@@ -14789,8 +14802,6 @@
             mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
 
             BinderInternal.nSetBinderProxyCountEnabled(true);
-            //STOPSHIP: Temporary BinderProxy Threshold for b/71353150
-            BinderInternal.nSetBinderProxyCountWatermarks(1500, 1200);
             BinderInternal.setBinderProxyCountCallback(
                     new BinderInternal.BinderProxyLimitListener() {
                         @Override
@@ -19529,6 +19540,7 @@
 
         mProcessesToGc.remove(app);
         mPendingPssProcesses.remove(app);
+        ProcessList.abortNextPssTime(app.procStateMemTracker);
 
         // Dismiss any open dialogs.
         if (app.crashDialog != null && !app.forceCrashReport) {
@@ -21531,8 +21543,7 @@
                 mUsageStatsService.reportEvent(ii.targetPackage, userId,
                         UsageEvents.Event.SYSTEM_INTERACTION);
             }
-            ProcessRecord app = addAppLocked(ai, defProcess, false, abiOverride);
-            app.instr = activeInstr;
+            ProcessRecord app = addAppLocked(ai, defProcess, false, abiOverride, activeInstr);
             activeInstr.mFinished = false;
             activeInstr.mRunningProcesses.add(app);
             if (!mActiveInstrumentation.contains(activeInstr)) {
@@ -23293,9 +23304,9 @@
     /**
      * Schedule PSS collection of a process.
      */
-    void requestPssLocked(ProcessRecord proc, int procState) {
+    boolean requestPssLocked(ProcessRecord proc, int procState) {
         if (mPendingPssProcesses.contains(proc)) {
-            return;
+            return false;
         }
         if (mPendingPssProcesses.size() == 0) {
             mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
@@ -23304,6 +23315,7 @@
         proc.pssProcState = procState;
         proc.pssStatType = ProcessStats.ADD_PSS_INTERNAL_SINGLE;
         mPendingPssProcesses.add(proc);
+        return true;
     }
 
     /**
@@ -23320,6 +23332,9 @@
         if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of all procs!  memLowered=" + memLowered);
         mLastFullPssTime = now;
         mFullPssPending = true;
+        for (int i = mPendingPssProcesses.size() - 1; i >= 0; i--) {
+            ProcessList.abortNextPssTime(mPendingPssProcesses.get(i).procStateMemTracker);;
+        }
         mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
         mPendingPssProcesses.clear();
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
@@ -23328,7 +23343,9 @@
                     || app.curProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
                 continue;
             }
-            if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
+            if (memLowered || (always && now >
+                            app.lastStateTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE)
+                    || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
                 app.pssProcState = app.setProcState;
                 app.pssStatType = always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL
                         : ProcessStats.ADD_PSS_INTERNAL_ALL_MEM;
@@ -23337,7 +23354,9 @@
                 mPendingPssProcesses.add(app);
             }
         }
-        mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
+        if (!mBgHandler.hasMessages(COLLECT_PSS_BG_MSG)) {
+            mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
+        }
     }
 
     public void setTestPssMode(boolean enabled) {
@@ -23695,7 +23714,7 @@
                 // Experimental code to more aggressively collect pss while
                 // running test...  the problem is that this tends to collect
                 // the data right when a process is transitioning between process
-                // states, which well tend to give noisy data.
+                // states, which will tend to give noisy data.
                 long start = SystemClock.uptimeMillis();
                 long startTime = SystemClock.currentThreadTimeMillis();
                 long pss = Debug.getPss(app.pid, mTmpLong, null);
@@ -23718,9 +23737,10 @@
             if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL)
                     && now > (app.lastStateTime+ProcessList.minTimeFromStateChange(
                     mTestPssMode)))) {
-                requestPssLocked(app, app.setProcState);
-                app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState,
-                        app.procStateMemTracker, mTestPssMode, isSleepingLocked(), now);
+                if (requestPssLocked(app, app.setProcState)) {
+                    app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState,
+                            app.procStateMemTracker, mTestPssMode, isSleepingLocked(), now);
+                }
             } else if (false && DEBUG_PSS) Slog.d(TAG_PSS,
                     "Not requesting pss of " + app + ": next=" + (app.nextPssTime-now));
         }
@@ -24922,7 +24942,7 @@
                     mRemovedProcesses.remove(i);
 
                     if (app.persistent) {
-                        addAppLocked(app.info, null, false, null /* ABI override */);
+                        addAppLocked(app.info, null, false, null /* ABI override */, app.instr);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index b061ba4..055b89b 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -790,7 +790,7 @@
         // Instead, launch the ephemeral installer. Once the installer is finished, it
         // starts either the intent we resolved here [on install error] or the ephemeral
         // app [on install success].
-        if (rInfo != null && rInfo.isInstantAppAvailable) {
+        if (rInfo != null && rInfo.auxiliaryInfo != null) {
             intent = createLaunchIntent(rInfo.auxiliaryInfo, ephemeralIntent,
                     callingPackage, verificationBundle, resolvedType, userId);
             resolvedType = null;
diff --git a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
index 9033b55..71fd71b 100644
--- a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
@@ -93,7 +93,7 @@
         }
         try {
             systemPropertiesSet(key, value);
-        } catch (IllegalArgumentException e) {
+        } catch (Exception e) {
             Slog.e(TAG, "Unable to set property " + key + " value '" + value + "'", e);
         }
     }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 08ee237..bf7aef9 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -501,19 +501,19 @@
     private static final int PSS_FIRST_CACHED_INTERVAL = 20*1000;
 
     // The amount of time until PSS when an important process stays in the same state.
-    private static final int PSS_SAME_PERSISTENT_INTERVAL = 20*60*1000;
+    private static final int PSS_SAME_PERSISTENT_INTERVAL = 10*60*1000;
 
     // The amount of time until PSS when the top process stays in the same state.
-    private static final int PSS_SAME_TOP_INTERVAL = 5*60*1000;
+    private static final int PSS_SAME_TOP_INTERVAL = 1*60*1000;
 
     // The amount of time until PSS when an important process stays in the same state.
-    private static final int PSS_SAME_IMPORTANT_INTERVAL = 15*60*1000;
+    private static final int PSS_SAME_IMPORTANT_INTERVAL = 10*60*1000;
 
     // The amount of time until PSS when a service process stays in the same state.
-    private static final int PSS_SAME_SERVICE_INTERVAL = 20*60*1000;
+    private static final int PSS_SAME_SERVICE_INTERVAL = 5*60*1000;
 
     // The amount of time until PSS when a cached process stays in the same state.
-    private static final int PSS_SAME_CACHED_INTERVAL = 20*60*1000;
+    private static final int PSS_SAME_CACHED_INTERVAL = 10*60*1000;
 
     // The amount of time until PSS when a persistent process first appears.
     private static final int PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL = 1*60*1000;
@@ -622,16 +622,17 @@
 
     public static final class ProcStateMemTracker {
         final int[] mHighestMem = new int[PROC_MEM_NUM];
+        final float[] mScalingFactor = new float[PROC_MEM_NUM];
         int mTotalHighestMem = PROC_MEM_CACHED;
-        float mCurFactor = 1.0f;
 
         int mPendingMemState;
         int mPendingHighestMemState;
-        boolean mPendingSame;
+        float mPendingScalingFactor;
 
         public ProcStateMemTracker() {
             for (int i = PROC_MEM_PERSISTENT; i < PROC_MEM_NUM; i++) {
                 mHighestMem[i] = PROC_MEM_NUM;
+                mScalingFactor[i] = 1.0f;
             }
             mPendingMemState = -1;
         }
@@ -639,16 +640,22 @@
         public void dumpLine(PrintWriter pw) {
             pw.print("best=");
             pw.print(mTotalHighestMem);
-            pw.print(" ");
-            pw.print(mCurFactor);
-            pw.print("x (");
+            pw.print(" (");
+            boolean needSep = false;
             for (int i = 0; i < PROC_MEM_NUM; i++) {
-                if (i != 0) {
-                    pw.print(", ");
+                if (mHighestMem[i] < PROC_MEM_NUM) {
+                    if (needSep) {
+                        pw.print(", ");
+                        needSep = false;
+                    }
+                    pw.print(i);
+                    pw.print("=");
+                    pw.print(mHighestMem[i]);
+                    pw.print(" ");
+                    pw.print(mScalingFactor[i]);
+                    pw.print("x");
+                    needSep = true;
                 }
-                pw.print(i);
-                pw.print("=");
-                pw.print(mHighestMem[i]);
             }
             pw.print(")");
             if (mPendingMemState >= 0) {
@@ -656,8 +663,9 @@
                 pw.print(mPendingMemState);
                 pw.print(" highest=");
                 pw.print(mPendingHighestMemState);
-                pw.print(" same=");
-                pw.print(mPendingSame);
+                pw.print(" ");
+                pw.print(mPendingScalingFactor);
+                pw.print("x");
             }
             pw.println();
         }
@@ -674,12 +682,8 @@
     public static void commitNextPssTime(ProcStateMemTracker tracker) {
         if (tracker.mPendingMemState >= 0) {
             tracker.mHighestMem[tracker.mPendingMemState] = tracker.mPendingHighestMemState;
+            tracker.mScalingFactor[tracker.mPendingMemState] = tracker.mPendingScalingFactor;
             tracker.mTotalHighestMem = tracker.mPendingHighestMemState;
-            if (tracker.mPendingSame) {
-                tracker.mCurFactor *= 1.5f;
-            } else {
-                tracker.mCurFactor = 1;
-            }
             tracker.mPendingMemState = -1;
         }
     }
@@ -691,6 +695,7 @@
     public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test,
             boolean sleeping, long now) {
         boolean first;
+        float scalingFactor;
         final int memState = sProcStateToProcMem[procState];
         if (tracker != null) {
             final int highestMemState = memState < tracker.mTotalHighestMem
@@ -698,9 +703,15 @@
             first = highestMemState < tracker.mHighestMem[memState];
             tracker.mPendingMemState = memState;
             tracker.mPendingHighestMemState = highestMemState;
-            tracker.mPendingSame = !first;
+            if (first) {
+                tracker.mPendingScalingFactor = scalingFactor = 1.0f;
+            } else {
+                scalingFactor = tracker.mScalingFactor[memState];
+                tracker.mPendingScalingFactor = scalingFactor * 1.5f;
+            }
         } else {
             first = true;
+            scalingFactor = 1.0f;
         }
         final long[] table = test
                 ? (first
@@ -709,8 +720,7 @@
                 : (first
                 ? (sleeping ? sFirstAsleepPssTimes : sFirstAwakePssTimes)
                 : (sleeping ? sSameAsleepPssTimes : sSameAwakePssTimes));
-        long delay = (long)(table[memState] * (tracker != null && !first
-                ? tracker.mCurFactor : 1.0f));
+        long delay = (long)(table[memState] * scalingFactor);
         if (delay > PSS_MAX_INTERVAL) {
             delay = PSS_MAX_INTERVAL;
         }
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 8bf320e..edf565a 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -237,6 +237,7 @@
             if (commit) {
                 mProcessStats.resetSafely();
                 updateFile();
+                mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
             }
             mLastWriteTime = SystemClock.uptimeMillis();
             totalTime = SystemClock.uptimeMillis() - now;
@@ -784,12 +785,14 @@
                 } else if ("--reset".equals(arg)) {
                     synchronized (mAm) {
                         mProcessStats.resetSafely();
+                        mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
                         pw.println("Process stats reset.");
                         quit = true;
                     }
                 } else if ("--clear".equals(arg)) {
                     synchronized (mAm) {
                         mProcessStats.resetSafely();
+                        mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
                         ArrayList<String> files = getCommittedFiles(0, true, true);
                         if (files != null) {
                             for (int fi=0; fi<files.size(); fi++) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index a6f049e..a17dd12 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1722,7 +1722,7 @@
                 final long totalBytes = getTotalBytes(
                         NetworkTemplate.buildTemplateMobileAll(state.subscriberId), start, end);
                 final long remainingBytes = limitBytes - totalBytes;
-                final long remainingDays = Math.min(1, (end - currentTimeMillis())
+                final long remainingDays = Math.max(1, (end - currentTimeMillis())
                         / TimeUnit.DAYS.toMillis(1));
                 if (remainingBytes > 0) {
                     quotaBytes = (remainingBytes / remainingDays) / 10;
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
index b0b07ea..671d7a6 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
@@ -363,7 +363,7 @@
             indent = indent + "  ";
 
             pw.print(indent);
-            pw.println("Battery Saver:       Off                                        On");
+            pw.println("Battery Saver:     w/Off                                      w/On");
             dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
                     DozeState.NOT_DOZING, "NonDoze");
             dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, "   Intr",
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index b58b890..e869f58 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -237,8 +237,11 @@
             final Rect minimizedHomeBounds =
                     mHomeAppToken != null && mHomeAppToken.inSplitScreenSecondaryWindowingMode()
                             ? mMinimizedHomeBounds : null;
-            mRunner.onAnimationStart_New(mController, appAnimations,
-                    mHomeAppToken.findMainWindow().mContentInsets, minimizedHomeBounds);
+            final Rect contentInsets =
+                    mHomeAppToken != null && mHomeAppToken.findMainWindow() != null
+                            ? mHomeAppToken.findMainWindow().mContentInsets : null;
+            mRunner.onAnimationStart_New(mController, appAnimations, contentInsets,
+                    minimizedHomeBounds);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to start recents animation", e);
         }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 7540e26..5e003ff 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -24,6 +24,7 @@
         "com_android_server_connectivity_Vpn.cpp",
         "com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp",
         "com_android_server_ConsumerIrService.cpp",
+        "com_android_server_devicepolicy_CryptoTestHelper.cpp",
         "com_android_server_HardwarePropertiesManagerService.cpp",
         "com_android_server_hdmi_HdmiCecController.cpp",
         "com_android_server_input_InputApplicationHandle.cpp",
diff --git a/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp b/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
new file mode 100644
index 0000000..b53ea92
--- /dev/null
+++ b/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni.h"
+#include "core_jni_helpers.h"
+
+#include <openssl/crypto.h>
+
+namespace {
+
+static jint runSelfTest(JNIEnv* env, jobject /* clazz */) {
+    return BORINGSSL_self_test();
+}
+
+static const JNINativeMethod methods[] = {
+    /* name, signature, funcPtr */
+    {"runSelfTest", "()I", (void*) runSelfTest}
+};
+
+} // anonymous namespace
+
+namespace android {
+
+int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv *env) {
+    return jniRegisterNativeMethods(
+            env, "com/android/server/devicepolicy/CryptoTestHelper", methods, NELEM(methods));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 07ddb05..bf2a637 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -42,6 +42,7 @@
 int register_android_server_location_GnssLocationProvider(JNIEnv* env);
 int register_android_server_connectivity_Vpn(JNIEnv* env);
 int register_android_server_connectivity_tethering_OffloadHardwareInterface(JNIEnv*);
+int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
 int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
 int register_android_server_tv_TvUinputBridge(JNIEnv* env);
 int register_android_server_tv_TvInputHal(JNIEnv* env);
@@ -88,6 +89,7 @@
     register_android_server_location_GnssLocationProvider(env);
     register_android_server_connectivity_Vpn(env);
     register_android_server_connectivity_tethering_OffloadHardwareInterface(env);
+    register_android_server_devicepolicy_CryptoTestHelper(env);
     register_android_server_ConsumerIrService(env);
     register_android_server_BatteryStatsService(env);
     register_android_server_hdmi_HdmiCecController(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CryptoTestHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/CryptoTestHelper.java
new file mode 100644
index 0000000..a20758e
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/CryptoTestHelper.java
@@ -0,0 +1,30 @@
+/*
+ * 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.devicepolicy;
+
+import android.app.admin.SecurityLog;
+
+/**
+ * Helper to call native BoringSSL self test.
+ */
+public class CryptoTestHelper {
+    public static void runAndLogSelfTest() {
+        final int result = runSelfTest();
+        SecurityLog.writeEvent(SecurityLog.TAG_CRYPTO_SELF_TEST_COMPLETED, result);
+    }
+    private static native int runSelfTest();
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 953a79f..95e71ed 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2044,6 +2044,10 @@
         public TransferOwnershipMetadataManager newTransferOwnershipMetadataManager() {
             return new TransferOwnershipMetadataManager();
         }
+
+        public void runCryptoSelfTest() {
+            CryptoTestHelper.runAndLogSelfTest();
+        }
     }
 
     /**
@@ -2296,6 +2300,7 @@
 
             if (hasDeviceOwner && mInjector.securityLogGetLoggingEnabledProperty()) {
                 mSecurityLogMonitor.start();
+                mInjector.runCryptoSelfTest();
                 maybePauseDeviceWideLoggingLocked();
             }
         }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c361434..5ea113b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -724,7 +724,6 @@
         MmsServiceBroker mmsService = null;
         HardwarePropertiesManagerService hardwarePropertiesService = null;
 
-        boolean disableRtt = SystemProperties.getBoolean("config.disable_rtt", false);
         boolean disableSystemTextClassifier = SystemProperties.getBoolean(
                 "config.disable_systemtextclassifier", false);
         boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
@@ -1105,18 +1104,12 @@
                 traceEnd();
             }
 
-            if (!disableRtt) {
-                traceBeginAndSlog("StartWifiRtt");
-                mSystemServiceManager.startService("com.android.server.wifi.RttService");
+            if (context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_RTT)) {
+                traceBeginAndSlog("StartRttService");
+                mSystemServiceManager.startService(
+                    "com.android.server.wifi.rtt.RttService");
                 traceEnd();
-
-                if (context.getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_WIFI_RTT)) {
-                    traceBeginAndSlog("StartRttService");
-                    mSystemServiceManager.startService(
-                        "com.android.server.wifi.rtt.RttService");
-                    traceEnd();
-                }
             }
 
             if (context.getPackageManager().hasSystemFeature(
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 00e27c9..ab0bfefb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -447,5 +447,8 @@
             return new TransferOwnershipMetadataManager(
                     new TransferOwnershipMetadataManagerTest.MockInjector());
         }
+
+        @Override
+        public void runCryptoSelfTest() {}
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 0abb48f..98483a8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -294,7 +294,7 @@
                 null /*disabledPkg*/,
                 null /*sharedUser*/,
                 UPDATED_CODE_PATH /*codePath*/,
-                null /*resourcePath*/,
+                UPDATED_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPath*/,
                 "arm64-v8a" /*primaryCpuAbi*/,
                 "armeabi" /*secondaryCpuAbi*/,
@@ -328,7 +328,7 @@
                 null /*disabledPkg*/,
                 null /*sharedUser*/,
                 UPDATED_CODE_PATH /*codePath*/,
-                null /*resourcePath*/,
+                UPDATED_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPath*/,
                 "arm64-v8a" /*primaryCpuAbi*/,
                 "armeabi" /*secondaryCpuAbi*/,
@@ -561,34 +561,6 @@
                 false /*notLaunched*/, false /*stopped*/, true /*installed*/);
     }
 
-    @Test
-    public void testInsertPackageSetting() {
-        final PackageSetting ps = createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
-        final PackageParser.Package pkg = new PackageParser.Package(PACKAGE_NAME);
-        pkg.applicationInfo.setCodePath(ps.codePathString);
-        pkg.applicationInfo.setResourcePath(ps.resourcePathString);
-        final Context context = InstrumentationRegistry.getContext();
-        final Object lock = new Object();
-        PermissionManagerInternal pmInt = PermissionManagerService.create(context, null, lock);
-        final Settings settings =
-                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
-        pkg.usesStaticLibraries = new ArrayList<>(
-                Arrays.asList("foo.bar1", "foo.bar2", "foo.bar3"));
-        pkg.usesStaticLibrariesVersions = new long[] {2, 4, 6};
-        settings.insertPackageSettingLPw(ps, pkg);
-        assertEquals(pkg, ps.pkg);
-        assertArrayEquals(pkg.usesStaticLibraries.toArray(new String[0]), ps.usesStaticLibraries);
-        assertArrayEquals(pkg.usesStaticLibrariesVersions, ps.usesStaticLibrariesVersions);
-
-        pkg.usesStaticLibraries = null;
-        pkg.usesStaticLibrariesVersions = null;
-        settings.insertPackageSettingLPw(ps, pkg);
-        assertEquals(pkg, ps.pkg);
-        assertNull("Actual: " + Arrays.toString(ps.usesStaticLibraries), ps.usesStaticLibraries);
-        assertNull("Actual: " + Arrays.toString(ps.usesStaticLibrariesVersions),
-                ps.usesStaticLibrariesVersions);
-    }
-
     private <T> void assertArrayEquals(T[] a, T[] b) {
         assertTrue("Expected: " + Arrays.toString(a) + ", actual: " + Arrays.toString(b),
                 Arrays.equals(a, b));
diff --git a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
index f23bd62..3a1485e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.graphics.Color.BLUE;
 import static android.graphics.Color.RED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
@@ -27,7 +26,6 @@
 import static android.view.Gravity.RIGHT;
 import static android.view.Gravity.TOP;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -49,6 +47,7 @@
 import android.os.Handler;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Pair;
@@ -66,18 +65,19 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.function.BooleanSupplier;
 
 /**
  * Tests for the {@link android.view.WindowManager.LayoutParams#PRIVATE_FLAG_IS_SCREEN_DECOR} flag.
  *
  * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.wm.ScreenDecorWindowTests
+ *  atest FrameworksServicesTests:com.android.server.wm.ScreenDecorWindowTests
  */
 // TODO: Add test for FLAG_FULLSCREEN which hides the status bar and also other flags.
 // TODO: Test non-Activity windows.
 @SmallTest
-// TODO(b/68957554)
-//@Presubmit
+@Presubmit
+@FlakyTest(bugId = 68957554)
 @RunWith(AndroidJUnit4.class)
 public class ScreenDecorWindowTests {
 
@@ -123,40 +123,33 @@
     public void testScreenSides() throws Exception {
         // Decor on top
         final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
-        WindowInsets insets = getInsets(mTestActivity);
-        assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
 
         // Decor at the bottom
         updateWindow(decorWindow, BOTTOM, MATCH_PARENT, mDecorThickness, 0, 0);
-        insets = getInsets(mTestActivity);
-        assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), mDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mDecorThickness);
 
         // Decor to the left
         updateWindow(decorWindow, LEFT, mDecorThickness, MATCH_PARENT, 0, 0);
-        insets = getInsets(mTestActivity);
-        assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), mDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, LEFT, mDecorThickness);
 
         // Decor to the right
         updateWindow(decorWindow, RIGHT, mDecorThickness, MATCH_PARENT, 0, 0);
-        insets = getInsets(mTestActivity);
-        assertGreaterOrEqual(insets.getSystemWindowInsetRight(), mDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, RIGHT, mDecorThickness);
     }
 
     @Test
     public void testMultipleDecors() throws Exception {
         // Test 2 decor windows on-top.
         createDecorWindow(TOP, MATCH_PARENT, mHalfDecorThickness);
-        WindowInsets insets = getInsets(mTestActivity);
-        assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mHalfDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, TOP, mHalfDecorThickness);
         createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
-        insets = getInsets(mTestActivity);
-        assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
 
         // And one at the bottom.
         createDecorWindow(BOTTOM, MATCH_PARENT, mHalfDecorThickness);
-        insets = getInsets(mTestActivity);
-        assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness);
-        assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), mHalfDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mHalfDecorThickness);
     }
 
     @Test
@@ -164,18 +157,15 @@
         WindowInsets initialInsets = getInsets(mTestActivity);
 
         final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
-        WindowInsets insets = getInsets(mTestActivity);
-        assertEquals(mDecorThickness, insets.getSystemWindowInsetTop());
+        assertTopInsetEquals(mTestActivity, mDecorThickness);
 
         updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness,
                 0, PRIVATE_FLAG_IS_SCREEN_DECOR);
-        insets = getInsets(mTestActivity);
-        assertEquals(initialInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop());
+        assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop());
 
         updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness,
                 PRIVATE_FLAG_IS_SCREEN_DECOR, PRIVATE_FLAG_IS_SCREEN_DECOR);
-        insets = getInsets(mTestActivity);
-        assertEquals(mDecorThickness, insets.getSystemWindowInsetTop());
+        assertTopInsetEquals(mTestActivity, mDecorThickness);
     }
 
     @Test
@@ -183,17 +173,10 @@
         WindowInsets initialInsets = getInsets(mTestActivity);
 
         final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
-        WindowInsets insets = getInsets(mTestActivity);
-        assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
 
         removeWindow(decorWindow);
-        insets = getInsets(mTestActivity);
-        assertEquals(initialInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop());
-    }
-
-    private View createAppWindow() {
-        return createWindow("appWindow", TOP, MATCH_PARENT, MATCH_PARENT, BLUE,
-                FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR, 0);
+        assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop());
     }
 
     private View createDecorWindow(int gravity, int width, int height) {
@@ -249,10 +232,6 @@
         waitForIdle();
     }
 
-    private WindowInsets getInsets(View v) {
-        return new WindowInsets(v.getRootWindowInsets());
-    }
-
     private WindowInsets getInsets(Activity a) {
         return new WindowInsets(a.getWindow().getDecorView().getRootWindowInsets());
     }
@@ -269,11 +248,59 @@
         lp.flags = (lp.flags & ~mask) | (flags & mask);
     }
 
+    /**
+     * Asserts the top inset of {@param activity} is equal to {@param expected} waiting as needed.
+     */
+    private void assertTopInsetEquals(Activity activity, int expected) throws Exception {
+        waitFor(() -> getInsets(activity).getSystemWindowInsetTop() == expected);
+        assertEquals(expected, getInsets(activity).getSystemWindowInsetTop());
+    }
+
+    /**
+     * Asserts the inset at {@param side} of {@param activity} is equal to {@param expected}
+     * waiting as needed.
+     */
+    private void assertInsetGreaterOrEqual(Activity activity, int side, int expected)
+            throws Exception {
+        waitFor(() -> {
+            final WindowInsets insets = getInsets(activity);
+            switch (side) {
+                case TOP: return insets.getSystemWindowInsetTop() >= expected;
+                case BOTTOM: return insets.getSystemWindowInsetBottom() >= expected;
+                case LEFT: return insets.getSystemWindowInsetLeft() >= expected;
+                case RIGHT: return insets.getSystemWindowInsetRight() >= expected;
+                default: return true;
+            }
+        });
+
+        final WindowInsets insets = getInsets(activity);
+        switch (side) {
+            case TOP: assertGreaterOrEqual(insets.getSystemWindowInsetTop(), expected); break;
+            case BOTTOM: assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), expected); break;
+            case LEFT: assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), expected); break;
+            case RIGHT: assertGreaterOrEqual(insets.getSystemWindowInsetRight(), expected); break;
+        }
+    }
+
     /** Asserts that the first entry is greater than or equal to the second entry. */
     private void assertGreaterOrEqual(int first, int second) throws Exception {
         Assert.assertTrue("Excepted " + first + " >= " + second, first >= second);
     }
 
+    private void waitFor(BooleanSupplier waitCondition) {
+        int retriesLeft = 5;
+        do {
+            if (waitCondition.getAsBoolean()) {
+                break;
+            }
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                // Well I guess we are not waiting...
+            }
+        } while (retriesLeft-- > 0);
+    }
+
     private void finishActivity(Activity a) {
         if (a == null) {
             return;
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 6799417..8c18518 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -352,8 +352,11 @@
          */
         public static final int CAPABILITY_CAN_PULL_CALL = 0x00800000;
 
+        /** Call supports the deflect feature. */
+        public static final int CAPABILITY_SUPPORT_DEFLECT = 0x01000000;
+
         //******************************************************************************************
-        // Next CAPABILITY value: 0x01000000
+        // Next CAPABILITY value: 0x02000000
         //******************************************************************************************
 
         /**
@@ -528,6 +531,9 @@
             if (can(capabilities, CAPABILITY_CAN_PULL_CALL)) {
                 builder.append(" CAPABILITY_CAN_PULL_CALL");
             }
+            if (can(capabilities, CAPABILITY_SUPPORT_DEFLECT)) {
+                builder.append(" CAPABILITY_SUPPORT_DEFLECT");
+            }
             builder.append("]");
             return builder.toString();
         }
@@ -1235,6 +1241,15 @@
     }
 
     /**
+     * Instructs this {@link #STATE_RINGING} {@code Call} to deflect.
+     *
+     * @param address The address to which the call will be deflected.
+     */
+    public void deflect(Uri address) {
+        mInCallAdapter.deflectCall(mTelecomCallId, address);
+    }
+
+    /**
      * Instructs this {@link #STATE_RINGING} {@code Call} to reject.
      *
      * @param rejectWithMessage Whether to reject with a text message.
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index e0b0bbf..24184e0 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -328,8 +328,11 @@
      */
     public static final int CAPABILITY_CAN_PULL_CALL = 0x01000000;
 
+    /** Call supports the deflect feature. */
+    public static final int CAPABILITY_SUPPORT_DEFLECT = 0x02000000;
+
     //**********************************************************************************************
-    // Next CAPABILITY value: 0x02000000
+    // Next CAPABILITY value: 0x04000000
     //**********************************************************************************************
 
     /**
@@ -726,6 +729,9 @@
         if (can(capabilities, CAPABILITY_CAN_PULL_CALL)) {
             builder.append(isLong ? " CAPABILITY_CAN_PULL_CALL" : " pull");
         }
+        if (can(capabilities, CAPABILITY_SUPPORT_DEFLECT)) {
+            builder.append(isLong ? " CAPABILITY_SUPPORT_DEFLECT" : " sup_def");
+        }
 
         builder.append("]");
         return builder.toString();
@@ -2752,6 +2758,12 @@
 
     /**
      * Notifies this Connection, which is in {@link #STATE_RINGING}, of
+     * a request to deflect.
+     */
+    public void onDeflect(Uri address) {}
+
+    /**
+     * Notifies this Connection, which is in {@link #STATE_RINGING}, of
      * a request to reject.
      */
     public void onReject() {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index c1040ad..211699e 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -124,6 +124,7 @@
     private static final String SESSION_ABORT = "CS.ab";
     private static final String SESSION_ANSWER = "CS.an";
     private static final String SESSION_ANSWER_VIDEO = "CS.anV";
+    private static final String SESSION_DEFLECT = "CS.def";
     private static final String SESSION_REJECT = "CS.r";
     private static final String SESSION_REJECT_MESSAGE = "CS.rWM";
     private static final String SESSION_SILENCE = "CS.s";
@@ -181,6 +182,7 @@
     private static final int MSG_CONNECTION_SERVICE_FOCUS_GAINED = 31;
     private static final int MSG_HANDOVER_FAILED = 32;
     private static final int MSG_HANDOVER_COMPLETE = 33;
+    private static final int MSG_DEFLECT = 34;
 
     private static Connection sNullConnection;
 
@@ -353,6 +355,20 @@
         }
 
         @Override
+        public void deflect(String callId, Uri address, Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_DEFLECT);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = address;
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_DEFLECT, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void reject(String callId, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_REJECT);
             try {
@@ -847,6 +863,17 @@
                     }
                     break;
                 }
+                case MSG_DEFLECT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_DEFLECT);
+                    try {
+                        deflect((String) args.arg1, (Uri) args.arg2);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
                 case MSG_REJECT: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_REJECT);
@@ -1605,6 +1632,11 @@
         findConnectionForAction(callId, "answer").onAnswer();
     }
 
+    private void deflect(String callId, Uri address) {
+        Log.d(this, "deflect %s", callId);
+        findConnectionForAction(callId, "deflect").onDeflect(address);
+    }
+
     private void reject(String callId) {
         Log.d(this, "reject %s", callId);
         findConnectionForAction(callId, "reject").onReject();
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 658685f..8678e33 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.net.Uri;
 import android.bluetooth.BluetoothDevice;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -61,6 +62,19 @@
     }
 
     /**
+     * Instructs Telecom to deflect the specified call.
+     *
+     * @param callId The identifier of the call to deflect.
+     * @param address The address to deflect.
+     */
+    public void deflectCall(String callId, Uri address) {
+        try {
+            mAdapter.deflectCall(callId, address);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Instructs Telecom to reject the specified call.
      *
      * @param callId The identifier of the call to reject.
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 3a84f00..e35093c 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -16,6 +16,7 @@
 
 package com.android.internal.telecom;
 
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.telecom.CallAudioState;
@@ -58,6 +59,8 @@
 
     void answer(String callId, in Session.Info sessionInfo);
 
+    void deflect(String callId, in Uri address, in Session.Info sessionInfo);
+
     void reject(String callId, in Session.Info sessionInfo);
 
     void rejectWithMessage(String callId, String message, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index 87ccd3e..57df5c1 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -16,6 +16,7 @@
 
 package com.android.internal.telecom;
 
+import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.PhoneAccountHandle;
 
@@ -29,6 +30,8 @@
 oneway interface IInCallAdapter {
     void answerCall(String callId, int videoState);
 
+    void deflectCall(String callId, in Uri address);
+
     void rejectCall(String callId, boolean rejectWithMessage, String textMessage);
 
     void disconnectCall(String callId);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7eb691b..c8c898a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1376,6 +1376,12 @@
      */
     public static final String KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL = "allow_hold_in_ims_call";
 
+    /**
+     * Flag indicating whether the carrier supports call deflection for an incoming IMS call.
+     * @hide
+     */
+    public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL =
+            "carrier_allow_deflect_ims_call_bool";
 
     /**
      * Flag indicating whether the carrier always wants to play an "on-hold" tone when a call has
@@ -1844,6 +1850,7 @@
     static {
         sDefaults = new PersistableBundle();
         sDefaults.putBoolean(KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL, true);
+        sDefaults.putBoolean(KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_PLAY_REMOTE_HOLD_TONE_BOOL, false);
         sDefaults.putBoolean(KEY_ADDITIONAL_CALL_SETTING_BOOL, true);
         sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL, false);
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 0b3cbad..0c17147 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -55,10 +55,11 @@
     /** Card state restricted. */
     public static final int CARD_STATE_INFO_RESTRICTED = 4;
 
-    public final boolean isActive;
-    public final boolean isEuicc;
-    public final String cardId;
-    public final @CardStateInfo int cardStateInfo;
+    private final boolean mIsActive;
+    private final boolean mIsEuicc;
+    private final String mCardId;
+    private final @CardStateInfo int mCardStateInfo;
+    private final int mLogicalSlotIdx;
 
     public static final Creator<UiccSlotInfo> CREATOR = new Creator<UiccSlotInfo>() {
         @Override
@@ -73,18 +74,20 @@
     };
 
     private UiccSlotInfo(Parcel in) {
-        isActive = in.readByte() != 0;
-        isEuicc = in.readByte() != 0;
-        cardId = in.readString();
-        cardStateInfo = in.readInt();
+        mIsActive = in.readByte() != 0;
+        mIsEuicc = in.readByte() != 0;
+        mCardId = in.readString();
+        mCardStateInfo = in.readInt();
+        mLogicalSlotIdx = in.readInt();
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeByte((byte) (isActive ? 1 : 0));
-        dest.writeByte((byte) (isEuicc ? 1 : 0));
-        dest.writeString(cardId);
-        dest.writeInt(cardStateInfo);
+        dest.writeByte((byte) (mIsActive ? 1 : 0));
+        dest.writeByte((byte) (mIsEuicc ? 1 : 0));
+        dest.writeString(mCardId);
+        dest.writeInt(mCardStateInfo);
+        dest.writeInt(mLogicalSlotIdx);
     }
 
     @Override
@@ -93,28 +96,33 @@
     }
 
     public UiccSlotInfo(boolean isActive, boolean isEuicc, String cardId,
-            @CardStateInfo int cardStateInfo) {
-        this.isActive = isActive;
-        this.isEuicc = isEuicc;
-        this.cardId = cardId;
-        this.cardStateInfo = cardStateInfo;
+            @CardStateInfo int cardStateInfo, int logicalSlotIdx) {
+        this.mIsActive = isActive;
+        this.mIsEuicc = isEuicc;
+        this.mCardId = cardId;
+        this.mCardStateInfo = cardStateInfo;
+        this.mLogicalSlotIdx = logicalSlotIdx;
     }
 
     public boolean getIsActive() {
-        return isActive;
+        return mIsActive;
     }
 
     public boolean getIsEuicc() {
-        return isEuicc;
+        return mIsEuicc;
     }
 
     public String getCardId() {
-        return cardId;
+        return mCardId;
     }
 
     @CardStateInfo
     public int getCardStateInfo() {
-        return cardStateInfo;
+        return mCardStateInfo;
+    }
+
+    public int getLogicalSlotIdx() {
+        return mLogicalSlotIdx;
     }
 
     @Override
@@ -127,32 +135,36 @@
         }
 
         UiccSlotInfo that = (UiccSlotInfo) obj;
-        return (isActive == that.isActive)
-                && (isEuicc == that.isEuicc)
-                && (cardId == that.cardId)
-                && (cardStateInfo == that.cardStateInfo);
+        return (mIsActive == that.mIsActive)
+                && (mIsEuicc == that.mIsEuicc)
+                && (mCardId == that.mCardId)
+                && (mCardStateInfo == that.mCardStateInfo)
+                && (mLogicalSlotIdx == that.mLogicalSlotIdx);
     }
 
     @Override
     public int hashCode() {
         int result = 1;
-        result = 31 * result + (isActive ? 1 : 0);
-        result = 31 * result + (isEuicc ? 1 : 0);
-        result = 31 * result + Objects.hashCode(cardId);
-        result = 31 * result + cardStateInfo;
+        result = 31 * result + (mIsActive ? 1 : 0);
+        result = 31 * result + (mIsEuicc ? 1 : 0);
+        result = 31 * result + Objects.hashCode(mCardId);
+        result = 31 * result + mCardStateInfo;
+        result = 31 * result + mLogicalSlotIdx;
         return result;
     }
 
     @Override
     public String toString() {
-        return "UiccSlotInfo (isActive="
-                + isActive
-                + ", isEuicc="
-                + isEuicc
-                + ", cardId="
-                + cardId
+        return "UiccSlotInfo (mIsActive="
+                + mIsActive
+                + ", mIsEuicc="
+                + mIsEuicc
+                + ", mCardId="
+                + mCardId
                 + ", cardState="
-                + cardStateInfo
+                + mCardStateInfo
+                + ", phoneId="
+                + mLogicalSlotIdx
                 + ")";
     }
 }
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index c3d103f..a20d4f5 100644
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -754,6 +754,22 @@
     }
 
     /**
+     * Deflects an incoming call.
+     *
+     * @param number number to be deflected to
+     */
+    public void deflect(String number) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.deflect(number);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Rejects an incoming call or session update.
      *
      * @param reason reason code to reject an incoming call
diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
index 00cb1e2..e5ed825 100644
--- a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
@@ -182,6 +182,15 @@
     }
 
     /**
+     * Deflects an incoming call.
+     *
+     * @param deflectNumber number to deflect the call
+     */
+    @Override
+    public void deflect(String deflectNumber) {
+    }
+
+    /**
      * Rejects an incoming call or session update.
      *
      * @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}.
diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
index c6ca6fd..7b9fe2b 100644
--- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
@@ -174,6 +174,11 @@
         }
 
         @Override
+        public void deflect(String deflectNumber) {
+            ImsCallSessionImplBase.this.deflect(deflectNumber);
+        }
+
+        @Override
         public void reject(int reason) {
             ImsCallSessionImplBase.this.reject(reason);
         }
@@ -395,6 +400,14 @@
     }
 
     /**
+     * Deflects an incoming call.
+     *
+     * @param deflectNumber number to deflect the call
+     */
+    public void deflect(String deflectNumber) {
+    }
+
+    /**
      * Rejects an incoming call or session update.
      *
      * @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}.
diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
index 203e6cf..15234e5 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
@@ -136,6 +136,13 @@
     void accept(int callType, in ImsStreamMediaProfile profile);
 
     /**
+     * Deflects an incoming call.
+     *
+     * @param deflectNumber number to deflect the call
+     */
+    void deflect(String deflectNumber);
+
+    /**
      * Rejects an incoming call or session update.
      *
      * @param reason reason code to reject an incoming call
diff --git a/wifi/java/android/net/wifi/IRttManager.aidl b/wifi/java/android/net/wifi/IRttManager.aidl
deleted file mode 100644
index 3831809..0000000
--- a/wifi/java/android/net/wifi/IRttManager.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi;
-import android.os.Messenger;
-import android.net.wifi.RttManager;
-
-/**
- * {@hide}
- */
-interface IRttManager
-{
-    Messenger getMessenger(in IBinder binder, out int[] key);
-    RttManager.RttCapabilities getRttCapabilities();
-}
diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java
index fe63aa1..3fb4fae 100644
--- a/wifi/java/android/net/wifi/RttManager.java
+++ b/wifi/java/android/net/wifi/RttManager.java
@@ -12,7 +12,6 @@
 import android.net.wifi.rtt.RangingResult;
 import android.net.wifi.rtt.RangingResultCallback;
 import android.net.wifi.rtt.WifiRttManager;
-import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
@@ -838,8 +837,8 @@
                     }
                     dest.writeByte(result.LCR.id);
                     if (result.LCR.id != (byte) 0xFF) {
-                        dest.writeInt((byte) result.LCR.data.length);
-                        dest.writeByte(result.LCR.id);
+                        dest.writeByte((byte) result.LCR.data.length);
+                        dest.writeByteArray(result.LCR.data);
                     }
                     dest.writeByte(result.secure ? (byte) 1 : 0);
                 }
@@ -919,51 +918,6 @@
     }
 
     /**
-     * A parcelable that contains rtt client information.
-     *
-     * @hide
-     */
-    public static class RttClient implements Parcelable {
-        // Package name of RttClient.
-        private final String mPackageName;
-
-        public RttClient(String packageName) {
-            mPackageName = packageName;
-        }
-
-        protected RttClient(Parcel in) {
-            mPackageName = in.readString();
-        }
-
-        public static final Creator<RttManager.RttClient> CREATOR =
-                new Creator<RttManager.RttClient>() {
-            @Override
-            public RttManager.RttClient createFromParcel(Parcel in) {
-                return new RttManager.RttClient(in);
-            }
-
-            @Override
-            public RttManager.RttClient[] newArray(int size) {
-                return new RttManager.RttClient[size];
-            }
-        };
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel parcel, int i) {
-            parcel.writeString(mPackageName);
-        }
-
-        public String getPackageName() {
-            return mPackageName;
-        }
-    }
-
-    /**
      * Request to start an RTT ranging
      *
      * @param params  -- RTT request Parameters
@@ -1202,7 +1156,6 @@
     /** @hide */
     public static final int CMD_OP_REG_BINDER           = BASE + 9;
 
-    private final Context mContext;
     private final WifiRttManager mNewService;
     private RttCapabilities mRttCapabilities;
 
@@ -1211,17 +1164,14 @@
      * Applications will almost always want to use
      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
      * the standard {@link android.content.Context#WIFI_RTT_SERVICE Context.WIFI_RTT_SERVICE}.
-     * @param context the application context
-     * @param service the Binder interface
-     * @param looper Looper for running the callbacks.
+     * @param service the new WifiRttManager service
      *
      * @hide
      */
-    public RttManager(Context context, IRttManager service, Looper looper) {
-        mContext = context;
-        mNewService = (WifiRttManager) mContext.getSystemService(Context.WIFI_RTT_RANGING_SERVICE);
+    public RttManager(Context context, WifiRttManager service) {
+        mNewService = service;
 
-        boolean rttSupported = mContext.getPackageManager().hasSystemFeature(
+        boolean rttSupported = context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_RTT);
 
         mRttCapabilities = new RttCapabilities();
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index c46789c..8024bf0 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -273,27 +273,6 @@
     public RadioChainInfo[] radioChainInfos;
 
     /**
-     * @hide
-     * Update RSSI of the scan result
-     * @param previousRssi
-     * @param previousSeen
-     * @param maxAge
-     */
-    public void averageRssi(int previousRssi, long previousSeen, int maxAge) {
-
-        if (seen == 0) {
-            seen = System.currentTimeMillis();
-        }
-        long age = seen - previousSeen;
-
-        if (previousSeen > 0 && age > 0 && age < maxAge/2) {
-            // Average the RSSI with previously seen instances of this scan result
-            double alpha = 0.5 - (double) age / (double) maxAge;
-            level = (int) ((double) level * (1 - alpha) + (double) previousRssi * alpha);
-        }
-    }
-
-    /**
      * Status indicating the scan result does not correspond to a user's saved configuration
      * @hide
      * @removed
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 7da2656..ddcf327 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -917,6 +917,9 @@
      * Does not guarantee that the returned address is valid for use.
      */
     public MacAddress getRandomizedMacAddress() {
+        if (mRandomizedMacAddress == null) {
+            mRandomizedMacAddress = MacAddress.ALL_ZEROS_ADDRESS;
+        }
         return mRandomizedMacAddress;
     }
 
@@ -1617,6 +1620,7 @@
         creatorUid = -1;
         shared = true;
         dtimInterval = 0;
+        mRandomizedMacAddress = MacAddress.ALL_ZEROS_ADDRESS;
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index e7377c1..8a3a7f5 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -176,6 +176,8 @@
     @Test
     public void testGetOrCreateRandomizedMacAddress_SavesAndReturnsSameAddress() {
         WifiConfiguration config = new WifiConfiguration();
+        assertEquals(MacAddress.ALL_ZEROS_ADDRESS, config.getRandomizedMacAddress());
+
         MacAddress firstMacAddress = config.getOrCreateRandomizedMacAddress();
         MacAddress secondMacAddress = config.getOrCreateRandomizedMacAddress();
 
@@ -185,6 +187,8 @@
     @Test
     public void testSetRandomizedMacAddress_ChangesSavedAddress() {
         WifiConfiguration config = new WifiConfiguration();
+        assertEquals(MacAddress.ALL_ZEROS_ADDRESS, config.getRandomizedMacAddress());
+
         MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress();
         config.setRandomizedMacAddress(macToChangeInto);
         MacAddress macAfterChange = config.getOrCreateRandomizedMacAddress();