Merge "Abort long-running benchmarks, report progress."
diff --git a/api/current.txt b/api/current.txt
index 9f4e051..12a3f07 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31015,6 +31015,7 @@
   }
 
   public final class Debug {
+    method public static void attachJvmtiAgent(java.lang.String, java.lang.String) throws java.io.IOException;
     method public static deprecated void changeDebugPort(int);
     method public static void dumpHprofData(java.lang.String) throws java.io.IOException;
     method public static boolean dumpService(java.lang.String, java.io.FileDescriptor, java.lang.String[]);
@@ -44558,6 +44559,7 @@
     field public static final int STATE_DOZE_SUSPEND = 4; // 0x4
     field public static final int STATE_OFF = 1; // 0x1
     field public static final int STATE_ON = 2; // 0x2
+    field public static final int STATE_ON_SUSPEND = 6; // 0x6
     field public static final int STATE_UNKNOWN = 0; // 0x0
     field public static final int STATE_VR = 5; // 0x5
   }
@@ -48955,14 +48957,22 @@
     method public android.view.textclassifier.TextClassification.Builder setText(java.lang.String);
   }
 
+  public static final class TextClassification.Options {
+    ctor public TextClassification.Options();
+    method public android.os.LocaleList getDefaultLocales();
+    method public android.view.textclassifier.TextClassification.Options setDefaultLocales(android.os.LocaleList);
+  }
+
   public final class TextClassificationManager {
     method public android.view.textclassifier.TextClassifier getTextClassifier();
     method public void setTextClassifier(android.view.textclassifier.TextClassifier);
   }
 
   public abstract interface TextClassifier {
-    method public abstract android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
-    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
+    method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options);
+    method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
+    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
+    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
     field public static final android.view.textclassifier.TextClassifier NO_OP;
     field public static final java.lang.String TYPE_ADDRESS = "address";
     field public static final java.lang.String TYPE_EMAIL = "email";
@@ -48985,6 +48995,12 @@
     method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
   }
 
+  public static final class TextSelection.Options {
+    ctor public TextSelection.Options();
+    method public android.os.LocaleList getDefaultLocales();
+    method public android.view.textclassifier.TextSelection.Options setDefaultLocales(android.os.LocaleList);
+  }
+
 }
 
 package android.view.textservice {
diff --git a/api/system-current.txt b/api/system-current.txt
index 53794b5..0b34782 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -33728,7 +33728,6 @@
     field public static final java.lang.String ACTION_UPDATE_PINS = "android.intent.action.UPDATE_PINS";
     field public static final java.lang.String ACTION_UPDATE_SMART_SELECTION = "android.intent.action.UPDATE_SMART_SELECTION";
     field public static final java.lang.String ACTION_UPDATE_SMS_SHORT_CODES = "android.intent.action.UPDATE_SMS_SHORT_CODES";
-    field public static final java.lang.String ACTION_UPDATE_TZDATA = "android.intent.action.UPDATE_TZDATA";
   }
 
   public abstract class CountDownTimer {
@@ -33757,6 +33756,7 @@
   }
 
   public final class Debug {
+    method public static void attachJvmtiAgent(java.lang.String, java.lang.String) throws java.io.IOException;
     method public static deprecated void changeDebugPort(int);
     method public static void dumpHprofData(java.lang.String) throws java.io.IOException;
     method public static boolean dumpService(java.lang.String, java.io.FileDescriptor, java.lang.String[]);
@@ -48294,6 +48294,7 @@
     field public static final int STATE_DOZE_SUSPEND = 4; // 0x4
     field public static final int STATE_OFF = 1; // 0x1
     field public static final int STATE_ON = 2; // 0x2
+    field public static final int STATE_ON_SUSPEND = 6; // 0x6
     field public static final int STATE_UNKNOWN = 0; // 0x0
     field public static final int STATE_VR = 5; // 0x5
   }
@@ -52695,14 +52696,22 @@
     method public android.view.textclassifier.TextClassification.Builder setText(java.lang.String);
   }
 
+  public static final class TextClassification.Options {
+    ctor public TextClassification.Options();
+    method public android.os.LocaleList getDefaultLocales();
+    method public android.view.textclassifier.TextClassification.Options setDefaultLocales(android.os.LocaleList);
+  }
+
   public final class TextClassificationManager {
     method public android.view.textclassifier.TextClassifier getTextClassifier();
     method public void setTextClassifier(android.view.textclassifier.TextClassifier);
   }
 
   public abstract interface TextClassifier {
-    method public abstract android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
-    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
+    method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options);
+    method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
+    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
+    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
     field public static final android.view.textclassifier.TextClassifier NO_OP;
     field public static final java.lang.String TYPE_ADDRESS = "address";
     field public static final java.lang.String TYPE_EMAIL = "email";
@@ -52725,6 +52734,12 @@
     method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
   }
 
+  public static final class TextSelection.Options {
+    ctor public TextSelection.Options();
+    method public android.os.LocaleList getDefaultLocales();
+    method public android.view.textclassifier.TextSelection.Options setDefaultLocales(android.os.LocaleList);
+  }
+
 }
 
 package android.view.textservice {
diff --git a/api/test-current.txt b/api/test-current.txt
index c8d676a..4eba70c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -31218,6 +31218,7 @@
   }
 
   public final class Debug {
+    method public static void attachJvmtiAgent(java.lang.String, java.lang.String) throws java.io.IOException;
     method public static deprecated void changeDebugPort(int);
     method public static void dumpHprofData(java.lang.String) throws java.io.IOException;
     method public static boolean dumpService(java.lang.String, java.io.FileDescriptor, java.lang.String[]);
@@ -45170,6 +45171,7 @@
     field public static final int STATE_DOZE_SUSPEND = 4; // 0x4
     field public static final int STATE_OFF = 1; // 0x1
     field public static final int STATE_ON = 2; // 0x2
+    field public static final int STATE_ON_SUSPEND = 6; // 0x6
     field public static final int STATE_UNKNOWN = 0; // 0x0
     field public static final int STATE_VR = 5; // 0x5
   }
@@ -49592,14 +49594,22 @@
     method public android.view.textclassifier.TextClassification.Builder setText(java.lang.String);
   }
 
+  public static final class TextClassification.Options {
+    ctor public TextClassification.Options();
+    method public android.os.LocaleList getDefaultLocales();
+    method public android.view.textclassifier.TextClassification.Options setDefaultLocales(android.os.LocaleList);
+  }
+
   public final class TextClassificationManager {
     method public android.view.textclassifier.TextClassifier getTextClassifier();
     method public void setTextClassifier(android.view.textclassifier.TextClassifier);
   }
 
   public abstract interface TextClassifier {
-    method public abstract android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
-    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
+    method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options);
+    method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
+    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
+    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
     field public static final android.view.textclassifier.TextClassifier NO_OP;
     field public static final java.lang.String TYPE_ADDRESS = "address";
     field public static final java.lang.String TYPE_EMAIL = "email";
@@ -49622,6 +49632,12 @@
     method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
   }
 
+  public static final class TextSelection.Options {
+    ctor public TextSelection.Options();
+    method public android.os.LocaleList getDefaultLocales();
+    method public android.view.textclassifier.TextSelection.Options setDefaultLocales(android.os.LocaleList);
+  }
+
 }
 
 package android.view.textservice {
diff --git a/cmds/appwidget/appwidget b/cmds/appwidget/appwidget
index 6105009..26ab173 100755
--- a/cmds/appwidget/appwidget
+++ b/cmds/appwidget/appwidget
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "appwidget" on the device, which has a very rudimentary shell.
 base=/system
 export CLASSPATH=$base/framework/appwidget.jar
diff --git a/cmds/bmgr/bmgr b/cmds/bmgr/bmgr
index 6b4bbe2d..60b5833 100755
--- a/cmds/bmgr/bmgr
+++ b/cmds/bmgr/bmgr
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "bmgr" on the device, which has a very rudimentary
 # shell.
 #
diff --git a/cmds/bu/bu b/cmds/bu/bu
index e8dbc31..e50b53d 100755
--- a/cmds/bu/bu
+++ b/cmds/bu/bu
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "bu" on the device
 #
 base=/system
diff --git a/cmds/content/content b/cmds/content/content
index a8e056d..f1bfe17 100755
--- a/cmds/content/content
+++ b/cmds/content/content
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "content" on the device, which has a very rudimentary shell.
 base=/system
 export CLASSPATH=$base/framework/content.jar
diff --git a/cmds/dpm/dpm b/cmds/dpm/dpm
index c2e5cbb..e0efdc1 100755
--- a/cmds/dpm/dpm
+++ b/cmds/dpm/dpm
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "dpm" on the device
 #
 base=/system
diff --git a/cmds/ime/ime b/cmds/ime/ime
index 96c56d3..1a1fdd9 100755
--- a/cmds/ime/ime
+++ b/cmds/ime/ime
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "pm" on the device, which has a very rudimentary
 # shell.
 #
diff --git a/cmds/input/input b/cmds/input/input
index 7f1a18e..54ab947 100755
--- a/cmds/input/input
+++ b/cmds/input/input
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "input" on the device, which has a very rudimentary
 # shell.
 #
diff --git a/cmds/locksettings/locksettings b/cmds/locksettings/locksettings
index c963b23..0ef4fa9 100755
--- a/cmds/locksettings/locksettings
+++ b/cmds/locksettings/locksettings
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "locksettings" on the device
 #
 base=/system
diff --git a/cmds/media/media b/cmds/media/media
index 1194442..5c0eb2f 100755
--- a/cmds/media/media
+++ b/cmds/media/media
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "media_cmd" on the device, which has a very rudimentary
 # shell.
 #
diff --git a/cmds/pm/pm b/cmds/pm/pm
index 8183838..53f85b2 100755
--- a/cmds/pm/pm
+++ b/cmds/pm/pm
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "pm" on the device, which has a very rudimentary
 # shell.
 #
diff --git a/cmds/requestsync/requestsync b/cmds/requestsync/requestsync
index 9315675..2d5d0e4 100755
--- a/cmds/requestsync/requestsync
+++ b/cmds/requestsync/requestsync
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "requestsync" on the device
 #
 base=/system
diff --git a/cmds/sm/sm b/cmds/sm/sm
index 8fba007..4bc859e0 100755
--- a/cmds/sm/sm
+++ b/cmds/sm/sm
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "sm" on the device, which has a very rudimentary
 # shell.
 #
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index c5805fbb..87d318b 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -46,6 +46,7 @@
     src/metrics/duration_helper/OringDurationTracker.cpp \
     src/metrics/duration_helper/MaxDurationTracker.cpp \
     src/metrics/ValueMetricProducer.cpp \
+    src/metrics/GaugeMetricProducer.cpp \
     src/metrics/MetricsManager.cpp \
     src/metrics/metrics_manager_util.cpp \
     src/packages/UidMap.cpp \
@@ -156,8 +157,10 @@
     tests/LogReader_test.cpp \
     tests/MetricsManager_test.cpp \
     tests/UidMap_test.cpp \
-    tests/OringDurationTracker_test.cpp \
-    tests/MaxDurationTracker_test.cpp
+    tests/metrics/OringDurationTracker_test.cpp \
+    tests/metrics/MaxDurationTracker_test.cpp \
+    tests/metrics/CountMetricProducer_test.cpp \
+    tests/metrics/EventMetricProducer_test.cpp
 
 
 LOCAL_STATIC_LIBRARIES := \
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index c0cedb1..8910523 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -89,7 +89,7 @@
         *dest = m;
     }
     auto temp = mUidMap->getOutput(key);
-    report.set_allocated_uid_map(&temp);
+    report.mutable_uid_map()->Swap(&temp);
     return report;
 }
 
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 890f44b..02e6903 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -142,6 +142,9 @@
     int KERNEL_WAKELOCK_TAG_ID = 1004;
     int KERNEL_WAKELOCK_NAME_KEY = 4;
 
+    int DEVICE_TEMPERATURE_TAG_ID = 33;
+    int DEVICE_TEMPERATURE_KEY = 1;
+
     // Count Screen ON events.
     CountMetric* metric = config.add_count_metric();
     metric->set_metric_id(1);
@@ -227,7 +230,7 @@
     // Duration of screen on time.
     durationMetric = config.add_duration_metric();
     durationMetric->set_metric_id(8);
-    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L);
     durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
     durationMetric->set_what("SCREEN_IS_ON");
 
@@ -247,7 +250,19 @@
     eventMetric->set_metric_id(9);
     eventMetric->set_what("SCREEN_TURNED_ON");
 
+    // Add an GaugeMetric.
+    GaugeMetric* gaugeMetric = config.add_gauge_metric();
+    gaugeMetric->set_metric_id(10);
+    gaugeMetric->set_what("DEVICE_TEMPERATURE");
+    gaugeMetric->set_gauge_field(DEVICE_TEMPERATURE_KEY);
+    gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
+
     // Event matchers............
+    LogEntryMatcher* temperatureEntryMatcher = config.add_log_entry_matcher();
+    temperatureEntryMatcher->set_name("DEVICE_TEMPERATURE");
+    temperatureEntryMatcher->mutable_simple_log_entry_matcher()->set_tag(
+        DEVICE_TEMPERATURE_TAG_ID);
+
     LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
     eventMatcher->set_name("SCREEN_TURNED_ON");
     SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
diff --git a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
index 38953f1..3608ee4 100644
--- a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
+++ b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
@@ -83,28 +83,31 @@
         return false;
     }
 
+    uint64_t timestamp = time(nullptr) * NS_PER_SEC;
+
     data->clear();
     Return<void> ret = gPowerHalV1_0->getPlatformLowPowerStats(
-            [&data](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
+            [&data, timestamp](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
 
                 if (status != Status::SUCCESS) return;
 
                 for (size_t i = 0; i < states.size(); i++) {
                     const PowerStatePlatformSleepState& state = states[i];
 
-                    auto statePtr = make_shared<LogEvent>(power_state_platform_sleep_state_tag);
+                    auto statePtr =
+                            make_shared<LogEvent>(power_state_platform_sleep_state_tag, timestamp);
                     auto elemList = statePtr->GetAndroidLogEventList();
                     *elemList << state.name;
                     *elemList << state.residencyInMsecSinceBoot;
                     *elemList << state.totalTransitions;
                     *elemList << state.supportedOnlyInSuspend;
+                    statePtr->init();
                     data->push_back(statePtr);
-
                     VLOG("powerstate: %s, %lld, %lld, %d", state.name.c_str(),
                          (long long)state.residencyInMsecSinceBoot,
                          (long long)state.totalTransitions, state.supportedOnlyInSuspend ? 1 : 0);
                     for (auto voter : state.voters) {
-                        auto voterPtr = make_shared<LogEvent>(power_state_voter_tag);
+                        auto voterPtr = make_shared<LogEvent>(power_state_voter_tag, timestamp);
                         auto elemList = voterPtr->GetAndroidLogEventList();
                         *elemList << state.name;
                         *elemList << voter.name;
@@ -128,7 +131,7 @@
             android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
     if (gPowerHal_1_1 != nullptr) {
         ret = gPowerHal_1_1->getSubsystemLowPowerStats(
-                [&data](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
+                [&data, timestamp](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
 
                     if (status != Status::SUCCESS) return;
 
@@ -137,8 +140,8 @@
                             const PowerStateSubsystem& subsystem = subsystems[i];
                             for (size_t j = 0; j < subsystem.states.size(); j++) {
                                 const PowerStateSubsystemSleepState& state = subsystem.states[j];
-                                auto subsystemStatePtr =
-                                        make_shared<LogEvent>(power_state_subsystem_state_tag);
+                                auto subsystemStatePtr = make_shared<LogEvent>(
+                                        power_state_subsystem_state_tag, timestamp);
                                 auto elemList = subsystemStatePtr->GetAndroidLogEventList();
                                 *elemList << subsystem.name;
                                 *elemList << state.name;
@@ -146,6 +149,7 @@
                                 *elemList << state.totalTransitions;
                                 *elemList << state.lastEntryTimestampMs;
                                 *elemList << state.supportedOnlyInSuspend;
+                                subsystemStatePtr->init();
                                 data->push_back(subsystemStatePtr);
                                 VLOG("subsystemstate: %s, %s, %lld, %lld, %lld",
                                      subsystem.name.c_str(), state.name.c_str(),
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 003b5c4..43543cc 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -30,11 +30,11 @@
 
 #include <iostream>
 
+using std::make_shared;
 using std::map;
+using std::shared_ptr;
 using std::string;
 using std::vector;
-using std::make_shared;
-using std::shared_ptr;
 
 namespace android {
 namespace os {
@@ -42,40 +42,35 @@
 
 StatsPullerManager::StatsPullerManager()
     : mCurrentPullingInterval(LONG_MAX), mPullStartTimeMs(get_pull_start_time_ms()) {
-    shared_ptr<StatsPuller> statsCompanionServicePuller = make_shared<StatsCompanionServicePuller>();
-    shared_ptr <StatsPuller>
-        resourcePowerManagerPuller = make_shared<ResourcePowerManagerPuller>();
+    shared_ptr<StatsPuller> statsCompanionServicePuller =
+            make_shared<StatsCompanionServicePuller>();
+    shared_ptr<StatsPuller> resourcePowerManagerPuller = make_shared<ResourcePowerManagerPuller>();
 
-    mPullers.insert({android::util::KERNEL_WAKELOCK_PULLED,
-                     statsCompanionServicePuller});
-    mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED,
-                     statsCompanionServicePuller});
-    mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED,
-                     statsCompanionServicePuller});
-    mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED_BY_FG_BG,
-                     statsCompanionServicePuller});
-    mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED_BY_FG_BG,
-                     statsCompanionServicePuller});
-    mPullers.insert({android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED,
-                     resourcePowerManagerPuller});
-    mPullers.insert({android::util::POWER_STATE_VOTER_PULLED,
-                     resourcePowerManagerPuller});
-    mPullers.insert({android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED,
-                     resourcePowerManagerPuller});
+    mPullers.insert({android::util::KERNEL_WAKELOCK_PULLED, statsCompanionServicePuller});
+    mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED, statsCompanionServicePuller});
+    mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED, statsCompanionServicePuller});
+    mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED_BY_FG_BG, statsCompanionServicePuller});
+    mPullers.insert(
+            {android::util::MOBILE_BYTES_TRANSFERRED_BY_FG_BG, statsCompanionServicePuller});
+    mPullers.insert(
+            {android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED, resourcePowerManagerPuller});
+    mPullers.insert({android::util::POWER_STATE_VOTER_PULLED, resourcePowerManagerPuller});
+    mPullers.insert(
+            {android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED, resourcePowerManagerPuller});
 
     mStatsCompanionService = StatsService::getStatsCompanionService();
 }
 
-        bool StatsPullerManager::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) {
-            if (DEBUG) ALOGD("Initiating pulling %d", tagId);
+bool StatsPullerManager::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) {
+    if (DEBUG) ALOGD("Initiating pulling %d", tagId);
 
-            if (mPullers.find(tagId) != mPullers.end()) {
-                return mPullers.find(tagId)->second->Pull(tagId, data);
-            } else {
-                ALOGD("Unknown tagId %d", tagId);
-                return false;  // Return early since we don't know what to pull.
-            }
-        }
+    if (mPullers.find(tagId) != mPullers.end()) {
+        return mPullers.find(tagId)->second->Pull(tagId, data);
+    } else {
+        ALOGD("Unknown tagId %d", tagId);
+        return false;  // Return early since we don't know what to pull.
+    }
+}
 
 StatsPullerManager& StatsPullerManager::GetInstance() {
     static StatsPullerManager instance;
@@ -91,7 +86,8 @@
     return time(nullptr) * 1000;
 }
 
-void StatsPullerManager::RegisterReceiver(int tagId, sp<PullDataReceiver> receiver, long intervalMs) {
+void StatsPullerManager::RegisterReceiver(int tagId, sp<PullDataReceiver> receiver,
+                                          long intervalMs) {
     AutoMutex _l(mReceiversLock);
     vector<ReceiverInfo>& receivers = mReceivers[tagId];
     for (auto it = receivers.begin(); it != receivers.end(); it++) {
@@ -143,8 +139,8 @@
             vector<pair<int, vector<ReceiverInfo*>>>();
     for (auto& pair : mReceivers) {
         vector<ReceiverInfo*> receivers = vector<ReceiverInfo*>();
-        if (pair.second.size() != 0){
-            for(auto& receiverInfo : pair.second) {
+        if (pair.second.size() != 0) {
+            for (auto& receiverInfo : pair.second) {
                 if (receiverInfo.timeInfo.first + receiverInfo.timeInfo.second > currentTimeMs) {
                     receivers.push_back(&receiverInfo);
                 }
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 8220fcb..913b906 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -35,7 +35,7 @@
     init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &mList);
 }
 
-LogEvent::LogEvent(int tag) : mList(tag) {
+LogEvent::LogEvent(int tag, uint64_t timestampNs) : mList(tag), mTimestampNs(timestampNs) {
 }
 
 LogEvent::~LogEvent() {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index df75d9f..2984940 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -50,7 +50,7 @@
      * any of the values. This constructor is useful for unit-testing since we can't pass in an
      * android_log_event_list since there is no copy constructor or assignment operator available.
      */
-    explicit LogEvent(int tag);
+    explicit LogEvent(int tag, uint64_t timestampNs);
 
     ~LogEvent();
 
@@ -123,7 +123,9 @@
     vector<android_log_list_element> mElements;
     // Need a copy of the android_log_event_list so the strings are not cleared.
     android_log_event_list mList;
-    long mTimestampNs;
+
+    uint64_t mTimestampNs;
+
     int mTagId;
 };
 
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 71cb7717..100a7a4 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -58,10 +58,9 @@
 
 // TODO: add back AnomalyTracker.
 CountMetricProducer::CountMetricProducer(const CountMetric& metric, const int conditionIndex,
-                                         const sp<ConditionWizard>& wizard)
-    // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
-    : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
-      mMetric(metric) {
+                                         const sp<ConditionWizard>& wizard,
+                                         const uint64_t startTimeNs)
+    : MetricProducer(startTimeNs, conditionIndex, wizard), mMetric(metric) {
     // TODO: evaluate initial conditions. and set mConditionMet.
     if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
         mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
@@ -114,15 +113,17 @@
 }
 
 StatsLogReport CountMetricProducer::onDumpReport() {
-    long long endTime = time(nullptr) * NANO_SECONDS_IN_A_SECOND;
+    long long endTime = time(nullptr) * NS_PER_SEC;
 
     // Dump current bucket if it's stale.
     // If current bucket is still on-going, don't force dump current bucket.
     // In finish(), We can force dump current bucket.
     flushCounterIfNeeded(endTime);
+    VLOG("metric %lld dump report now...", mMetric.metric_id());
 
-    for (const auto& counter : mPastBucketProtos) {
+    for (const auto& counter : mPastBuckets) {
         const HashableDimensionKey& hashableKey = counter.first;
+        VLOG("  dimension key %s", hashableKey.c_str());
         auto it = mDimensionKeyMap.find(hashableKey);
         if (it == mDimensionKeyMap.end()) {
             ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
@@ -147,20 +148,17 @@
         }
 
         // Then fill bucket_info (CountBucketInfo).
-        for (const auto& proto : counter.second) {
-            size_t bufferSize = proto->size();
-            char* buffer(new char[bufferSize]);
-            size_t pos = 0;
-            auto it = proto->data();
-            while (it.readBuffer() != NULL) {
-                size_t toRead = it.currentToRead();
-                std::memcpy(&buffer[pos], it.readBuffer(), toRead);
-                pos += toRead;
-                it.rp()->move(toRead);
-            }
-            mProto->write(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION, buffer, bufferSize);
+        for (const auto& bucket : counter.second) {
+            long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
+                          (long long)bucket.mBucketStartNs);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
+                          (long long)bucket.mBucketEndNs);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_COUNT, (long long)bucket.mCount);
+            mProto->end(bucketInfoToken);
+            VLOG("\t bucket [%lld - %lld] count: %lld", (long long)bucket.mBucketStartNs,
+                 (long long)bucket.mBucketEndNs, (long long)bucket.mCount);
         }
-
         mProto->end(wrapperToken);
     }
 
@@ -169,8 +167,8 @@
                   (long long)mCurrentBucketStartTimeNs);
 
     size_t bufferSize = mProto->size();
-    VLOG("metric %lld dump report now...", mMetric.metric_id());
     std::unique_ptr<uint8_t[]> buffer(new uint8_t[bufferSize]);
+
     size_t pos = 0;
     auto it = mProto->data();
     while (it.readBuffer() != NULL) {
@@ -181,7 +179,7 @@
     }
 
     startNewProtoOutputStream(endTime);
-    mPastBucketProtos.clear();
+    mPastBuckets.clear();
     mByteSize = 0;
 
     // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
@@ -239,20 +237,16 @@
     // adjust the bucket start time
     int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
 
+    CountBucket info;
+    info.mBucketStartNs = mCurrentBucketStartTimeNs;
+    info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
     for (const auto& counter : mCurrentSlicedCounter) {
-        unique_ptr<ProtoOutputStream> proto = make_unique<ProtoOutputStream>();
-        proto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
-                     (long long)mCurrentBucketStartTimeNs);
-        proto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
-                      (long long)mCurrentBucketStartTimeNs + mBucketSizeNs);
-        proto->write(FIELD_TYPE_INT64 | FIELD_ID_COUNT, (long long)counter.second);
-
-        auto& bucketList = mPastBucketProtos[counter.first];
-        bucketList.push_back(std::move(proto));
-        mByteSize += proto->size();
-
+        info.mCount = counter.second;
+        auto& bucketList = mPastBuckets[counter.first];
+        bucketList.push_back(info);
         VLOG("metric %lld, dump key value: %s -> %d", mMetric.metric_id(), counter.first.c_str(),
              counter.second);
+        mByteSize += sizeof(info);
     }
 
     // TODO: Re-add anomaly detection (similar to):
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 473a4ba..3bfc724 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -20,6 +20,7 @@
 #include <unordered_map>
 
 #include <android/util/ProtoOutputStream.h>
+#include <gtest/gtest_prod.h>
 #include "../condition/ConditionTracker.h"
 #include "../matchers/matcher_util.h"
 #include "CountAnomalyTracker.h"
@@ -34,11 +35,17 @@
 namespace os {
 namespace statsd {
 
+struct CountBucket {
+    int64_t mBucketStartNs;
+    int64_t mBucketEndNs;
+    int64_t mCount;
+};
+
 class CountMetricProducer : public MetricProducer {
 public:
     // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
     CountMetricProducer(const CountMetric& countMetric, const int conditionIndex,
-                        const sp<ConditionWizard>& wizard);
+                        const sp<ConditionWizard>& wizard, const uint64_t startTimeNs);
 
     virtual ~CountMetricProducer();
 
@@ -66,8 +73,7 @@
 private:
     const CountMetric mMetric;
 
-    std::unordered_map<HashableDimensionKey,
-        std::vector<unique_ptr<android::util::ProtoOutputStream>>> mPastBucketProtos;
+    std::unordered_map<HashableDimensionKey, std::vector<CountBucket>> mPastBuckets;
 
     size_t mByteSize;
 
@@ -83,6 +89,10 @@
     long long mProtoToken;
 
     void startNewProtoOutputStream(long long timestamp);
+
+    FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
+    FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
+    FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 340f503..09132bf 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -35,9 +35,9 @@
                                                const int conditionIndex, const size_t startIndex,
                                                const size_t stopIndex, const size_t stopAllIndex,
                                                const sp<ConditionWizard>& wizard,
-                                               const vector<KeyMatcher>& internalDimension)
-    // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
-    : MetricProducer(time(nullptr) * NANO_SECONDS_IN_A_SECOND, conditionIndex, wizard),
+                                               const vector<KeyMatcher>& internalDimension,
+                                               const uint64_t startTimeNs)
+    : MetricProducer(startTimeNs, conditionIndex, wizard),
       mMetric(metric),
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
@@ -131,7 +131,7 @@
     // Dump current bucket if it's stale.
     // If current bucket is still on-going, don't force dump current bucket.
     // In finish(), We can force dump current bucket.
-    flushIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+    flushIfNeeded(time(nullptr) * NS_PER_SEC);
     report.set_end_report_nanos(mCurrentBucketStartTimeNs);
 
     StatsLogReport_DurationMetricDataWrapper* wrapper = report.mutable_duration_metrics();
@@ -195,10 +195,10 @@
 }
 
 size_t DurationMetricProducer::byteSize() {
-// TODO: return actual proto size when ProtoOutputStream is ready for use for
-// DurationMetricsProducer.
-//    return mProto->size();
-  return 0;
+    // TODO: return actual proto size when ProtoOutputStream is ready for use for
+    // DurationMetricsProducer.
+    //    return mProto->size();
+    return 0;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index febf25d..12ff58e 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -40,7 +40,7 @@
     DurationMetricProducer(const DurationMetric& durationMetric, const int conditionIndex,
                            const size_t startIndex, const size_t stopIndex,
                            const size_t stopAllIndex, const sp<ConditionWizard>& wizard,
-                           const vector<KeyMatcher>& internalDimension);
+                           const vector<KeyMatcher>& internalDimension, const uint64_t startTimeNs);
 
     virtual ~DurationMetricProducer();
 
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index cbae1d3..677ae38 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -46,10 +46,9 @@
 const int FIELD_ID_STATS_EVENTS = 2;
 
 EventMetricProducer::EventMetricProducer(const EventMetric& metric, const int conditionIndex,
-                                         const sp<ConditionWizard>& wizard)
-    // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
-    : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
-      mMetric(metric) {
+                                         const sp<ConditionWizard>& wizard,
+                                         const uint64_t startTimeNs)
+    : MetricProducer(startTimeNs, conditionIndex, wizard), mMetric(metric) {
     if (metric.links().size() > 0) {
         mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
                                metric.links().end());
@@ -82,7 +81,7 @@
 }
 
 StatsLogReport EventMetricProducer::onDumpReport() {
-    long long endTime = time(nullptr) * NANO_SECONDS_IN_A_SECOND;
+    long long endTime = time(nullptr) * NS_PER_SEC;
     mProto->end(mProtoToken);
     mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, endTime);
 
@@ -114,7 +113,6 @@
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
         const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
         const LogEvent& event, bool scheduledPull) {
-
     if (!condition) {
         return;
     }
@@ -128,7 +126,7 @@
 }
 
 size_t EventMetricProducer::byteSize() {
-  return mProto->size();
+    return mProto->size();
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 7dd0e38..0fc2b5b 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -20,6 +20,7 @@
 #include <unordered_map>
 
 #include <android/util/ProtoOutputStream.h>
+
 #include "../condition/ConditionTracker.h"
 #include "../matchers/matcher_util.h"
 #include "MetricProducer.h"
@@ -35,13 +36,14 @@
 public:
     // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
     EventMetricProducer(const EventMetric& eventMetric, const int conditionIndex,
-                        const sp<ConditionWizard>& wizard);
+                        const sp<ConditionWizard>& wizard, const uint64_t startTimeNs);
 
     virtual ~EventMetricProducer();
 
     void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
                                    const std::map<std::string, HashableDimensionKey>& conditionKey,
-                                   bool condition, const LogEvent& event, bool scheduledPull) override;
+                                   bool condition, const LogEvent& event,
+                                   bool scheduledPull) override;
 
     void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
 
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
new file mode 100644
index 0000000..285c8f4
--- /dev/null
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -0,0 +1,232 @@
+/*
+* 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.
+*/
+
+#define DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "GaugeMetricProducer.h"
+#include "stats_util.h"
+
+#include <cutils/log.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using std::map;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+GaugeMetricProducer::GaugeMetricProducer(const GaugeMetric& metric, const int conditionIndex,
+                                         const sp<ConditionWizard>& wizard, const int pullTagId)
+    : MetricProducer((time(nullptr) * NS_PER_SEC), conditionIndex, wizard),
+      mMetric(metric),
+      mPullTagId(pullTagId) {
+    if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
+        mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
+    } else {
+        mBucketSizeNs = kDefaultGaugemBucketSizeNs;
+    }
+
+    // TODO: use UidMap if uid->pkg_name is required
+    mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+
+    if (metric.links().size() > 0) {
+        mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+                               metric.links().end());
+        mConditionSliced = true;
+    }
+
+    // Kicks off the puller immediately.
+    if (mPullTagId != -1) {
+        mStatsPullerManager.RegisterReceiver(mPullTagId, this,
+                                             metric.bucket().bucket_size_millis());
+    }
+
+    VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+         (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+GaugeMetricProducer::~GaugeMetricProducer() {
+    VLOG("~GaugeMetricProducer() called");
+}
+
+void GaugeMetricProducer::finish() {
+}
+
+static void addSlicedGaugeToReport(const vector<KeyValuePair>& key,
+                                   const vector<GaugeBucketInfo>& buckets,
+                                   StatsLogReport_GaugeMetricDataWrapper& wrapper) {
+    GaugeMetricData* data = wrapper.add_data();
+    for (const auto& kv : key) {
+        data->add_dimension()->CopyFrom(kv);
+    }
+    for (const auto& bucket : buckets) {
+        data->add_bucket_info()->CopyFrom(bucket);
+        VLOG("\t bucket [%lld - %lld] gauge: %lld", bucket.start_bucket_nanos(),
+             bucket.end_bucket_nanos(), bucket.gauge());
+    }
+}
+
+StatsLogReport GaugeMetricProducer::onDumpReport() {
+    VLOG("gauge metric %lld dump report now...", mMetric.metric_id());
+
+    StatsLogReport report;
+    report.set_metric_id(mMetric.metric_id());
+    report.set_start_report_nanos(mStartTimeNs);
+
+    // Dump current bucket if it's stale.
+    // If current bucket is still on-going, don't force dump current bucket.
+    // In finish(), We can force dump current bucket.
+    flushGaugeIfNeededLocked(time(nullptr) * NS_PER_SEC);
+    report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+    StatsLogReport_GaugeMetricDataWrapper* wrapper = report.mutable_gauge_metrics();
+
+    for (const auto& pair : mPastBuckets) {
+        const HashableDimensionKey& hashableKey = pair.first;
+        auto it = mDimensionKeyMap.find(hashableKey);
+        if (it == mDimensionKeyMap.end()) {
+            ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+            continue;
+        }
+
+        VLOG("  dimension key %s", hashableKey.c_str());
+        addSlicedGaugeToReport(it->second, pair.second, *wrapper);
+    }
+    return report;
+    // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
+}
+
+void GaugeMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
+    AutoMutex _l(mLock);
+    VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
+    mCondition = conditionMet;
+
+    // Push mode. Nothing to do.
+    if (mPullTagId == -1) {
+        return;
+    }
+    // If (1) the condition is not met or (2) we already pulled the gauge metric in the current
+    // bucket, do not pull gauge again.
+    if (!mCondition || mCurrentSlicedBucket.size() > 0) {
+        return;
+    }
+    vector<std::shared_ptr<LogEvent>> allData;
+    if (!mStatsPullerManager.Pull(mPullTagId, &allData)) {
+        ALOGE("Stats puller failed for tag: %d", mPullTagId);
+        return;
+    }
+    for (const auto& data : allData) {
+        onMatchedLogEvent(0, *data, false /*scheduledPull*/);
+    }
+    flushGaugeIfNeededLocked(eventTime);
+}
+
+void GaugeMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
+    VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+}
+
+long GaugeMetricProducer::getGauge(const LogEvent& event) {
+    status_t err = NO_ERROR;
+    long val = event.GetLong(mMetric.gauge_field(), &err);
+    if (err == NO_ERROR) {
+        return val;
+    } else {
+        VLOG("Can't find value in message.");
+        return -1;
+    }
+}
+
+void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
+    AutoMutex mutex(mLock);
+    if (allData.size() == 0) {
+        return;
+    }
+    for (const auto& data : allData) {
+        onMatchedLogEvent(0, *data, true /*scheduledPull*/);
+    }
+    uint64_t eventTime = allData.at(0)->GetTimestampNs();
+    flushGaugeIfNeededLocked(eventTime);
+}
+
+void GaugeMetricProducer::onMatchedLogEventInternal(
+        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const map<string, HashableDimensionKey>& conditionKey, bool condition,
+        const LogEvent& event, bool scheduledPull) {
+    if (condition == false) {
+        return;
+    }
+    uint64_t eventTimeNs = event.GetTimestampNs();
+    if (eventTimeNs < mCurrentBucketStartTimeNs) {
+        VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
+             (long long)mCurrentBucketStartTimeNs);
+        return;
+    }
+
+    // For gauge metric, we just simply use the latest guage in the given bucket.
+    const long gauge = getGauge(event);
+    if (gauge < 0) {
+        VLOG("Invalid gauge at event Time: %lld", (long long)eventTimeNs);
+        return;
+    }
+    mCurrentSlicedBucket[eventKey] = gauge;
+    if (mPullTagId < 0) {
+        flushGaugeIfNeededLocked(eventTimeNs);
+    }
+}
+
+// When a new matched event comes in, we check if event falls into the current
+// bucket. If not, flush the old counter to past buckets and initialize the new
+// bucket.
+// if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside
+// the GaugeMetricProducer while holding the lock.
+void GaugeMetricProducer::flushGaugeIfNeededLocked(const uint64_t eventTimeNs) {
+    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
+        VLOG("event time is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
+             (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
+        return;
+    }
+
+    // Adjusts the bucket start time
+    int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+
+    GaugeBucketInfo info;
+    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+    info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
+
+    for (const auto& slice : mCurrentSlicedBucket) {
+        info.set_gauge(slice.second);
+        auto& bucketList = mPastBuckets[slice.first];
+        bucketList.push_back(info);
+
+        VLOG("gauge metric %lld, dump key value: %s -> %ld", mMetric.metric_id(),
+             slice.first.c_str(), slice.second);
+    }
+    // Reset counters
+    mCurrentSlicedBucket.clear();
+
+    mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+    VLOG("metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+         (long long)mCurrentBucketStartTimeNs);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
new file mode 100644
index 0000000..bf8a86f
--- /dev/null
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <unordered_map>
+
+#include "../condition/ConditionTracker.h"
+#include "../external/PullDataReceiver.h"
+#include "../external/StatsPullerManager.h"
+#include "../matchers/matcher_util.h"
+#include "MetricProducer.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// This gauge metric producer first register the puller to automatically pull the gauge at the
+// beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise
+// proactively pull the gauge when the condition is changed to be true. Therefore, the gauge metric
+// producer always reports the guage at the earliest time of the bucket when the condition is met.
+class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
+public:
+    // TODO: Pass in the start time from MetricsManager, it should be consistent
+    // for all metrics.
+    GaugeMetricProducer(const GaugeMetric& countMetric, const int conditionIndex,
+                        const sp<ConditionWizard>& wizard, const int pullTagId);
+
+    virtual ~GaugeMetricProducer();
+
+    // Handles when the pulled data arrives.
+    void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
+
+    void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
+    void onSlicedConditionMayChange(const uint64_t eventTime) override;
+
+    void finish() override;
+
+    StatsLogReport onDumpReport() override;
+
+    // TODO: implements it when supporting proto stream.
+    size_t byteSize() override {
+        return 0;
+    };
+
+    // TODO: Implement this later.
+    virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+    // TODO: Implement this later.
+    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
+
+protected:
+    void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+                                   const std::map<std::string, HashableDimensionKey>& conditionKey,
+                                   bool condition, const LogEvent& event,
+                                   bool scheduledPull) override;
+
+private:
+    // The default bucket size for gauge metric is 1 second.
+    static const uint64_t kDefaultGaugemBucketSizeNs = 1000 * 1000 * 1000;
+    const GaugeMetric mMetric;
+
+    StatsPullerManager& mStatsPullerManager = StatsPullerManager::GetInstance();
+    // tagId for pulled data. -1 if this is not pulled
+    const int mPullTagId;
+
+    Mutex mLock;
+
+    // Save the past buckets and we can clear when the StatsLogReport is dumped.
+    std::unordered_map<HashableDimensionKey, std::vector<GaugeBucketInfo>> mPastBuckets;
+
+    // The current bucket.
+    std::unordered_map<HashableDimensionKey, long> mCurrentSlicedBucket;
+
+    void flushGaugeIfNeededLocked(const uint64_t newEventTime);
+
+    long getGauge(const LogEvent& event);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 3b117ec..6ba726f4 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -40,6 +40,7 @@
         : mStartTimeNs(startTimeNs),
           mCurrentBucketStartTimeNs(startTimeNs),
           mCondition(conditionIndex >= 0 ? false : true),
+          mConditionSliced(false),
           mWizard(wizard),
           mConditionTrackerIndex(conditionIndex) {
         // reuse the same map for non-sliced metrics too. this way, we avoid too many if-else.
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 521fcc3..80b325f 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -15,17 +15,17 @@
  */
 #define DEBUG true  // STOPSHIP if true
 #include "Log.h"
-
 #include "MetricsManager.h"
-#include <log/logprint.h>
-#include "../condition/CombinationConditionTracker.h"
-#include "../condition/SimpleConditionTracker.h"
-#include "../matchers/CombinationLogMatchingTracker.h"
-#include "../matchers/SimpleLogMatchingTracker.h"
+
 #include "CountMetricProducer.h"
+#include "condition/CombinationConditionTracker.h"
+#include "condition/SimpleConditionTracker.h"
+#include "matchers/CombinationLogMatchingTracker.h"
+#include "matchers/SimpleLogMatchingTracker.h"
 #include "metrics_manager_util.h"
 #include "stats_util.h"
 
+#include <log/logprint.h>
 using std::make_unique;
 using std::set;
 using std::string;
@@ -146,7 +146,8 @@
                 auto& metricList = pair->second;
                 for (const int metricIndex : metricList) {
                     // pushed metrics are never scheduled pulls
-                    mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event, false);
+                    mAllMetricProducers[metricIndex]->onMatchedLogEvent(
+                        i, event, false /* schedulePull */);
                 }
             }
         }
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 44cd637..63e2c33 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -96,4 +96,3 @@
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
-
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index ca33371..07a078f 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -23,12 +23,12 @@
 #include <limits.h>
 #include <stdlib.h>
 
-using std::map;
-using std::unordered_map;
 using std::list;
 using std::make_shared;
+using std::map;
 using std::shared_ptr;
 using std::unique_ptr;
+using std::unordered_map;
 
 namespace android {
 namespace os {
@@ -36,51 +36,50 @@
 
 // ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
 ValueMetricProducer::ValueMetricProducer(const ValueMetric& metric, const int conditionIndex,
-                                         const sp<ConditionWizard>& wizard, const int pullTagId)
-    : MetricProducer((time(nullptr) / 600 * 600 * NANO_SECONDS_IN_A_SECOND), conditionIndex,
-                     wizard),
-      mMetric(metric),
-      mPullTagId(pullTagId) {
-  // TODO: valuemetric for pushed events may need unlimited bucket length
-  mBucketSizeNs = mMetric.bucket().bucket_size_millis() * 1000 * 1000;
+                                         const sp<ConditionWizard>& wizard, const int pullTagId,
+                                         const uint64_t startTimeNs)
+    : MetricProducer(startTimeNs, conditionIndex, wizard), mMetric(metric), mPullTagId(pullTagId) {
+    // TODO: valuemetric for pushed events may need unlimited bucket length
+    mBucketSizeNs = mMetric.bucket().bucket_size_millis() * 1000 * 1000;
 
-  mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+    mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
 
-  if (metric.links().size() > 0) {
-    mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
-                           metric.links().end());
-    mConditionSliced = true;
-  }
+    if (metric.links().size() > 0) {
+        mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+                               metric.links().end());
+        mConditionSliced = true;
+    }
 
-  if (!metric.has_condition() && mPullTagId != -1) {
-    mStatsPullerManager.RegisterReceiver(mPullTagId, this, metric.bucket().bucket_size_millis());
-  }
+    if (!metric.has_condition() && mPullTagId != -1) {
+        mStatsPullerManager.RegisterReceiver(mPullTagId, this,
+                                             metric.bucket().bucket_size_millis());
+    }
 
-  VLOG("value metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
-       (long long)mBucketSizeNs, (long long)mStartTimeNs);
+    VLOG("value metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+         (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
 
 ValueMetricProducer::~ValueMetricProducer() {
-  VLOG("~ValueMetricProducer() called");
+    VLOG("~ValueMetricProducer() called");
 }
 
 void ValueMetricProducer::finish() {
-  // TODO: write the StatsLogReport to dropbox using
-  // DropboxWriter.
+    // TODO: write the StatsLogReport to dropbox using
+    // DropboxWriter.
 }
 
 static void addSlicedCounterToReport(StatsLogReport_ValueMetricDataWrapper& wrapper,
                                      const vector<KeyValuePair>& key,
                                      const vector<ValueBucketInfo>& buckets) {
-  ValueMetricData* data = wrapper.add_data();
-  for (const auto& kv : key) {
-    data->add_dimension()->CopyFrom(kv);
-  }
-  for (const auto& bucket : buckets) {
-    data->add_bucket_info()->CopyFrom(bucket);
-    VLOG("\t bucket [%lld - %lld] value: %lld", bucket.start_bucket_nanos(),
-         bucket.end_bucket_nanos(), bucket.value());
-  }
+    ValueMetricData* data = wrapper.add_data();
+    for (const auto& kv : key) {
+        data->add_dimension()->CopyFrom(kv);
+    }
+    for (const auto& bucket : buckets) {
+        data->add_bucket_info()->CopyFrom(bucket);
+        VLOG("\t bucket [%lld - %lld] value: %lld", bucket.start_bucket_nanos(),
+             bucket.end_bucket_nanos(), bucket.value());
+    }
 }
 
 void ValueMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
@@ -88,33 +87,28 @@
 }
 
 StatsLogReport ValueMetricProducer::onDumpReport() {
-  VLOG("metric %lld dump report now...", mMetric.metric_id());
+    VLOG("metric %lld dump report now...", mMetric.metric_id());
 
-  StatsLogReport report;
-  report.set_metric_id(mMetric.metric_id());
-  report.set_start_report_nanos(mStartTimeNs);
+    StatsLogReport report;
+    report.set_metric_id(mMetric.metric_id());
+    report.set_start_report_nanos(mStartTimeNs);
+    report.set_end_report_nanos(mCurrentBucketStartTimeNs);
 
-  // Dump current bucket if it's stale.
-  // If current bucket is still on-going, don't force dump current bucket.
-  // In finish(), We can force dump current bucket.
-  //    flush_if_needed(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
-  report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+    StatsLogReport_ValueMetricDataWrapper* wrapper = report.mutable_value_metrics();
 
-  StatsLogReport_ValueMetricDataWrapper* wrapper = report.mutable_value_metrics();
+    for (const auto& pair : mPastBuckets) {
+        const HashableDimensionKey& hashableKey = pair.first;
+        auto it = mDimensionKeyMap.find(hashableKey);
+        if (it == mDimensionKeyMap.end()) {
+            ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+            continue;
+        }
 
-  for (const auto& pair : mPastBuckets) {
-    const HashableDimensionKey& hashableKey = pair.first;
-    auto it = mDimensionKeyMap.find(hashableKey);
-    if (it == mDimensionKeyMap.end()) {
-      ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
-      continue;
+        VLOG("  dimension key %s", hashableKey.c_str());
+        addSlicedCounterToReport(*wrapper, it->second, pair.second);
     }
-
-    VLOG("  dimension key %s", hashableKey.c_str());
-    addSlicedCounterToReport(*wrapper, it->second, pair.second);
-  }
-  return report;
-  // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
+    return report;
+    // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
 }
 
 void ValueMetricProducer::onConditionChanged(const bool condition, const uint64_t eventTime) {
@@ -158,50 +152,50 @@
 }
 
 void ValueMetricProducer::onMatchedLogEventInternal(
-    const size_t matcherIndex, const HashableDimensionKey& eventKey,
-    const map<string, HashableDimensionKey>& conditionKey, bool condition,
-    const LogEvent& event, bool scheduledPull) {
-  uint64_t eventTimeNs = event.GetTimestampNs();
-  if (eventTimeNs < mCurrentBucketStartTimeNs) {
-      VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
-           (long long)mCurrentBucketStartTimeNs);
-      return;
-  }
-
-  Interval& interval = mCurrentSlicedBucket[eventKey];
-
-  long value = get_value(event);
-
-  if (scheduledPull) {
-    if (interval.raw.size() > 0) {
-      interval.raw.back().second = value;
-    } else {
-      interval.raw.push_back(std::make_pair(value, value));
+        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const map<string, HashableDimensionKey>& conditionKey, bool condition,
+        const LogEvent& event, bool scheduledPull) {
+    uint64_t eventTimeNs = event.GetTimestampNs();
+    if (eventTimeNs < mCurrentBucketStartTimeNs) {
+        VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
+             (long long)mCurrentBucketStartTimeNs);
+        return;
     }
-    mNextSlicedBucket[eventKey].raw[0].first = value;
-  } else {
-    if (mCondition == ConditionState::kTrue) {
-      interval.raw.push_back(std::make_pair(value, 0));
+
+    Interval& interval = mCurrentSlicedBucket[eventKey];
+
+    long value = get_value(event);
+
+    if (scheduledPull) {
+        if (interval.raw.size() > 0) {
+            interval.raw.back().second = value;
+        } else {
+            interval.raw.push_back(std::make_pair(value, value));
+        }
+        mNextSlicedBucket[eventKey].raw[0].first = value;
     } else {
-      if (interval.raw.size() != 0) {
-        interval.raw.back().second = value;
-      }
+        if (mCondition == ConditionState::kTrue) {
+            interval.raw.push_back(std::make_pair(value, 0));
+        } else {
+            if (interval.raw.size() != 0) {
+                interval.raw.back().second = value;
+            }
+        }
     }
-  }
-  if (mPullTagId == -1) {
-      flush_if_needed(eventTimeNs);
-  }
+    if (mPullTagId == -1) {
+        flush_if_needed(eventTimeNs);
+    }
 }
 
 long ValueMetricProducer::get_value(const LogEvent& event) {
-  status_t err = NO_ERROR;
-  long val = event.GetLong(mMetric.value_field(), &err);
-  if (err == NO_ERROR) {
-    return val;
-  } else {
-    VLOG("Can't find value in message.");
-    return 0;
-  }
+    status_t err = NO_ERROR;
+    long val = event.GetLong(mMetric.value_field(), &err);
+    if (err == NO_ERROR) {
+        return val;
+    } else {
+        VLOG("Can't find value in message.");
+        return 0;
+    }
 }
 
 void ValueMetricProducer::flush_if_needed(const uint64_t eventTimeNs) {
@@ -218,22 +212,22 @@
     info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
 
     for (const auto& slice : mCurrentSlicedBucket) {
-      long value = 0;
-      for (const auto& pair : slice.second.raw) {
-        value += pair.second - pair.first;
-      }
-      info.set_value(value);
-      VLOG(" %s, %ld", slice.first.c_str(), value);
-      // it will auto create new vector of ValuebucketInfo if the key is not found.
-      auto& bucketList = mPastBuckets[slice.first];
-      bucketList.push_back(info);
+        long value = 0;
+        for (const auto& pair : slice.second.raw) {
+            value += pair.second - pair.first;
+        }
+        info.set_value(value);
+        VLOG(" %s, %ld", slice.first.c_str(), value);
+        // it will auto create new vector of ValuebucketInfo if the key is not found.
+        auto& bucketList = mPastBuckets[slice.first];
+        bucketList.push_back(info);
     }
 
     // Reset counters
     mCurrentSlicedBucket.swap(mNextSlicedBucket);
     mNextSlicedBucket.clear();
     int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-    if (numBucketsForward >1) {
+    if (numBucketsForward > 1) {
         VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
     }
     mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
@@ -243,4 +237,4 @@
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 8653981..548cd44 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -33,7 +33,8 @@
 class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
 public:
     ValueMetricProducer(const ValueMetric& valueMetric, const int conditionIndex,
-                        const sp<ConditionWizard>& wizard, const int pullTagId);
+                        const sp<ConditionWizard>& wizard, const int pullTagId,
+                        const uint64_t startTimeNs);
 
     virtual ~ValueMetricProducer();
 
@@ -47,7 +48,9 @@
 
     void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
     // TODO: Implement this later.
-    size_t byteSize() override{return 0;};
+    size_t byteSize() override {
+        return 0;
+    };
 
     // TODO: Implement this later.
     virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 3ff2a77..ca9cdfb 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -22,6 +22,7 @@
 #include "CountMetricProducer.h"
 #include "DurationMetricProducer.h"
 #include "EventMetricProducer.h"
+#include "GaugeMetricProducer.h"
 #include "ValueMetricProducer.h"
 #include "stats_util.h"
 
@@ -191,10 +192,11 @@
                  unordered_map<int, std::vector<int>>& conditionToMetricMap,
                  unordered_map<int, std::vector<int>>& trackerToMetricMap) {
     sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
-    const int allMetricsCount =
-            config.count_metric_size() + config.duration_metric_size() + config.event_metric_size() + config.value_metric_size();
+    const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
+                                config.event_metric_size() + config.value_metric_size();
     allMetricProducers.reserve(allMetricsCount);
     StatsPullerManager& statsPullerManager = StatsPullerManager::GetInstance();
+    uint64_t startTimeNs = time(nullptr) * NS_PER_SEC;
 
     // Build MetricProducers for each metric defined in config.
     // build CountMetricProducer
@@ -220,7 +222,8 @@
                                        conditionToMetricMap);
         }
 
-        sp<MetricProducer> countProducer = new CountMetricProducer(metric, conditionIndex, wizard);
+        sp<MetricProducer> countProducer =
+                new CountMetricProducer(metric, conditionIndex, wizard, startTimeNs);
         allMetricProducers.push_back(countProducer);
     }
 
@@ -281,7 +284,7 @@
 
         sp<MetricProducer> durationMetric = new DurationMetricProducer(
                 metric, conditionIndex, trackerIndices[0], trackerIndices[1], trackerIndices[2],
-                wizard, internalDimension);
+                wizard, internalDimension, startTimeNs);
 
         allMetricProducers.push_back(durationMetric);
     }
@@ -307,7 +310,8 @@
                                        conditionToMetricMap);
         }
 
-        sp<MetricProducer> eventMetric = new EventMetricProducer(metric, conditionIndex, wizard);
+        sp<MetricProducer> eventMetric =
+                new EventMetricProducer(metric, conditionIndex, wizard, startTimeNs);
 
         allMetricProducers.push_back(eventMetric);
     }
@@ -348,9 +352,49 @@
         }
 
         sp<MetricProducer> valueProducer =
-                new ValueMetricProducer(metric, conditionIndex, wizard, pullTagId);
+                new ValueMetricProducer(metric, conditionIndex, wizard, pullTagId, startTimeNs);
         allMetricProducers.push_back(valueProducer);
     }
+
+    // Gauge metrics.
+    for (int i = 0; i < config.gauge_metric_size(); i++) {
+        const GaugeMetric& metric = config.gauge_metric(i);
+        if (!metric.has_what()) {
+            ALOGW("cannot find what in ValueMetric %lld", metric.metric_id());
+            return false;
+        }
+
+        int metricIndex = allMetricProducers.size();
+        int trackerIndex;
+        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
+                                         allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
+                                         trackerIndex)) {
+            return false;
+        }
+
+        sp<LogMatchingTracker> atomMatcher = allLogEntryMatchers.at(trackerIndex);
+        // If it is pulled atom, it should be simple matcher with one tagId.
+        int pullTagId = -1;
+        for (int tagId : atomMatcher->getTagIds()) {
+            if (statsPullerManager.PullerForMatcherExists(tagId)) {
+                if (atomMatcher->getTagIds().size() != 1) {
+                    return false;
+                }
+                pullTagId = tagId;
+            }
+        }
+
+        int conditionIndex = -1;
+        if (metric.has_condition()) {
+            handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+                                       metric.links(), allConditionTrackers, conditionIndex,
+                                       conditionToMetricMap);
+        }
+
+        sp<MetricProducer> gaugeProducer =
+                new GaugeMetricProducer(metric, conditionIndex, wizard, pullTagId);
+        allMetricProducers.push_back(gaugeProducer);
+    }
     return true;
 }
 
@@ -368,6 +412,7 @@
         ALOGE("initLogMatchingTrackers failed");
         return false;
     }
+    ALOGD("initLogMatchingTrackers succeed...");
 
     if (!initConditions(config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
                         trackerToConditionMap)) {
diff --git a/cmds/statsd/src/stats_events_copy.proto b/cmds/statsd/src/stats_events_copy.proto
index 9470372..898856b 100644
--- a/cmds/statsd/src/stats_events_copy.proto
+++ b/cmds/statsd/src/stats_events_copy.proto
@@ -61,6 +61,7 @@
         UidProcessStateChanged uid_process_state_changed = 27;
         ProcessLifeCycleStateChanged process_life_cycle_state_changed = 28;
         ScreenStateChanged screen_state_changed = 29;
+        DeviceTemperatureReported device_temperature_reported = 33;
         // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 }
@@ -101,6 +102,17 @@
  */
 
 /**
+ * Logs the temperature of the device, in tenths of a degree Celsius.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message DeviceTemperatureReported {
+    // Temperature in tenths of a degree C.
+    optional int32 temperature = 1;
+}
+
+/**
  * Logs when the screen state changes.
  *
  * Logged from:
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index d3d7e37..a9507bf 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -28,7 +28,6 @@
 
 #define DEFAULT_DIMENSION_KEY ""
 #define MATCHER_NOT_FOUND -2
-#define NANO_SECONDS_IN_A_SECOND (1000 * 1000 * 1000)
 
 typedef std::string HashableDimensionKey;
 
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 87884b33..f3e6894 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -230,7 +230,9 @@
 
   repeated DurationMetric duration_metric = 5;
 
-  repeated LogEntryMatcher log_entry_matcher = 6;
+  repeated GaugeMetric gauge_metric = 6;
 
-  repeated Condition condition = 7;
+  repeated LogEntryMatcher log_entry_matcher = 7;
+
+  repeated Condition condition = 8;
 }
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index fdfe8ef..d0898b0 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -42,7 +42,7 @@
     auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
     simpleMatcher->set_tag(TAG_ID);
 
-    LogEvent event(TAG_ID);
+    LogEvent event(TAG_ID, 0);
 
     // Convert to a LogEvent
     event.init();
@@ -62,7 +62,7 @@
     keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2);
 
     // Set up the event
-    LogEvent event(TAG_ID);
+    LogEvent event(TAG_ID, 0);
     auto list = event.GetAndroidLogEventList();
     *list << true;
     *list << false;
@@ -98,7 +98,7 @@
     keyValue->set_eq_string("some value");
 
     // Set up the event
-    LogEvent event(TAG_ID);
+    LogEvent event(TAG_ID, 0);
     auto list = event.GetAndroidLogEventList();
     *list << "some value";
 
@@ -119,7 +119,7 @@
     keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
 
     // Set up the event
-    LogEvent event(TAG_ID);
+    LogEvent event(TAG_ID, 0);
     auto list = event.GetAndroidLogEventList();
     *list << 11;
 
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index e8e4d8b..7333785 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -17,6 +17,7 @@
 #include "src/condition/ConditionTracker.h"
 #include "src/matchers/LogMatchingTracker.h"
 #include "src/metrics/CountMetricProducer.h"
+#include "src/metrics/GaugeMetricProducer.h"
 #include "src/metrics/MetricProducer.h"
 #include "src/metrics/ValueMetricProducer.h"
 #include "src/metrics/metrics_manager_util.h"
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
new file mode 100644
index 0000000..5a4ee73
--- /dev/null
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -0,0 +1,183 @@
+// 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.
+
+#include "metrics_test_helper.h"
+#include "src/metrics/CountMetricProducer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(CountMetricProducerTest, TestNonDimensionalEvents) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+    int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+    int tagId = 1;
+
+    CountMetric metric;
+    metric.set_metric_id(1);
+    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+
+    LogEvent event1(tagId, bucketStartTimeNs + 1);
+    LogEvent event2(tagId, bucketStartTimeNs + 2);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    CountMetricProducer countProducer(metric, -1 /*-1 meaning no condition*/, wizard,
+                                      bucketStartTimeNs);
+
+    // 2 events in bucket 1.
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
+    countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+                countProducer.mPastBuckets.end());
+    const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+    EXPECT_EQ(1UL, buckets.size());
+    const auto& bucketInfo = buckets[0];
+    EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
+    EXPECT_EQ(2LL, bucketInfo.mCount);
+
+    // 1 matched event happens in bucket 2.
+    LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 2);
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3, false);
+    countProducer.flushCounterIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1);
+    EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+                countProducer.mPastBuckets.end());
+    EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY].size());
+    const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY][1];
+    EXPECT_EQ(bucket2StartTimeNs, bucketInfo2.mBucketStartNs);
+    EXPECT_EQ(bucket2StartTimeNs + bucketSizeNs, bucketInfo2.mBucketEndNs);
+    EXPECT_EQ(1LL, bucketInfo2.mCount);
+
+    // nothing happens in bucket 3. we should not record anything for bucket 3.
+    countProducer.flushCounterIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 1);
+    EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+                countProducer.mPastBuckets.end());
+    const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+    EXPECT_EQ(2UL, buckets3.size());
+}
+
+TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    CountMetric metric;
+    metric.set_metric_id(1);
+    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_condition("SCREEN_ON");
+
+    LogEvent event1(1, bucketStartTimeNs + 1);
+    LogEvent event2(1, bucketStartTimeNs + 10);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    CountMetricProducer countProducer(metric, 1, wizard, bucketStartTimeNs);
+
+    countProducer.onConditionChanged(true, bucketStartTimeNs);
+    countProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
+    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
+    countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2);
+    countProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
+    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
+    countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+
+    EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+                countProducer.mPastBuckets.end());
+    const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+    EXPECT_EQ(1UL, buckets.size());
+    const auto& bucketInfo = buckets[0];
+    EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
+    EXPECT_EQ(1LL, bucketInfo.mCount);
+}
+
+TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    CountMetric metric;
+    metric.set_metric_id(1);
+    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON");
+    EventConditionLink* link = metric.add_links();
+    link->set_condition("APP_IN_BACKGROUND_PER_UID");
+    link->add_key_in_main()->set_key(1);
+    link->add_key_in_condition()->set_key(2);
+
+    LogEvent event1(1, bucketStartTimeNs + 1);
+    auto list = event1.GetAndroidLogEventList();
+    *list << "111";  // uid
+    event1.init();
+    ConditionKey key1;
+    key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
+
+    LogEvent event2(1, bucketStartTimeNs + 10);
+    auto list2 = event2.GetAndroidLogEventList();
+    *list2 << "222";  // uid
+    event2.init();
+    ConditionKey key2;
+    key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
+
+    EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue));
+
+    CountMetricProducer countProducer(metric, 1 /*condition tracker index*/, wizard,
+                                      bucketStartTimeNs);
+
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
+
+    countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+
+    EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+                countProducer.mPastBuckets.end());
+    const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+    EXPECT_EQ(1UL, buckets.size());
+    const auto& bucketInfo = buckets[0];
+    EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
+    EXPECT_EQ(1LL, bucketInfo.mCount);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
new file mode 100644
index 0000000..76dbc73
--- /dev/null
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -0,0 +1,130 @@
+// 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.
+
+#include "metrics_test_helper.h"
+#include "src/metrics/EventMetricProducer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(EventMetricProducerTest, TestNoCondition) {
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    EventMetric metric;
+    metric.set_metric_id(1);
+
+    LogEvent event1(1 /*tag id*/, bucketStartTimeNs + 1);
+    LogEvent event2(1 /*tag id*/, bucketStartTimeNs + 2);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    EventMetricProducer eventProducer(metric, -1 /*-1 meaning no condition*/, wizard,
+                                      bucketStartTimeNs);
+
+    eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
+    eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
+
+    // TODO: get the report and check the content after the ProtoOutputStream change is done.
+    // eventProducer.onDumpReport();
+}
+
+TEST(EventMetricProducerTest, TestEventsWithNonSlicedCondition) {
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    EventMetric metric;
+    metric.set_metric_id(1);
+    metric.set_condition("SCREEN_ON");
+
+    LogEvent event1(1, bucketStartTimeNs + 1);
+    LogEvent event2(1, bucketStartTimeNs + 10);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    EventMetricProducer eventProducer(metric, 1, wizard, bucketStartTimeNs);
+
+    eventProducer.onConditionChanged(true /*condition*/, bucketStartTimeNs);
+    eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
+
+    eventProducer.onConditionChanged(false /*condition*/, bucketStartTimeNs + 2);
+
+    eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
+
+    // TODO: get the report and check the content after the ProtoOutputStream change is done.
+    // eventProducer.onDumpReport();
+}
+
+TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) {
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    EventMetric metric;
+    metric.set_metric_id(1);
+    metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON");
+    EventConditionLink* link = metric.add_links();
+    link->set_condition("APP_IN_BACKGROUND_PER_UID");
+    link->add_key_in_main()->set_key(1);
+    link->add_key_in_condition()->set_key(2);
+
+    LogEvent event1(1, bucketStartTimeNs + 1);
+    auto list = event1.GetAndroidLogEventList();
+    *list << "111";  // uid
+    event1.init();
+    ConditionKey key1;
+    key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
+
+    LogEvent event2(1, bucketStartTimeNs + 10);
+    auto list2 = event2.GetAndroidLogEventList();
+    *list2 << "222";  // uid
+    event2.init();
+    ConditionKey key2;
+    key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
+
+    EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue));
+
+    EventMetricProducer eventProducer(metric, 1, wizard, bucketStartTimeNs);
+
+    eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
+    eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
+
+    // TODO: get the report and check the content after the ProtoOutputStream change is done.
+    // eventProducer.onDumpReport();
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
similarity index 92%
rename from cmds/statsd/tests/MaxDurationTracker_test.cpp
rename to cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index ae8bf42..f2abe7b 100644
--- a/cmds/statsd/tests/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
+#include "metrics_test_helper.h"
 #include "src/condition/ConditionWizard.h"
 #include "src/metrics/duration_helper/MaxDurationTracker.h"
 
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
 #include <stdio.h>
 #include <set>
 #include <unordered_map>
@@ -32,13 +32,9 @@
 
 #ifdef __ANDROID__
 
-class MockConditionWizard : public ConditionWizard {
-public:
-    MOCK_METHOD2(
-            query,
-            ConditionState(const int conditionIndex,
-                           const std::map<std::string, HashableDimensionKey>& conditionParameters));
-};
+namespace android {
+namespace os {
+namespace statsd {
 
 TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -110,6 +106,9 @@
     EXPECT_EQ(5, buckets[0].duration_nanos());
 }
 
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/cmds/statsd/tests/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
similarity index 90%
rename from cmds/statsd/tests/OringDurationTracker_test.cpp
rename to cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 0b79819..338d55d 100644
--- a/cmds/statsd/tests/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -12,18 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
+#include "metrics_test_helper.h"
 #include "src/condition/ConditionWizard.h"
 #include "src/metrics/duration_helper/OringDurationTracker.h"
 
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
 #include <stdio.h>
 #include <set>
 #include <unordered_map>
 #include <vector>
 
-using namespace android::os::statsd;
 using namespace testing;
 using android::sp;
 using std::set;
@@ -31,14 +30,9 @@
 using std::vector;
 
 #ifdef __ANDROID__
-
-class MockConditionWizard : public ConditionWizard {
-public:
-    MOCK_METHOD2(
-            query,
-            ConditionState(const int conditionIndex,
-                           const std::map<std::string, HashableDimensionKey>& conditionParameters));
-};
+namespace android {
+namespace os {
+namespace statsd {
 
 TEST(OringDurationTrackerTest, TestDurationOverlap) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -93,7 +87,9 @@
     EXPECT_EQ(1u, buckets.size());
     EXPECT_EQ(5, buckets[0].duration_nanos());
 }
-
+}  // 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
new file mode 100644
index 0000000..5fd7d62
--- /dev/null
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -0,0 +1,35 @@
+// 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.
+#pragma once
+
+#include "src/condition/ConditionWizard.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class MockConditionWizard : public ConditionWizard {
+public:
+    MOCK_METHOD2(
+            query,
+            ConditionState(const int conditionIndex,
+                           const std::map<std::string, HashableDimensionKey>& conditionParameters));
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/svc/svc b/cmds/svc/svc
index 27111cd..07b50fe 100755
--- a/cmds/svc/svc
+++ b/cmds/svc/svc
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "am" on the device, which has a very rudimentary
 # shell.
 #
diff --git a/cmds/telecom/telecom b/cmds/telecom/telecom
index 9efdcfd..a19036b 100755
--- a/cmds/telecom/telecom
+++ b/cmds/telecom/telecom
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "telecom" on the device
 #
 base=/system
diff --git a/cmds/uiautomator/cmds/uiautomator/uiautomator b/cmds/uiautomator/cmds/uiautomator/uiautomator
index 86a1dba..889c2b5 100755
--- a/cmds/uiautomator/cmds/uiautomator/uiautomator
+++ b/cmds/uiautomator/cmds/uiautomator/uiautomator
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 #
 # Copyright (C) 2012 The Android Open Source Project
 #
diff --git a/cmds/vr/vr b/cmds/vr/vr
index a279007..dbde02a 100755
--- a/cmds/vr/vr
+++ b/cmds/vr/vr
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "vr" on the device
 #
 base=/system
diff --git a/cmds/wm/wm b/cmds/wm/wm
index f7a5bc7..16d6bd6 100755
--- a/cmds/wm/wm
+++ b/cmds/wm/wm
@@ -1,3 +1,4 @@
+#!/system/bin/sh
 # Script to start "wm" on the device, which has a very rudimentary
 # shell.
 #
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8226e0f..d5d95fb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -22,6 +22,7 @@
 import android.annotation.DrawableRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -3900,7 +3901,7 @@
             final Bundle ex = mN.extras;
             updateBackgroundColor(contentView);
             bindNotificationHeader(contentView, p.ambient);
-            bindLargeIcon(contentView);
+            bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
             boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
             if (p.title != null) {
                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
@@ -4110,11 +4111,13 @@
             }
         }
 
-        private void bindLargeIcon(RemoteViews contentView) {
+        private void bindLargeIcon(RemoteViews contentView, boolean hideLargeIcon,
+                boolean alwaysShowReply) {
             if (mN.mLargeIcon == null && mN.largeIcon != null) {
                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
             }
-            if (mN.mLargeIcon != null) {
+            boolean showLargeIcon = mN.mLargeIcon != null && !hideLargeIcon;
+            if (showLargeIcon) {
                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
                 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
                 processLargeLegacyIcon(mN.mLargeIcon, contentView);
@@ -4122,32 +4125,45 @@
                 contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin);
                 contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin);
                 contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin);
-                // Bind the reply action
-                Action action = findReplyAction();
-                contentView.setViewVisibility(R.id.reply_icon_action, action != null
-                        ? View.VISIBLE
-                        : View.GONE);
+            }
+            // Bind the reply action
+            Action action = findReplyAction();
 
-                if (action != null) {
-                    int contrastColor = resolveContrastColor();
+            boolean actionVisible = action != null && (showLargeIcon || alwaysShowReply);
+            int replyId = showLargeIcon ? R.id.reply_icon_action : R.id.right_icon;
+            if (actionVisible) {
+                // We're only showing the icon as big if we're hiding the large icon
+                int contrastColor = resolveContrastColor();
+                int iconColor;
+                if (showLargeIcon) {
                     contentView.setDrawableTint(R.id.reply_icon_action,
                             true /* targetBackground */,
                             contrastColor, PorterDuff.Mode.SRC_ATOP);
-                    int iconColor = NotificationColorUtil.isColorLight(contrastColor)
-                            ? Color.BLACK : Color.WHITE;
-                    contentView.setDrawableTint(R.id.reply_icon_action,
-                            false /* targetBackground */,
-                            iconColor, PorterDuff.Mode.SRC_ATOP);
                     contentView.setOnClickPendingIntent(R.id.right_icon,
                             action.actionIntent);
-                    contentView.setOnClickPendingIntent(R.id.reply_icon_action,
-                            action.actionIntent);
                     contentView.setRemoteInputs(R.id.right_icon, action.mRemoteInputs);
-                    contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
-
+                    iconColor = NotificationColorUtil.isColorLight(contrastColor)
+                            ? Color.BLACK : Color.WHITE;
+                } else {
+                    contentView.setImageViewResource(R.id.right_icon,
+                            R.drawable.ic_reply_notification_large);
+                    contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+                    iconColor = contrastColor;
                 }
+                contentView.setDrawableTint(replyId,
+                        false /* targetBackground */,
+                        iconColor,
+                        PorterDuff.Mode.SRC_ATOP);
+                contentView.setOnClickPendingIntent(replyId,
+                        action.actionIntent);
+                contentView.setRemoteInputs(replyId, action.mRemoteInputs);
+            } else {
+                contentView.setRemoteInputs(R.id.right_icon, null);
             }
-            contentView.setViewVisibility(R.id.right_icon_container, mN.mLargeIcon != null
+            contentView.setViewVisibility(R.id.reply_icon_action, actionVisible && showLargeIcon
+                    ? View.VISIBLE
+                    : View.GONE);
+            contentView.setViewVisibility(R.id.right_icon_container, actionVisible || showLargeIcon
                     ? View.VISIBLE
                     : View.GONE);
         }
@@ -6055,18 +6071,12 @@
         protected void restoreFromExtras(Bundle extras) {
             super.restoreFromExtras(extras);
 
-            mMessages.clear();
-            mHistoricMessages.clear();
             mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
             Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
-            if (messages != null && messages instanceof Parcelable[]) {
-                mMessages = Message.getMessagesFromBundleArray(messages);
-            }
+            mMessages = Message.getMessagesFromBundleArray(messages);
             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
-            if (histMessages != null && histMessages instanceof Parcelable[]) {
-                mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
-            }
+            mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
         }
 
         /**
@@ -6074,38 +6084,34 @@
          */
         @Override
         public RemoteViews makeContentView(boolean increasedHeight) {
-            if (!increasedHeight) {
-                Message m = findLatestIncomingMessage();
-                CharSequence title = mConversationTitle != null
-                        ? mConversationTitle
-                        : (m == null) ? null : m.mSender;
-                CharSequence text = (m == null)
-                        ? null
-                        : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
-                return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(),
-                        mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
-            } else {
-                mBuilder.mOriginalActions = mBuilder.mActions;
-                mBuilder.mActions = new ArrayList<>();
-                RemoteViews remoteViews = makeBigContentView();
-                mBuilder.mActions = mBuilder.mOriginalActions;
-                mBuilder.mOriginalActions = null;
-                return remoteViews;
-            }
+            mBuilder.mOriginalActions = mBuilder.mActions;
+            mBuilder.mActions = new ArrayList<>();
+            RemoteViews remoteViews = makeBigContentView();
+            mBuilder.mActions = mBuilder.mOriginalActions;
+            mBuilder.mOriginalActions = null;
+            return remoteViews;
         }
 
         private Message findLatestIncomingMessage() {
-            for (int i = mMessages.size() - 1; i >= 0; i--) {
-                Message m = mMessages.get(i);
+            return findLatestIncomingMessage(mMessages);
+        }
+
+        /**
+         * @hide
+         */
+        @Nullable
+        public static Message findLatestIncomingMessage(
+                List<Message> messages) {
+            for (int i = messages.size() - 1; i >= 0; i--) {
+                Message m = messages.get(i);
                 // Incoming messages have a non-empty sender.
                 if (!TextUtils.isEmpty(m.mSender)) {
                     return m;
                 }
             }
-            if (!mMessages.isEmpty()) {
+            if (!messages.isEmpty()) {
                 // No incoming messages, fall back to outgoing message
-                return mMessages.get(mMessages.size() - 1);
+                return messages.get(messages.size() - 1);
             }
             return null;
         }
@@ -6115,118 +6121,82 @@
          */
         @Override
         public RemoteViews makeBigContentView() {
-            CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle)
+            CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
                     ? super.mBigContentTitle
                     : mConversationTitle;
-            boolean hasTitle = !TextUtils.isEmpty(title);
-
-            if (mMessages.size() == 1) {
-                // Special case for a single message: Use the big text style
-                // so the collapsed and expanded versions match nicely.
-                CharSequence bigTitle;
-                CharSequence text;
-                if (hasTitle) {
-                    bigTitle = title;
-                    text = makeMessageLine(mMessages.get(0), mBuilder);
-                } else {
-                    bigTitle = mMessages.get(0).mSender;
-                    text = mMessages.get(0).mText;
-                }
-                RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
-                        mBuilder.getBigTextLayoutResource(),
-                        mBuilder.mParams.reset().hasProgress(false).title(bigTitle).text(null));
-                BigTextStyle.applyBigTextContentView(mBuilder, contentView, text);
-                return contentView;
+            boolean isOneToOne = TextUtils.isEmpty(conversationTitle);
+            if (isOneToOne) {
+                // Let's add the conversationTitle in case we didn't have one before and all
+                // messages are from the same sender
+                conversationTitle = createConversationTitleFromMessages();
+            } else if (hasOnlyWhiteSpaceSenders()) {
+                isOneToOne = true;
             }
-
+            boolean hasTitle = !TextUtils.isEmpty(conversationTitle);
             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
                     mBuilder.getMessagingLayoutResource(),
-                    mBuilder.mParams.reset().hasProgress(false).title(title).text(null));
-
-            int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
-                    R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
-
-            // Make sure all rows are gone in case we reuse a view.
-            for (int rowId : rowIds) {
-                contentView.setViewVisibility(rowId, View.GONE);
-            }
-
-            int i=0;
-            contentView.setViewLayoutMarginBottomDimen(R.id.line1,
-                    hasTitle ? R.dimen.notification_messaging_spacing : 0);
-            contentView.setInt(R.id.notification_messaging, "setNumIndentLines",
-                    !mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2));
-
-            int contractedChildId = View.NO_ID;
-            Message contractedMessage = findLatestIncomingMessage();
-            int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
-                    - (rowIds.length - mMessages.size()));
-            while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
-                Message m = mHistoricMessages.get(firstHistoricMessage + i);
-                int rowId = rowIds[i];
-
-                contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
-
-                if (contractedMessage == m) {
-                    contractedChildId = rowId;
-                }
-
-                i++;
-            }
-
-            int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
-            while (firstMessage + i < mMessages.size() && i < rowIds.length) {
-                Message m = mMessages.get(firstMessage + i);
-                int rowId = rowIds[i];
-
-                contentView.setViewVisibility(rowId, View.VISIBLE);
-                contentView.setTextViewText(rowId, mBuilder.processTextSpans(
-                        makeMessageLine(m, mBuilder)));
-                mBuilder.setTextViewColorSecondary(contentView, rowId);
-
-                if (contractedMessage == m) {
-                    contractedChildId = rowId;
-                }
-
-                i++;
-            }
-            // Clear the remaining views for reapply. Ensures that historic message views can
-            // reliably be identified as being GONE and having non-null text.
-            while (i < rowIds.length) {
-                int rowId = rowIds[i];
-                contentView.setTextViewText(rowId, null);
-                i++;
-            }
-
-            // Record this here to allow transformation between the contracted and expanded views.
-            contentView.setInt(R.id.notification_messaging, "setContractedChildId",
-                    contractedChildId);
+                    mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
+                            .hideLargeIcon(isOneToOne).alwaysShowReply(true));
+            addExtras(mBuilder.mN.extras);
+            contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+                    mBuilder.resolveContrastColor());
+            contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+                    mBuilder.mN.mLargeIcon);
+            contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
+                    isOneToOne);
+            contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+                    mBuilder.mN.extras);
             return contentView;
         }
 
-        private CharSequence makeMessageLine(Message m, Builder builder) {
-            BidiFormatter bidi = BidiFormatter.getInstance();
-            SpannableStringBuilder sb = new SpannableStringBuilder();
-            boolean colorize = builder.isColorized();
-            TextAppearanceSpan colorSpan;
-            CharSequence messageName;
-            if (TextUtils.isEmpty(m.mSender)) {
-                CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
-                sb.append(bidi.unicodeWrap(replyName),
-                        makeFontColorSpan(colorize
-                                ? builder.getPrimaryTextColor()
-                                : mBuilder.resolveContrastColor()),
-                        0 /* flags */);
-            } else {
-                sb.append(bidi.unicodeWrap(m.mSender),
-                        makeFontColorSpan(colorize
-                                ? builder.getPrimaryTextColor()
-                                : Color.BLACK),
-                        0 /* flags */);
+        private boolean hasOnlyWhiteSpaceSenders() {
+            for (int i = 0; i < mMessages.size(); i++) {
+                Message m = mMessages.get(i);
+                CharSequence sender = m.getSender();
+                if (!isWhiteSpace(sender)) {
+                    return false;
+                }
             }
-            CharSequence text = m.mText == null ? "" : m.mText;
-            sb.append("  ").append(bidi.unicodeWrap(text));
-            return sb;
+            return true;
+        }
+
+        private boolean isWhiteSpace(CharSequence sender) {
+            if (TextUtils.isEmpty(sender)) {
+                return true;
+            }
+            if (sender.toString().matches("^\\s*$")) {
+                return true;
+            }
+            // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
+            // For the presentation that we had.
+            for (int i = 0; i < sender.length(); i++) {
+                char c = sender.charAt(i);
+                if (c != '\u200B') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private CharSequence createConversationTitleFromMessages() {
+            ArraySet<CharSequence> names = new ArraySet<>();
+            for (int i = 0; i < mMessages.size(); i++) {
+                Message m = mMessages.get(i);
+                CharSequence sender = m.getSender();
+                if (sender != null) {
+                    names.add(sender);
+                }
+            }
+            SpannableStringBuilder title = new SpannableStringBuilder();
+            int size = names.size();
+            for (int i = 0; i < size; i++) {
+                CharSequence name = names.valueAt(i);
+                if (!TextUtils.isEmpty(title)) {
+                    title.append(", ");
+                }
+                title.append(BidiFormatter.getInstance().unicodeWrap(name));
+            }
+            return title;
         }
 
         /**
@@ -6234,19 +6204,9 @@
          */
         @Override
         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
-            if (increasedHeight) {
-                return makeBigContentView();
-            }
-            Message m = findLatestIncomingMessage();
-            CharSequence title = mConversationTitle != null
-                    ? mConversationTitle
-                    : (m == null) ? null : m.mSender;
-            CharSequence text = (m == null)
-                    ? null
-                    : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
-            return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(),
-                    mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
+            RemoteViews remoteViews = makeBigContentView();
+            remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
+            return remoteViews;
         }
 
         private static TextAppearanceSpan makeFontColorSpan(int color) {
@@ -6394,7 +6354,15 @@
                 return bundles;
             }
 
-            static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+            /**
+             * @return A list of messages read from the bundles.
+             *
+             * @hide
+             */
+            public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+                if (bundles == null) {
+                    return new ArrayList<>();
+                }
                 List<Message> messages = new ArrayList<>(bundles.length);
                 for (int i = 0; i < bundles.length; i++) {
                     if (bundles[i] instanceof Bundle) {
@@ -8487,6 +8455,8 @@
         boolean ambient = false;
         CharSequence title;
         CharSequence text;
+        boolean hideLargeIcon;
+        public boolean alwaysShowReply;
 
         final StandardTemplateParams reset() {
             hasProgress = true;
@@ -8511,6 +8481,16 @@
             return this;
         }
 
+        final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) {
+            this.alwaysShowReply = alwaysShowReply;
+            return this;
+        }
+
+        final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
+            this.hideLargeIcon = hideLargeIcon;
+            return this;
+        }
+
         final StandardTemplateParams ambient(boolean ambient) {
             Preconditions.checkState(title == null && text == null, "must set ambient before text");
             this.ambient = ambient;
@@ -8527,7 +8507,6 @@
                 text = extras.getCharSequence(EXTRA_TEXT);
             }
             this.text = b.processLegacyText(text, ambient);
-
             return this;
         }
     }
diff --git a/core/java/android/app/slice/widget/SliceView.java b/core/java/android/app/slice/widget/SliceView.java
index c583562..cc13ba3 100644
--- a/core/java/android/app/slice/widget/SliceView.java
+++ b/core/java/android/app/slice/widget/SliceView.java
@@ -212,11 +212,15 @@
         validate(sliceUri);
         Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
         if (s != null) {
+            if (mObserver != null) {
+                getContext().getContentResolver().unregisterContentObserver(mObserver);
+            }
             mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
             if (isAttachedToWindow()) {
                 registerSlice(sliceUri);
             }
-            showSlice(s);
+            mCurrentSlice = s;
+            reinflate();
         }
         return s != null;
     }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index d7ecc81..8071e8b 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -619,6 +619,35 @@
      */
     public static final int NETID_UNSET = 0;
 
+    /**
+     * Private DNS Mode values.
+     *
+     * The "private_dns_mode" global setting stores a String value which is
+     * expected to be one of the following.
+     */
+
+    /**
+     * @hide
+     */
+    public static final String PRIVATE_DNS_MODE_OFF = "off";
+    /**
+     * @hide
+     */
+    public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
+    /**
+     * @hide
+     */
+    public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
+    /**
+     * The default Private DNS mode.
+     *
+     * This may change from release to release or may become dependent upon
+     * the capabilities of the underlying platform.
+     *
+     * @hide
+     */
+    public static final String PRIVATE_DNS_DEFAULT_MODE = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+
     private final IConnectivityManager mService;
     /**
      * A kludge to facilitate static access where a Context pointer isn't available, like in the
diff --git a/core/java/android/net/metrics/DefaultNetworkEvent.java b/core/java/android/net/metrics/DefaultNetworkEvent.java
index eb61c153..8ff8e4f 100644
--- a/core/java/android/net/metrics/DefaultNetworkEvent.java
+++ b/core/java/android/net/metrics/DefaultNetworkEvent.java
@@ -20,44 +20,72 @@
 
 import android.net.NetworkCapabilities;
 
+import com.android.internal.util.BitUtils;
+
+import java.util.StringJoiner;
+
 /**
  * An event recorded by ConnectivityService when there is a change in the default network.
  * {@hide}
  */
 public class DefaultNetworkEvent {
 
-    // The ID of the network that has become the new default or NETID_UNSET if none.
+    // The creation time in milliseconds of this DefaultNetworkEvent.
+    public final long creationTimeMs;
+    // The network ID of the network or NETID_UNSET if none.
     public int netId = NETID_UNSET;
-    // The list of transport types of the new default network, for example TRANSPORT_WIFI, as
-    // defined in NetworkCapabilities.java.
-    public int[] transportTypes = new int[0];
-    // The ID of the network that was the default before or NETID_UNSET if none.
-    public int prevNetId = NETID_UNSET;
-    // Whether the previous network had IPv4/IPv6 connectivity.
-    public boolean prevIPv4;
-    public boolean prevIPv6;
+    // The list of transport types, as defined in NetworkCapabilities.java.
+    public int transports;
+    // The list of transport types of the last previous default network.
+    public int previousTransports;
+    // Whether the network has IPv4/IPv6 connectivity.
+    public boolean ipv4;
+    public boolean ipv6;
+    // The initial network score when this network became the default network.
+    public int initialScore;
+    // The initial network score when this network stopped being the default network.
+    public int finalScore;
+    // The total duration in milliseconds this network was the default network.
+    public long durationMs;
+    // The total duration in milliseconds this network was the default network and was validated.
+    public long validatedMs;
+
+    public DefaultNetworkEvent(long timeMs) {
+        creationTimeMs = timeMs;
+    }
+
+    /** Update the durationMs of this DefaultNetworkEvent for the given current time. */
+    public void updateDuration(long timeMs) {
+        durationMs = timeMs - creationTimeMs;
+    }
 
     @Override
     public String toString() {
-        String prevNetwork = String.valueOf(prevNetId);
-        String newNetwork = String.valueOf(netId);
-        if (prevNetId != 0) {
-            prevNetwork += ":" + ipSupport();
+        StringJoiner j = new StringJoiner(", ", "DefaultNetworkEvent(", ")");
+        j.add("netId=" + netId);
+        for (int t : BitUtils.unpackBits(transports)) {
+            j.add(NetworkCapabilities.transportNameOf(t));
         }
-        if (netId != 0) {
-            newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
+        j.add("ip=" + ipSupport());
+        if (initialScore > 0) {
+            j.add("initial_score=" + initialScore);
         }
-        return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
+        if (finalScore > 0) {
+            j.add("final_score=" + finalScore);
+        }
+        j.add(String.format("duration=%.0fs", durationMs / 1000.0));
+        j.add(String.format("validation=%4.1f%%", (validatedMs * 100.0) / durationMs));
+        return j.toString();
     }
 
     private String ipSupport() {
-        if (prevIPv4 && prevIPv6) {
+        if (ipv4 && ipv6) {
             return "IPv4v6";
         }
-        if (prevIPv6) {
+        if (ipv6) {
             return "IPv6";
         }
-        if (prevIPv4) {
+        if (ipv4) {
             return "IPv4";
         }
         return "NONE";
diff --git a/core/java/android/os/ConfigUpdate.java b/core/java/android/os/ConfigUpdate.java
index 1396877..94a44ec 100644
--- a/core/java/android/os/ConfigUpdate.java
+++ b/core/java/android/os/ConfigUpdate.java
@@ -68,13 +68,6 @@
             = "android.intent.action.UPDATE_CT_LOGS";
 
     /**
-     * Update system wide timezone data.
-     * @hide
-     */
-    @SystemApi
-    public static final String ACTION_UPDATE_TZDATA = "android.intent.action.UPDATE_TZDATA";
-
-    /**
      * Update language detection model file.
      * @hide
      */
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 2e62eb6..2acf36f 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -16,11 +16,14 @@
 
 package android.os;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.content.Context;
 import android.util.Log;
 
 import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.TypedProperties;
 
 import dalvik.system.VMDebug;
@@ -2347,4 +2350,24 @@
     public static String getCaller() {
         return getCaller(Thread.currentThread().getStackTrace(), 0);
     }
+
+    /**
+     * Attach a library as a jvmti agent to the current runtime.
+     *
+     * @param library library containing the agent
+     * @param options options passed to the agent
+     *
+     * @throws IOException If the agent could not be attached
+     */
+    public static void attachJvmtiAgent(@NonNull String library, @Nullable String options)
+            throws IOException {
+        Preconditions.checkNotNull(library);
+        Preconditions.checkArgument(!library.contains("="));
+
+        if (options == null) {
+            VMDebug.attachAgent(library);
+        } else {
+            VMDebug.attachAgent(library + "=" + options);
+        }
+    }
 }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index c2cf3967..10adb5a 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -2020,8 +2020,6 @@
     @Deprecated
     static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
 
-    static native void clearFileDescriptor(FileDescriptor desc);
-
     /**
      * Read a byte value from the parcel at the current dataPosition().
      */
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 7f588ad..7556f09 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -683,7 +683,7 @@
                 throw new IllegalStateException("Already closed");
             }
             final int fd = getFd();
-            Parcel.clearFileDescriptor(mFd);
+            mFd.setInt$(-1);
             writeCommStatusAndClose(Status.DETACHED, null);
             mClosed = true;
             mGuard.close();
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 53d87ff..d5820b6 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -29,6 +29,8 @@
 import android.content.pm.ApplicationInfo;
 import android.net.TrafficStats;
 import android.net.Uri;
+import android.os.StrictMode.ThreadPolicy;
+import android.os.StrictMode.VmPolicy;
 import android.os.strictmode.CleartextNetworkViolation;
 import android.os.strictmode.ContentUriWithoutPermissionViolation;
 import android.os.strictmode.CustomViolation;
@@ -54,6 +56,7 @@
 import android.view.IWindowManager;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.HexDump;
@@ -1533,7 +1536,6 @@
 
             if (violationMaskSubset != 0) {
                 violationMaskSubset |= info.getViolationBit();
-                final int savedPolicyMask = getThreadPolicyMask();
 
                 final boolean justDropBox = (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
                 if (justDropBox) {
@@ -1544,29 +1546,8 @@
                     // isn't always super fast, despite the implementation
                     // in the ActivityManager trying to be mostly async.
                     dropboxViolationAsync(violationMaskSubset, info);
-                    return;
-                }
-
-                // Normal synchronous call to the ActivityManager.
-                try {
-                    // First, remove any policy before we call into the Activity Manager,
-                    // otherwise we'll infinite recurse as we try to log policy violations
-                    // to disk, thus violating policy, thus requiring logging, etc...
-                    // We restore the current policy below, in the finally block.
-                    setThreadPolicyMask(0);
-
-                    ActivityManager.getService()
-                            .handleApplicationStrictModeViolation(
-                                    RuntimeInit.getApplicationObject(), violationMaskSubset, info);
-                } catch (RemoteException e) {
-                    if (e instanceof DeadObjectException) {
-                        // System process is dead; ignore
-                    } else {
-                        Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
-                    }
-                } finally {
-                    // Restore the policy.
-                    setThreadPolicyMask(savedPolicyMask);
+                } else {
+                    handleApplicationStrictModeViolation(violationMaskSubset, info);
                 }
             }
 
@@ -1598,28 +1579,39 @@
 
         if (LOG_V) Log.d(TAG, "Dropboxing async; in-flight=" + outstanding);
 
-        new Thread("callActivityManagerForStrictModeDropbox") {
-            public void run() {
-                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-                try {
-                    IActivityManager am = ActivityManager.getService();
-                    if (am == null) {
-                        Log.d(TAG, "No activity manager; failed to Dropbox violation.");
-                    } else {
-                        am.handleApplicationStrictModeViolation(
-                                RuntimeInit.getApplicationObject(), violationMaskSubset, info);
-                    }
-                } catch (RemoteException e) {
-                    if (e instanceof DeadObjectException) {
-                        // System process is dead; ignore
-                    } else {
-                        Log.e(TAG, "RemoteException handling StrictMode violation", e);
-                    }
-                }
-                int outstanding = sDropboxCallsInFlight.decrementAndGet();
-                if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstanding);
+        BackgroundThread.getHandler().post(() -> {
+            handleApplicationStrictModeViolation(violationMaskSubset, info);
+            int outstandingInner = sDropboxCallsInFlight.decrementAndGet();
+            if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstandingInner);
+        });
+    }
+
+    private static void handleApplicationStrictModeViolation(int violationMaskSubset,
+            ViolationInfo info) {
+        final int oldMask = getThreadPolicyMask();
+        try {
+            // First, remove any policy before we call into the Activity Manager,
+            // otherwise we'll infinite recurse as we try to log policy violations
+            // to disk, thus violating policy, thus requiring logging, etc...
+            // We restore the current policy below, in the finally block.
+            setThreadPolicyMask(0);
+
+            IActivityManager am = ActivityManager.getService();
+            if (am == null) {
+                Log.w(TAG, "No activity manager; failed to Dropbox violation.");
+            } else {
+                am.handleApplicationStrictModeViolation(
+                        RuntimeInit.getApplicationObject(), violationMaskSubset, info);
             }
-        }.start();
+        } catch (RemoteException e) {
+            if (e instanceof DeadObjectException) {
+                // System process is dead; ignore
+            } else {
+                Log.e(TAG, "RemoteException handling StrictMode violation", e);
+            }
+        } finally {
+            setThreadPolicyMask(oldMask);
+        }
     }
 
     private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
@@ -1908,31 +1900,7 @@
         }
 
         if (penaltyDropbox && lastViolationTime == 0) {
-            // The violationMask, passed to ActivityManager, is a
-            // subset of the original StrictMode policy bitmask, with
-            // only the bit violated and penalty bits to be executed
-            // by the ActivityManagerService remaining set.
-            final int savedPolicyMask = getThreadPolicyMask();
-            try {
-                // First, remove any policy before we call into the Activity Manager,
-                // otherwise we'll infinite recurse as we try to log policy violations
-                // to disk, thus violating policy, thus requiring logging, etc...
-                // We restore the current policy below, in the finally block.
-                setThreadPolicyMask(0);
-
-                ActivityManager.getService()
-                        .handleApplicationStrictModeViolation(
-                                RuntimeInit.getApplicationObject(), violationMaskSubset, info);
-            } catch (RemoteException e) {
-                if (e instanceof DeadObjectException) {
-                    // System process is dead; ignore
-                } else {
-                    Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
-                }
-            } finally {
-                // Restore the policy.
-                setThreadPolicyMask(savedPolicyMask);
-            }
+            handleApplicationStrictModeViolation(violationMaskSubset, info);
         }
 
         if (penaltyDeath) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 433878e..2501f22 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9297,11 +9297,20 @@
         public static final String DEFAULT_DNS_SERVER = "default_dns_server";
 
         /**
-         * Whether to disable DNS over TLS (boolean)
+         * The requested Private DNS mode (string), and an accompanying specifier (string).
+         *
+         * Currently, the specifier holds the chosen provider name when the mode requests
+         * a specific provider. It may be used to store the provider name even when the
+         * mode changes so that temporarily disabling and re-enabling the specific
+         * provider mode does not necessitate retyping the provider hostname.
          *
          * @hide
          */
-        public static final String DNS_TLS_DISABLED = "dns_tls_disabled";
+        public static final String PRIVATE_DNS_MODE = "private_dns_mode";
+        /**
+         * @hide
+         */
+        public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
 
         /** {@hide} */
         public static final String
@@ -10387,7 +10396,9 @@
             DOCK_AUDIO_MEDIA_ENABLED,
             ENCODED_SURROUND_OUTPUT,
             LOW_POWER_MODE_TRIGGER_LEVEL,
-            BLUETOOTH_ON
+            BLUETOOTH_ON,
+            PRIVATE_DNS_MODE,
+            PRIVATE_DNS_SPECIFIER
         };
 
         /** @hide */
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 6a15ade..2a245d0 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -680,8 +680,8 @@
      *
      * @return The screen state to use while dozing, such as {@link Display#STATE_ON},
      * {@link Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND},
-     * or {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN} for the default
-     * behavior.
+     * {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
+     * for the default behavior.
      *
      * @see #setDozeScreenState
      * @hide For use by system UI components only.
@@ -700,12 +700,18 @@
      * perform transitions between states while dozing to conserve power and
      * achieve various effects.
      * </p><p>
-     * It is recommended that the state be set to {@link Display#STATE_DOZE_SUSPEND}
-     * once the dream has completely finished drawing and before it releases its wakelock
-     * to allow the display hardware to be fully suspended.  While suspended, the
-     * display will preserve its on-screen contents or hand off control to dedicated
-     * doze hardware if the devices supports it.  If the doze suspend state is
-     * used, the dream must make sure to set the mode back
+     * Some devices will have dedicated hardware ("Sidekick") to animate
+     * the display content while the CPU sleeps. If the dream and the hardware support
+     * this, {@link Display#STATE_ON_SUSPEND} or {@link Display#STATE_DOZE_SUSPEND}
+     * will switch control to the Sidekick.
+     * </p><p>
+     * If not using Sidekick, it is recommended that the state be set to
+     * {@link Display#STATE_DOZE_SUSPEND} once the dream has completely
+     * finished drawing and before it releases its wakelock
+     * to allow the display hardware to be fully suspended.  While suspended,
+     * the display will preserve its on-screen contents.
+     * </p><p>
+     * If the doze suspend state is used, the dream must make sure to set the mode back
      * to {@link Display#STATE_DOZE} or {@link Display#STATE_ON} before drawing again
      * since the display updates may be ignored and not seen by the user otherwise.
      * </p><p>
@@ -716,8 +722,8 @@
      *
      * @param state The screen state to use while dozing, such as {@link Display#STATE_ON},
      * {@link Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND},
-     * or {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN} for the default
-     * behavior.
+     * {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
+     * for the default behavior.
      *
      * @hide For use by system UI components only.
      */
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index e7c3f92..6a44cdb 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -294,11 +294,10 @@
 
     /**
      * Display state: The display is dozing in a suspended low power state; it is still
-     * on but is optimized for showing static system-provided content while the device
-     * is non-interactive.  This mode may be used to conserve even more power by allowing
-     * the hardware to stop applying frame buffer updates from the graphics subsystem or
-     * to take over the display and manage it autonomously to implement low power always-on
-     * display functionality.
+     * on but the CPU is not updating it. This may be used in one of two ways: to show
+     * static system-provided content while the device is non-interactive, or to allow
+     * a "Sidekick" compute resource to update the display. For this reason, the
+     * CPU must not control the display in this mode.
      *
      * @see #getState
      * @see android.os.PowerManager#isInteractive
@@ -313,6 +312,18 @@
      */
     public static final int STATE_VR = 5;
 
+    /**
+     * Display state: The display is in a suspended full power state; it is still
+     * on but the CPU is not updating it. This may be used in one of two ways: to show
+     * static system-provided content while the device is non-interactive, or to allow
+     * a "Sidekick" compute resource to update the display. For this reason, the
+     * CPU must not control the display in this mode.
+     *
+     * @see #getState
+     * @see android.os.PowerManager#isInteractive
+     */
+    public static final int STATE_ON_SUSPEND = 6;
+
     /* The color mode constants defined below must be kept in sync with the ones in
      * system/core/include/system/graphics-base.h */
 
@@ -994,7 +1005,7 @@
      * Gets the state of the display, such as whether it is on or off.
      *
      * @return The state of the display: one of {@link #STATE_OFF}, {@link #STATE_ON},
-     * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, or
+     * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, {@link #STATE_ON_SUSPEND}, or
      * {@link #STATE_UNKNOWN}.
      */
     public int getState() {
@@ -1113,6 +1124,8 @@
                 return "DOZE_SUSPEND";
             case STATE_VR:
                 return "VR";
+            case STATE_ON_SUSPEND:
+                return "ON_SUSPEND";
             default:
                 return Integer.toString(state);
         }
@@ -1120,11 +1133,11 @@
 
     /**
      * Returns true if display updates may be suspended while in the specified
-     * display power state.
+     * display power state. In SUSPEND states, updates are absolutely forbidden.
      * @hide
      */
     public static boolean isSuspendedState(int state) {
-        return state == STATE_OFF || state == STATE_DOZE_SUSPEND;
+        return state == STATE_OFF || state == STATE_DOZE_SUSPEND || state == STATE_ON_SUSPEND;
     }
 
     /**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index cd84147..5641009 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -295,6 +295,12 @@
     public static final int POWER_MODE_DOZE_SUSPEND = 3;
 
     /**
+     * Display power mode on: used while putting the screen into a suspended
+     * full power mode.  Use only with {@link SurfaceControl#setDisplayPowerMode}.
+     */
+    public static final int POWER_MODE_ON_SUSPEND = 4;
+
+    /**
      * A value for windowType used to indicate that the window should be omitted from screenshots
      * and display mirroring. A temporary workaround until we express such things with
      * the hierarchy.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c29a1da..eb5fc92 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1422,7 +1422,7 @@
          * this window is visible.
          * @hide
          */
-        @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+        @RequiresPermission(permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
         public static final int PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
 
         /**
@@ -1443,6 +1443,15 @@
         public static final int PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN = 0x00200000;
 
         /**
+         * Flag to indicate that this window should be considered a screen decoration similar to the
+         * nav bar and status bar. This will cause this window to affect the window insets reported
+         * to other windows when it is visible.
+         * @hide
+         */
+        @RequiresPermission(permission.STATUS_BAR_SERVICE)
+        public static final int PRIVATE_FLAG_IS_SCREEN_DECOR = 0x00400000;
+
+        /**
          * Control flags that are private to the platform.
          * @hide
          */
@@ -1526,7 +1535,11 @@
                 @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
                         equals = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
-                        name = "ACQUIRES_SLEEP_TOKEN")
+                        name = "ACQUIRES_SLEEP_TOKEN"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_IS_SCREEN_DECOR,
+                        equals = PRIVATE_FLAG_IS_SCREEN_DECOR,
+                        name = "IS_SCREEN_DECOR")
         })
         @TestApi
         public int privateFlags;
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 137e551..ebe3633 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -758,7 +758,8 @@
      * @param attrs The window layout parameters to be modified.  These values
      * are modified in-place.
      */
-    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs);
+    public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+            boolean hasStatusBarServicePermission);
 
     /**
      * After the window manager has computed the current configuration based
@@ -1172,13 +1173,13 @@
     /**
      * Called when layout of the windows is about to start.
      *
-     * @param isDefaultDisplay true if window is on {@link Display#DEFAULT_DISPLAY}.
+     * @param displayId Id of the display we are doing layout on.
      * @param displayWidth The current full width of the screen.
      * @param displayHeight The current full height of the screen.
      * @param displayRotation The current rotation being applied to the base window.
      * @param uiMode The current uiMode in configuration.
      */
-    public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
+    public void beginLayoutLw(int displayId, int displayWidth, int displayHeight,
                               int displayRotation, int uiMode);
 
     /**
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 26d2141..2779aa2 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.os.LocaleList;
 import android.view.View.OnClickListener;
 import android.view.textclassifier.TextClassifier.EntityType;
 
@@ -438,4 +439,31 @@
                     mLogType, mVersionInfo);
         }
     }
+
+    /**
+     * TextClassification optional input parameters.
+     */
+    public static final class Options {
+
+        private LocaleList mDefaultLocales;
+
+        /**
+         * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
+         *      the provided text. If no locale preferences exist, set this to null or an empty
+         *      locale list.
+         */
+        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+            mDefaultLocales = defaultLocales;
+            return this;
+        }
+
+        /**
+         * @return ordered list of locale preferences that can be used to disambiguate
+         *      the provided text.
+         */
+        @Nullable
+        public LocaleList getDefaultLocales() {
+            return mDefaultLocales;
+        }
+    }
 }
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 46dbd0e..07455c1d 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -56,23 +56,7 @@
      * No-op TextClassifier.
      * This may be used to turn off TextClassifier features.
      */
-    TextClassifier NO_OP = new TextClassifier() {
-
-        @Override
-        public TextSelection suggestSelection(
-                CharSequence text,
-                int selectionStartIndex,
-                int selectionEndIndex,
-                LocaleList defaultLocales) {
-            return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
-        }
-
-        @Override
-        public TextClassification classifyText(
-                CharSequence text, int startIndex, int endIndex, LocaleList defaultLocales) {
-            return TextClassification.EMPTY;
-        }
-    };
+    TextClassifier NO_OP = new TextClassifier() {};
 
     /**
      * Returns suggested text selection start and end indices, recognized entity types, and their
@@ -82,21 +66,34 @@
      *      by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
      * @param selectionStartIndex start index of the selected part of text
      * @param selectionEndIndex end index of the selected part of text
-     * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
-     *      the provided text. If no locale preferences exist, set this to null or an empty locale
-     *      list in which case the classifier will decide whether to use no locale information, use
-     *      a default locale, or use the system default.
+     * @param options optional input parameters
      *
      * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
      *      selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
      */
     @WorkerThread
     @NonNull
-    TextSelection suggestSelection(
+    default TextSelection suggestSelection(
             @NonNull CharSequence text,
             @IntRange(from = 0) int selectionStartIndex,
             @IntRange(from = 0) int selectionEndIndex,
-            @Nullable LocaleList defaultLocales);
+            @Nullable TextSelection.Options options) {
+        return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+    }
+
+    /**
+     * @see #suggestSelection(CharSequence, int, int, TextSelection.Options)
+     */
+    // TODO: Consider deprecating (b/68846316)
+    @WorkerThread
+    @NonNull
+    default TextSelection suggestSelection(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int selectionStartIndex,
+            @IntRange(from = 0) int selectionEndIndex,
+            @Nullable LocaleList defaultLocales) {
+        return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+    }
 
     /**
      * Classifies the specified text and returns a {@link TextClassification} object that can be
@@ -106,21 +103,34 @@
      *      by the sub sequence starting at startIndex and ending at endIndex)
      * @param startIndex start index of the text to classify
      * @param endIndex end index of the text to classify
-     * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
-     *      the provided text. If no locale preferences exist, set this to null or an empty locale
-     *      list in which case the classifier will decide whether to use no locale information, use
-     *      a default locale, or use the system default.
+     * @param options optional input parameters
      *
      * @throws IllegalArgumentException if text is null; startIndex is negative;
      *      endIndex is greater than text.length() or not greater than startIndex
      */
     @WorkerThread
     @NonNull
-    TextClassification classifyText(
+    default TextClassification classifyText(
             @NonNull CharSequence text,
             @IntRange(from = 0) int startIndex,
             @IntRange(from = 0) int endIndex,
-            @Nullable LocaleList defaultLocales);
+            @Nullable TextClassification.Options options) {
+        return TextClassification.EMPTY;
+    }
+
+    /**
+     * @see #classifyText(CharSequence, int, int, TextClassification.Options)
+     */
+    // TODO: Consider deprecating (b/68846316)
+    @WorkerThread
+    @NonNull
+    default TextClassification classifyText(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int startIndex,
+            @IntRange(from = 0) int endIndex,
+            @Nullable LocaleList defaultLocales) {
+        return TextClassification.EMPTY;
+    }
 
     /**
      * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 1c07be4..2799f2b 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -100,16 +100,24 @@
     @Override
     public TextSelection suggestSelection(
             @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
-            @Nullable LocaleList defaultLocales) {
+            @Nullable TextSelection.Options options) {
         validateInput(text, selectionStartIndex, selectionEndIndex);
         try {
             if (text.length() > 0) {
-                final SmartSelection smartSelection = getSmartSelection(defaultLocales);
+                final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
+                final SmartSelection smartSelection = getSmartSelection(locales);
                 final String string = text.toString();
-                final int[] startEnd = smartSelection.suggest(
-                        string, selectionStartIndex, selectionEndIndex);
-                final int start = startEnd[0];
-                final int end = startEnd[1];
+                final int start;
+                final int end;
+                if (getSettings().isDarkLaunch() && !options.isDarkLaunchAllowed()) {
+                    start = selectionStartIndex;
+                    end = selectionEndIndex;
+                } else {
+                    final int[] startEnd = smartSelection.suggest(
+                            string, selectionStartIndex, selectionEndIndex);
+                    start = startEnd[0];
+                    end = startEnd[1];
+                }
                 if (start <= end
                         && start >= 0 && end <= string.length()
                         && start <= selectionStartIndex && end >= selectionEndIndex) {
@@ -139,18 +147,27 @@
         }
         // Getting here means something went wrong, return a NO_OP result.
         return TextClassifier.NO_OP.suggestSelection(
-                text, selectionStartIndex, selectionEndIndex, defaultLocales);
+                text, selectionStartIndex, selectionEndIndex, options);
+    }
+
+    @Override
+    public TextSelection suggestSelection(
+            @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
+            @Nullable LocaleList defaultLocales) {
+        return suggestSelection(text, selectionStartIndex, selectionEndIndex,
+                new TextSelection.Options().setDefaultLocales(defaultLocales));
     }
 
     @Override
     public TextClassification classifyText(
             @NonNull CharSequence text, int startIndex, int endIndex,
-            @Nullable LocaleList defaultLocales) {
+            @Nullable TextClassification.Options options) {
         validateInput(text, startIndex, endIndex);
         try {
             if (text.length() > 0) {
                 final String string = text.toString();
-                SmartSelection.ClassificationResult[] results = getSmartSelection(defaultLocales)
+                final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
+                final SmartSelection.ClassificationResult[] results = getSmartSelection(locales)
                         .classifyText(string, startIndex, endIndex,
                                 getHintFlags(string, startIndex, endIndex));
                 if (results.length > 0) {
@@ -165,8 +182,15 @@
             Log.e(LOG_TAG, "Error getting text classification info.", t);
         }
         // Getting here means something went wrong, return a NO_OP result.
-        return TextClassifier.NO_OP.classifyText(
-                text, startIndex, endIndex, defaultLocales);
+        return TextClassifier.NO_OP.classifyText(text, startIndex, endIndex, options);
+    }
+
+    @Override
+    public TextClassification classifyText(
+            @NonNull CharSequence text, int startIndex, int endIndex,
+            @Nullable LocaleList defaultLocales) {
+        return classifyText(text, startIndex, endIndex,
+                new TextClassification.Options().setDefaultLocales(defaultLocales));
     }
 
     @Override
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index 11ebe83..0a67954 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -19,6 +19,8 @@
 import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.LocaleList;
 import android.view.textclassifier.TextClassifier.EntityType;
 
 import com.android.internal.util.Preconditions;
@@ -181,4 +183,55 @@
                     mStartIndex, mEndIndex, mEntityConfidence, mLogSource, mVersionInfo);
         }
     }
+
+    /**
+     * TextSelection optional input parameters.
+     */
+    public static final class Options {
+
+        private LocaleList mDefaultLocales;
+        private boolean mDarkLaunchAllowed;
+
+        /**
+         * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
+         *      the provided text. If no locale preferences exist, set this to null or an empty
+         *      locale list.
+         */
+        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+            mDefaultLocales = defaultLocales;
+            return this;
+        }
+
+        /**
+         * @return ordered list of locale preferences that can be used to disambiguate
+         *      the provided text.
+         */
+        @Nullable
+        public LocaleList getDefaultLocales() {
+            return mDefaultLocales;
+        }
+
+        /**
+         * @param allowed whether or not the TextClassifier should return selection suggestions
+         *      when "dark launched". When a TextClassifier is dark launched, it can suggest
+         *      selection changes that should not be used to actually change the user's selection.
+         *      Instead, the suggested selection is logged, compared with the user's selection
+         *      interaction, and used to generate quality metrics for the TextClassifier.
+         *
+         * @hide
+         */
+        public void setDarkLaunchAllowed(boolean allowed) {
+            mDarkLaunchAllowed = allowed;
+        }
+
+        /**
+         * Returns true if the TextClassifier should return selection suggestions when
+         * "dark launched". Otherwise, returns false.
+         *
+         * @hide
+         */
+        public boolean isDarkLaunchAllowed() {
+            return mDarkLaunchAllowed;
+        }
+    }
 }
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 5e22650..71854ae 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -20,10 +20,12 @@
 import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.annotation.WorkerThread;
+import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.AsyncTask;
+import android.os.Build;
 import android.os.LocaleList;
 import android.text.Layout;
 import android.text.Selection;
@@ -81,6 +83,7 @@
         mEditor = Preconditions.checkNotNull(editor);
         mTextView = mEditor.getTextView();
         mTextClassificationHelper = new TextClassificationHelper(
+                mTextView.getContext(),
                 mTextView.getTextClassifier(),
                 getText(mTextView),
                 0, 1, mTextView.getTextLocales());
@@ -385,6 +388,7 @@
 
     private void resetTextClassificationHelper() {
         mTextClassificationHelper.init(
+                mTextView.getContext(),
                 mTextView.getTextClassifier(),
                 getText(mTextView),
                 mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
@@ -787,6 +791,7 @@
 
         private static final int TRIM_DELTA = 120;  // characters
 
+        private Context mContext;
         private TextClassifier mTextClassifier;
 
         /** The original TextView text. **/
@@ -795,7 +800,10 @@
         private int mSelectionStart;
         /** End index relative to mText. */
         private int mSelectionEnd;
-        private LocaleList mLocales;
+
+        private final TextSelection.Options mSelectionOptions = new TextSelection.Options();
+        private final TextClassification.Options mClassificationOptions =
+                new TextClassification.Options();
 
         /** Trimmed text starting from mTrimStart in mText. */
         private CharSequence mTrimmedText;
@@ -816,21 +824,24 @@
         /** Whether the TextClassifier has been initialized. */
         private boolean mHot;
 
-        TextClassificationHelper(TextClassifier textClassifier,
+        TextClassificationHelper(Context context, TextClassifier textClassifier,
                 CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
-            init(textClassifier, text, selectionStart, selectionEnd, locales);
+            init(context, textClassifier, text, selectionStart, selectionEnd, locales);
         }
 
         @UiThread
-        public void init(TextClassifier textClassifier,
+        public void init(Context context, TextClassifier textClassifier,
                 CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
+            mContext = Preconditions.checkNotNull(context);
             mTextClassifier = Preconditions.checkNotNull(textClassifier);
             mText = Preconditions.checkNotNull(text).toString();
             mLastClassificationText = null; // invalidate.
             Preconditions.checkArgument(selectionEnd > selectionStart);
             mSelectionStart = selectionStart;
             mSelectionEnd = selectionEnd;
-            mLocales = locales;
+            mClassificationOptions.setDefaultLocales(locales);
+            mSelectionOptions.setDefaultLocales(locales)
+                    .setDarkLaunchAllowed(true);
         }
 
         @WorkerThread
@@ -843,8 +854,16 @@
         public SelectionResult suggestSelection() {
             mHot = true;
             trimText();
-            final TextSelection selection = mTextClassifier.suggestSelection(
-                    mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
+            final TextSelection selection;
+            if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
+                selection = mTextClassifier.suggestSelection(
+                        mTrimmedText, mRelativeStart, mRelativeEnd, mSelectionOptions);
+            } else {
+                // Use old APIs.
+                selection = mTextClassifier.suggestSelection(
+                        mTrimmedText, mRelativeStart, mRelativeEnd,
+                        mSelectionOptions.getDefaultLocales());
+            }
             // Do not classify new selection boundaries if TextClassifier should be dark launched.
             if (!mTextClassifier.getSettings().isDarkLaunch()) {
                 mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
@@ -874,20 +893,28 @@
             if (!Objects.equals(mText, mLastClassificationText)
                     || mSelectionStart != mLastClassificationSelectionStart
                     || mSelectionEnd != mLastClassificationSelectionEnd
-                    || !Objects.equals(mLocales, mLastClassificationLocales)) {
+                    || !Objects.equals(
+                            mClassificationOptions.getDefaultLocales(),
+                            mLastClassificationLocales)) {
 
                 mLastClassificationText = mText;
                 mLastClassificationSelectionStart = mSelectionStart;
                 mLastClassificationSelectionEnd = mSelectionEnd;
-                mLastClassificationLocales = mLocales;
+                mLastClassificationLocales = mClassificationOptions.getDefaultLocales();
 
                 trimText();
+                final TextClassification classification;
+                if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
+                    classification = mTextClassifier.classifyText(
+                            mTrimmedText, mRelativeStart, mRelativeEnd, mClassificationOptions);
+                } else {
+                    // Use old APIs.
+                    classification = mTextClassifier.classifyText(
+                            mTrimmedText, mRelativeStart, mRelativeEnd,
+                            mClassificationOptions.getDefaultLocales());
+                }
                 mLastClassificationResult = new SelectionResult(
-                        mSelectionStart,
-                        mSelectionEnd,
-                        mTextClassifier.classifyText(
-                                mTrimmedText, mRelativeStart, mRelativeEnd, mLocales),
-                        selection);
+                        mSelectionStart, mSelectionEnd, classification, selection);
 
             }
             return mLastClassificationResult;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 0535ebe..2ec64a5 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -9626,7 +9626,8 @@
     }
 
     public boolean isScreenOn(int state) {
-        return state == Display.STATE_ON || state == Display.STATE_VR;
+        return state == Display.STATE_ON || state == Display.STATE_VR
+            || state == Display.STATE_ON_SUSPEND;
     }
 
     public boolean isScreenOff(int state) {
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 7870333..09f7282 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -176,8 +176,4 @@
         }
         return false;
     }
-
-    public int getLayoutHeight() {
-        return getLayout().getHeight();
-    }
 }
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
new file mode 100644
index 0000000..792f921
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -0,0 +1,393 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {
+    private static Pools.SimplePool<MessagingGroup> sInstancePool
+            = new Pools.SynchronizedPool<>(10);
+    private MessagingLinearLayout mMessageContainer;
+    private ImageFloatingTextView mSenderName;
+    private ImageView mAvatarView;
+    private String mAvatarSymbol = "";
+    private int mLayoutColor;
+    private CharSequence mAvatarName = "";
+    private Icon mAvatarIcon;
+    private ColorFilter mMessageBackgroundFilter;
+    private int mTextColor;
+    private List<MessagingMessage> mMessages;
+    private ArrayList<MessagingMessage> mAddedMessages = new ArrayList<>();
+    private boolean mFirstLayout;
+    private boolean mIsHidingAnimated;
+
+    public MessagingGroup(@NonNull Context context) {
+        super(context);
+    }
+
+    public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mMessageContainer = findViewById(R.id.group_message_container);
+        mSenderName = findViewById(R.id.message_name);
+        mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+        mAvatarView = findViewById(R.id.message_icon);
+    }
+
+    public void setSender(CharSequence sender) {
+        if (sender == null) {
+            mAvatarView.setVisibility(GONE);
+            mSenderName.setVisibility(GONE);
+            setGravity(Gravity.END);
+            mMessageBackgroundFilter = new PorterDuffColorFilter(mLayoutColor,
+                    PorterDuff.Mode.SRC_ATOP);
+            mTextColor = NotificationColorUtil.isColorLight(mLayoutColor) ? getNormalTextColor()
+                    : Color.WHITE;
+        } else {
+            mSenderName.setText(sender);
+            mAvatarView.setVisibility(VISIBLE);
+            mSenderName.setVisibility(VISIBLE);
+            setGravity(Gravity.START);
+            mMessageBackgroundFilter = null;
+            mTextColor = getNormalTextColor();
+        }
+    }
+
+    private int getNormalTextColor() {
+        return mContext.getColor(R.color.notification_primary_text_color_light);
+    }
+
+    public void setAvatar(Icon icon) {
+        mAvatarIcon = icon;
+        mAvatarView.setImageIcon(icon);
+        mAvatarSymbol = "";
+        mLayoutColor = 0;
+        mAvatarName = "";
+    }
+
+    static MessagingGroup createGroup(MessagingLinearLayout layout) {;
+        MessagingGroup createdGroup = sInstancePool.acquire();
+        if (createdGroup == null) {
+            createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
+                    R.layout.notification_template_messaging_group, layout,
+                    false);
+            createdGroup.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+        }
+        layout.addView(createdGroup);
+        return createdGroup;
+    }
+
+    public void removeMessage(MessagingMessage messagingMessage) {
+        mMessageContainer.removeView(messagingMessage);
+        Runnable recycleRunnable = () -> {
+            mMessageContainer.removeTransientView(messagingMessage);
+            messagingMessage.recycle();
+            if (mMessageContainer.getChildCount() == 0
+                    && mMessageContainer.getTransientViewCount() == 0) {
+                ViewParent parent = getParent();
+                if (parent instanceof ViewGroup) {
+                    ((ViewGroup) parent).removeView(MessagingGroup.this);
+                }
+                setAvatar(null);
+                mAvatarView.setAlpha(1.0f);
+                mAvatarView.setTranslationY(0.0f);
+                mSenderName.setAlpha(1.0f);
+                mSenderName.setTranslationY(0.0f);
+                sInstancePool.release(MessagingGroup.this);
+            }
+        };
+        if (isShown()) {
+            mMessageContainer.addTransientView(messagingMessage, 0);
+            performRemoveAnimation(messagingMessage, recycleRunnable);
+            if (mMessageContainer.getChildCount() == 0) {
+                removeGroupAnimated(null);
+            }
+        } else {
+            recycleRunnable.run();
+        }
+
+    }
+
+    private void removeGroupAnimated(Runnable endAction) {
+        MessagingPropertyAnimator.fadeOut(mAvatarView, null);
+        MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView,
+                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+        MessagingPropertyAnimator.fadeOut(mSenderName, null);
+        MessagingPropertyAnimator.startLocalTranslationTo(mSenderName,
+                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+        boolean endActionTriggered = false;
+        for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+            View child = mMessageContainer.getChildAt(i);
+            if (child.getVisibility() == View.GONE) {
+                continue;
+            }
+            final ViewGroup.LayoutParams lp = child.getLayoutParams();
+            if (lp instanceof MessagingLinearLayout.LayoutParams
+                    && ((MessagingLinearLayout.LayoutParams) lp).hide
+                    && !((MessagingLinearLayout.LayoutParams) lp).visibleBefore) {
+                continue;
+            }
+            Runnable childEndAction = endActionTriggered ? null : endAction;
+            performRemoveAnimation(child, childEndAction);
+            endActionTriggered = true;
+        }
+        if (!endActionTriggered && endAction != null) {
+            endAction.run();
+        }
+    }
+
+    public void performRemoveAnimation(View message,
+            Runnable recycleRunnable) {
+        MessagingPropertyAnimator.fadeOut(message, recycleRunnable);
+        MessagingPropertyAnimator.startLocalTranslationTo(message,
+                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+    }
+
+    public CharSequence getSenderName() {
+        return mSenderName.getText();
+    }
+
+    public void setSenderVisible(boolean visible) {
+        mSenderName.setVisibility(visible ? VISIBLE : GONE);
+    }
+
+    public static void dropCache() {
+        sInstancePool = new Pools.SynchronizedPool<>(10);
+    }
+
+    @Override
+    public int getMeasuredType() {
+        boolean hasNormal = false;
+        for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+            View child = mMessageContainer.getChildAt(i);
+            if (child instanceof MessagingLinearLayout.MessagingChild) {
+                int type = ((MessagingLinearLayout.MessagingChild) child).getMeasuredType();
+                if (type == MEASURED_TOO_SMALL) {
+                    if (hasNormal) {
+                        return MEASURED_SHORTENED;
+                    } else {
+                        return MEASURED_TOO_SMALL;
+                    }
+                } else if (type == MEASURED_SHORTENED) {
+                    return MEASURED_SHORTENED;
+                } else {
+                    hasNormal = true;
+                }
+            }
+        }
+        return MEASURED_NORMAL;
+    }
+
+    @Override
+    public int getConsumedLines() {
+        int result = 0;
+        for (int i = 0; i < mMessageContainer.getChildCount(); i++) {
+            View child = mMessageContainer.getChildAt(i);
+            if (child instanceof MessagingLinearLayout.MessagingChild) {
+                result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
+            }
+        }
+        // A group is usually taking up quite some space with the padding and the name, let's add 1
+        return result + 1;
+    }
+
+    @Override
+    public void setMaxDisplayedLines(int lines) {
+        mMessageContainer.setMaxDisplayedLines(lines);
+    }
+
+    @Override
+    public void hideAnimated() {
+        setIsHidingAnimated(true);
+        removeGroupAnimated(() -> setIsHidingAnimated(false));
+    }
+
+    @Override
+    public boolean isHidingAnimated() {
+        return mIsHidingAnimated;
+    }
+
+    private void setIsHidingAnimated(boolean isHiding) {
+        ViewParent parent = getParent();
+        mIsHidingAnimated = isHiding;
+        invalidate();
+        if (parent instanceof ViewGroup) {
+            ((ViewGroup) parent).invalidate();
+        }
+    }
+
+    public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol,
+            int layoutColor) {
+        if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol)
+                && layoutColor == mLayoutColor) {
+            return mAvatarIcon;
+        }
+        return null;
+    }
+
+    public void setCreatedAvatar(Icon cachedIcon, CharSequence avatarName, String avatarSymbol,
+            int layoutColor) {
+        if (!mAvatarName.equals(avatarName) || !mAvatarSymbol.equals(avatarSymbol)
+                || layoutColor != mLayoutColor) {
+            setAvatar(cachedIcon);
+            mAvatarSymbol = avatarSymbol;
+            mLayoutColor = layoutColor;
+            mAvatarName = avatarName;
+        }
+    }
+
+    public void setLayoutColor(int layoutColor) {
+        mLayoutColor = layoutColor;
+    }
+
+    public void setMessages(List<MessagingMessage> group) {
+        // Let's now make sure all children are added and in the correct order
+        for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
+            MessagingMessage message = group.get(messageIndex);
+            if (message.getGroup() != this) {
+                message.setMessagingGroup(this);
+                ViewParent parent = mMessageContainer.getParent();
+                if (parent instanceof ViewGroup) {
+                    ((ViewGroup) parent).removeView(message);
+                }
+                mMessageContainer.addView(message, messageIndex);
+                mAddedMessages.add(message);
+            }
+            if (messageIndex != mMessageContainer.indexOfChild(message)) {
+                mMessageContainer.removeView(message);
+                mMessageContainer.addView(message, messageIndex);
+            }
+            // Let's make sure the message color is correct
+            Drawable targetDrawable = message.getBackground();
+
+            if (targetDrawable != null) {
+                targetDrawable.mutate().setColorFilter(mMessageBackgroundFilter);
+            }
+            message.setTextColor(mTextColor);
+        }
+        mMessages = group;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (!mAddedMessages.isEmpty()) {
+            getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    for (MessagingMessage message : mAddedMessages) {
+                        if (!message.isShown()) {
+                            continue;
+                        }
+                        MessagingPropertyAnimator.fadeIn(message);
+                        if (!mFirstLayout) {
+                            MessagingPropertyAnimator.startLocalTranslationFrom(message,
+                                    message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN);
+                        }
+                    }
+                    mAddedMessages.clear();
+                    getViewTreeObserver().removeOnPreDrawListener(this);
+                    return true;
+                }
+            });
+        }
+        mFirstLayout = false;
+    }
+
+    /**
+     * Calculates the group compatibility between this and another group.
+     *
+     * @param otherGroup the other group to compare it with
+     *
+     * @return 0 if the groups are totally incompatible or 1 + the number of matching messages if
+     *         they match.
+     */
+    public int calculateGroupCompatibility(MessagingGroup otherGroup) {
+        if (TextUtils.equals(getSenderName(),otherGroup.getSenderName())) {
+            int result = 1;
+            for (int i = 0; i < mMessages.size() && i < otherGroup.mMessages.size(); i++) {
+                MessagingMessage ownMessage = mMessages.get(mMessages.size() - 1 - i);
+                MessagingMessage otherMessage = otherGroup.mMessages.get(
+                        otherGroup.mMessages.size() - 1 - i);
+                if (!ownMessage.sameAs(otherMessage)) {
+                    return result;
+                }
+                result++;
+            }
+            return result;
+        }
+        return 0;
+    }
+
+    public View getSender() {
+        return mSenderName;
+    }
+
+    public View getAvatar() {
+        return mAvatarView;
+    }
+
+    public MessagingLinearLayout getMessageContainer() {
+        return mMessageContainer;
+    }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
new file mode 100644
index 0000000..2acdc01
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -0,0 +1,444 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.RemotableViewMethod;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
+ * messages and adapts the layout accordingly.
+ */
+@RemoteViews.RemoteView
+public class MessagingLayout extends FrameLayout {
+
+    private static final float COLOR_SHIFT_AMOUNT = 60;
+    private static final Consumer<MessagingMessage> REMOVE_MESSAGE
+            = MessagingMessage::removeMessage;
+    public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
+    public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+    public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+    public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR
+            = new MessagingPropertyAnimator();
+    private List<MessagingMessage> mMessages = new ArrayList<>();
+    private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
+    private MessagingLinearLayout mMessagingLinearLayout;
+    private View mContractedMessage;
+    private boolean mShowHistoricMessages;
+    private ArrayList<MessagingGroup> mGroups = new ArrayList<>();
+    private TextView mTitleView;
+    private int mLayoutColor;
+    private int mAvatarSize;
+    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private Paint mTextPaint = new Paint();
+    private CharSequence mConversationTitle;
+    private Icon mLargeIcon;
+    private boolean mIsOneToOne;
+    private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
+
+    public MessagingLayout(@NonNull Context context) {
+        super(context);
+    }
+
+    public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mMessagingLinearLayout = findViewById(R.id.notification_messaging);
+        mMessagingLinearLayout.setMessagingLayout(this);
+        // We still want to clip, but only on the top, since views can temporarily out of bounds
+        // during transitions.
+        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+        Rect rect = new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
+        mMessagingLinearLayout.setClipBounds(rect);
+        mTitleView = findViewById(R.id.title);
+        mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+        mTextPaint.setTextAlign(Paint.Align.CENTER);
+        mTextPaint.setAntiAlias(true);
+    }
+
+    @RemotableViewMethod
+    public void setLargeIcon(Icon icon) {
+        mLargeIcon = icon;
+    }
+
+    @RemotableViewMethod
+    public void setData(Bundle extras) {
+        Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+        List<Notification.MessagingStyle.Message> newMessages
+                = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
+        Parcelable[] histMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
+        List<Notification.MessagingStyle.Message> newHistoricMessages
+                = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
+        mConversationTitle = null;
+        TextView headerText = findViewById(R.id.header_text);
+        if (headerText != null) {
+            mConversationTitle = headerText.getText();
+        }
+        bind(newMessages, newHistoricMessages);
+    }
+
+    private void bind(List<Notification.MessagingStyle.Message> newMessages,
+            List<Notification.MessagingStyle.Message> newHistoricMessages) {
+
+        List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
+                true /* isHistoric */);
+        List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
+        addMessagesToGroups(historicMessages, messages);
+
+        // Let's remove the remaining messages
+        mMessages.forEach(REMOVE_MESSAGE);
+        mHistoricMessages.forEach(REMOVE_MESSAGE);
+
+        mMessages = messages;
+        mHistoricMessages = historicMessages;
+
+        updateContractedMessage();
+        updateHistoricMessageVisibility();
+        updateTitleAndNamesDisplay();
+    }
+
+    private void updateTitleAndNamesDisplay() {
+        ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
+        ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
+        for (int i = 0; i < mGroups.size(); i++) {
+            MessagingGroup group = mGroups.get(i);
+            CharSequence senderName = group.getSenderName();
+            if (TextUtils.isEmpty(senderName)) {
+                continue;
+            }
+            boolean visible = !mIsOneToOne;
+            group.setSenderVisible(visible);
+            if ((visible || mLargeIcon == null) && !uniqueNames.containsKey(senderName)) {
+                char c = senderName.charAt(0);
+                if (uniqueCharacters.containsKey(c)) {
+                    // this character was already used, lets make it more unique. We first need to
+                    // resolve the existing character if it exists
+                    CharSequence existingName = uniqueCharacters.get(c);
+                    if (existingName != null) {
+                        uniqueNames.put(existingName, findNameSplit((String) existingName));
+                        uniqueCharacters.put(c, null);
+                    }
+                    uniqueNames.put(senderName, findNameSplit((String) senderName));
+                } else {
+                    uniqueNames.put(senderName, Character.toString(c));
+                    uniqueCharacters.put(c, senderName);
+                }
+            }
+        }
+
+        // Now that we have the correct symbols, let's look what we have cached
+        ArrayMap<CharSequence, Icon> cachedAvatars = new ArrayMap<>();
+        for (int i = 0; i < mGroups.size(); i++) {
+            // Let's now set the avatars
+            MessagingGroup group = mGroups.get(i);
+            CharSequence senderName = group.getSenderName();
+            if (TextUtils.isEmpty(senderName) || (mIsOneToOne && mLargeIcon != null)) {
+                continue;
+            }
+            String symbol = uniqueNames.get(senderName);
+            Icon cachedIcon = group.getAvatarSymbolIfMatching(senderName,
+                    symbol, mLayoutColor);
+            if (cachedIcon != null) {
+                cachedAvatars.put(senderName, cachedIcon);
+            }
+        }
+
+        for (int i = 0; i < mGroups.size(); i++) {
+            // Let's now set the avatars
+            MessagingGroup group = mGroups.get(i);
+            CharSequence senderName = group.getSenderName();
+            if (TextUtils.isEmpty(senderName)) {
+                continue;
+            }
+            if (mIsOneToOne && mLargeIcon != null) {
+                group.setAvatar(mLargeIcon);
+            } else {
+                Icon cachedIcon = cachedAvatars.get(senderName);
+                if (cachedIcon == null) {
+                    cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
+                            mLayoutColor);
+                    cachedAvatars.put(senderName, cachedIcon);
+                }
+                group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
+                        mLayoutColor);
+            }
+        }
+    }
+
+    public Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
+        Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        float radius = mAvatarSize / 2.0f;
+        int color = findColor(senderName, layoutColor);
+        mPaint.setColor(color);
+        canvas.drawCircle(radius, radius, radius, mPaint);
+        boolean needDarkText  = ColorUtils.calculateLuminance(color) > 0.5f;
+        mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
+        mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.75f : mAvatarSize * 0.4f);
+        int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2)) ;
+        canvas.drawText(symbol, radius, yPos, mTextPaint);
+        return Icon.createWithBitmap(bitmap);
+    }
+
+    private int findColor(CharSequence senderName, int layoutColor) {
+        double luminance = NotificationColorUtil.calculateLuminance(layoutColor);
+        float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
+
+        // we need to offset the range if the luminance is too close to the borders
+        shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
+        shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
+        return NotificationColorUtil.getShiftedColor(layoutColor,
+                (int) (shift * COLOR_SHIFT_AMOUNT));
+    }
+
+    private String findNameSplit(String existingName) {
+        String[] split = existingName.split(" ");
+        if (split.length > 1) {
+            return Character.toString(split[0].charAt(0))
+                    + Character.toString(split[1].charAt(0));
+        }
+        return existingName.substring(0, 1);
+    }
+
+    @RemotableViewMethod
+    public void setLayoutColor(int color) {
+        mLayoutColor = color;
+    }
+
+    @RemotableViewMethod
+    public void setIsOneToOne(boolean oneToOne) {
+        mIsOneToOne = oneToOne;
+    }
+
+    private void addMessagesToGroups(List<MessagingMessage> historicMessages,
+            List<MessagingMessage> messages) {
+        // Let's first find our groups!
+        List<List<MessagingMessage>> groups = new ArrayList<>();
+        List<CharSequence> senders = new ArrayList<>();
+
+        // Lets first find the groups
+        findGroups(historicMessages, messages, groups, senders);
+
+        // Let's now create the views and reorder them accordingly
+        createGroupViews(groups, senders);
+    }
+
+    private void createGroupViews(List<List<MessagingMessage>> groups, List<CharSequence> senders) {
+        mGroups.clear();
+        for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
+            List<MessagingMessage> group = groups.get(groupIndex);
+            MessagingGroup newGroup = null;
+            // we'll just take the first group that exists or create one there is none
+            for (int messageIndex = group.size() - 1; messageIndex >= 0; messageIndex--) {
+                MessagingMessage message = group.get(messageIndex);
+                newGroup = message.getGroup();
+                if (newGroup != null) {
+                    break;
+                }
+            }
+            if (newGroup == null) {
+                newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
+                mAddedGroups.add(newGroup);
+            }
+            newGroup.setLayoutColor(mLayoutColor);
+            newGroup.setSender(senders.get(groupIndex));
+            mGroups.add(newGroup);
+
+            if (mMessagingLinearLayout.indexOfChild(newGroup) != groupIndex) {
+                mMessagingLinearLayout.removeView(newGroup);
+                mMessagingLinearLayout.addView(newGroup, groupIndex);
+            }
+            newGroup.setMessages(group);
+        }
+    }
+
+    private void findGroups(List<MessagingMessage> historicMessages,
+            List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
+            List<CharSequence> senders) {
+        CharSequence currentSender = null;
+        List<MessagingMessage> currentGroup = null;
+        int histSize = historicMessages.size();
+        for (int i = 0; i < histSize + messages.size(); i++) {
+            MessagingMessage message;
+            if (i < histSize) {
+                message = historicMessages.get(i);
+            } else {
+                message = messages.get(i - histSize);
+            }
+            boolean isNewGroup = currentGroup == null;
+            CharSequence sender = message.getMessage().getSender();
+            isNewGroup |= !TextUtils.equals(sender, currentSender);
+            if (isNewGroup) {
+                currentGroup = new ArrayList<>();
+                groups.add(currentGroup);
+                senders.add(sender);
+                currentSender = sender;
+            }
+            currentGroup.add(message);
+        }
+    }
+
+    private void updateContractedMessage() {
+        for (int i = mMessages.size() - 1; i >= 0; i--) {
+            MessagingMessage m = mMessages.get(i);
+            // Incoming messages have a non-empty sender.
+            if (!TextUtils.isEmpty(m.getMessage().getSender())) {
+                mContractedMessage = m;
+                return;
+            }
+        }
+        if (!mMessages.isEmpty()) {
+            // No incoming messages, fall back to outgoing message
+            mContractedMessage = mMessages.get(mMessages.size() - 1);
+            return;
+        }
+        mContractedMessage = null;
+    }
+
+    /**
+     * Creates new messages, reusing existing ones if they are available.
+     *
+     * @param newMessages the messages to parse.
+     */
+    private List<MessagingMessage> createMessages(
+            List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+        List<MessagingMessage> result = new ArrayList<>();;
+        for (int i = 0; i < newMessages.size(); i++) {
+            Notification.MessagingStyle.Message m = newMessages.get(i);
+            MessagingMessage message = findAndRemoveMatchingMessage(m);
+            if (message == null) {
+                message = MessagingMessage.createMessage(this, m);
+                message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR);
+            }
+            message.setIsHistoric(historic);
+            result.add(message);
+        }
+        return result;
+    }
+
+    private MessagingMessage findAndRemoveMatchingMessage(Notification.MessagingStyle.Message m) {
+        for (int i = 0; i < mMessages.size(); i++) {
+            MessagingMessage existing = mMessages.get(i);
+            if (existing.sameAs(m)) {
+                mMessages.remove(i);
+                return existing;
+            }
+        }
+        for (int i = 0; i < mHistoricMessages.size(); i++) {
+            MessagingMessage existing = mHistoricMessages.get(i);
+            if (existing.sameAs(m)) {
+                mHistoricMessages.remove(i);
+                return existing;
+            }
+        }
+        return null;
+    }
+
+    public void showHistoricMessages(boolean show) {
+        mShowHistoricMessages = show;
+        updateHistoricMessageVisibility();
+    }
+
+    private void updateHistoricMessageVisibility() {
+        for (int i = 0; i < mHistoricMessages.size(); i++) {
+            MessagingMessage existing = mHistoricMessages.get(i);
+            existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (!mAddedGroups.isEmpty()) {
+            getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    for (MessagingGroup group : mAddedGroups) {
+                        if (!group.isShown()) {
+                            continue;
+                        }
+                        MessagingPropertyAnimator.fadeIn(group.getAvatar());
+                        MessagingPropertyAnimator.fadeIn(group.getSender());
+                        MessagingPropertyAnimator.startLocalTranslationFrom(group,
+                                group.getHeight(), LINEAR_OUT_SLOW_IN);
+                    }
+                    mAddedGroups.clear();
+                    getViewTreeObserver().removeOnPreDrawListener(this);
+                    return true;
+                }
+            });
+        }
+    }
+
+    public View getContractedMessage() {
+        return mContractedMessage;
+    }
+
+    public MessagingLinearLayout getMessagingLinearLayout() {
+        return mMessagingLinearLayout;
+    }
+
+    public ArrayList<MessagingGroup> getMessagingGroups() {
+        return mGroups;
+    }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index 70473a0..f0ef370 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -36,28 +36,14 @@
 @RemoteViews.RemoteView
 public class MessagingLinearLayout extends ViewGroup {
 
-    private static final int NOT_MEASURED_BEFORE = -1;
     /**
      * Spacing to be applied between views.
      */
     private int mSpacing;
 
-    /**
-     * The maximum height allowed.
-     */
-    private int mMaxHeight;
+    private int mMaxDisplayedLines = Integer.MAX_VALUE;
 
-    private int mIndentLines;
-
-    /**
-     * Id of the child that's also visible in the contracted layout.
-     */
-    private int mContractedChildId;
-    /**
-     * The last measured with in a layout pass if it was measured before or
-     * {@link #NOT_MEASURED_BEFORE} if this is the first layout pass.
-     */
-    private int mLastMeasuredWidth = NOT_MEASURED_BEFORE;
+    private MessagingLayout mMessagingLayout;
 
     public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -79,7 +65,6 @@
         a.recycle();
     }
 
-
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // This is essentially a bottom-up linear layout that only adds children that fit entirely
@@ -91,118 +76,67 @@
                 break;
         }
         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-        boolean recalculateVisibility = mLastMeasuredWidth == NOT_MEASURED_BEFORE
-                || getMeasuredHeight() != targetHeight
-                || mLastMeasuredWidth != widthSize;
-
-        final int count = getChildCount();
-        if (recalculateVisibility) {
-            // We only need to recalculate the view visibilities if the view wasn't measured already
-            // in this pass, otherwise we may drop messages here already since we are measured
-            // exactly with what we returned before, which was optimized already with the
-            // line-indents.
-            for (int i = 0; i < count; ++i) {
-                final View child = getChildAt(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                lp.hide = true;
-            }
-
-            int totalHeight = mPaddingTop + mPaddingBottom;
-            boolean first = true;
-
-            // Starting from the bottom: we measure every view as if it were the only one. If it still
-
-            // fits, we take it, otherwise we stop there.
-            for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
-                if (getChildAt(i).getVisibility() == GONE) {
-                    continue;
-                }
-                final View child = getChildAt(i);
-                LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
-                ImageFloatingTextView textChild = null;
-                if (child instanceof ImageFloatingTextView) {
-                    // Pretend we need the image padding for all views, we don't know which
-                    // one will end up needing to do this (might end up not using all the space,
-                    // but calculating this exactly would be more expensive).
-                    textChild = (ImageFloatingTextView) child;
-                    textChild.setNumIndentLines(mIndentLines == 2 ? 3 : mIndentLines);
-                }
-
-                int spacing = first ? 0 : mSpacing;
-                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
-                        - mPaddingTop - mPaddingBottom + spacing);
-
-                final int childHeight = child.getMeasuredHeight();
-                int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
-                        lp.bottomMargin + spacing);
-                first = false;
-                boolean measuredTooSmall = false;
-                if (textChild != null) {
-                    measuredTooSmall = childHeight < textChild.getLayoutHeight()
-                            + textChild.getPaddingTop() + textChild.getPaddingBottom();
-                }
-
-                if (newHeight <= targetHeight && !measuredTooSmall) {
-                    totalHeight = newHeight;
-                    lp.hide = false;
-                } else {
-                    break;
-                }
-            }
-        }
 
         // Now that we know which views to take, fix up the indents and see what width we get.
         int measuredWidth = mPaddingLeft + mPaddingRight;
-        int imageLines = mIndentLines;
-        // Need to redo the height because it may change due to changing indents.
-        int totalHeight = mPaddingTop + mPaddingBottom;
-        boolean first = true;
-        for (int i = 0; i < count; i++) {
+        final int count = getChildCount();
+        int totalHeight;
+        for (int i = 0; i < count; ++i) {
             final View child = getChildAt(i);
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            if (child.getVisibility() == GONE || lp.hide) {
-                continue;
-            }
-
-            if (child instanceof ImageFloatingTextView) {
-                ImageFloatingTextView textChild = (ImageFloatingTextView) child;
-                if (imageLines == 2 && textChild.getLineCount() > 2) {
-                    // HACK: If we need indent for two lines, and they're coming from the same
-                    // view, we need extra spacing to compensate for the lack of margins,
-                    // so add an extra line of indent.
-                    imageLines = 3;
-                }
-                boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines));
-                if (changed || !recalculateVisibility) {
-                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
-                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
-                            lp.width);
-                    // we want to measure it at most as high as it is currently, otherwise we'll
-                    // drop later lines
-                    final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
-                            targetHeight - child.getMeasuredHeight(), lp.height);
-
-                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);;
-                }
-                imageLines -= textChild.getLineCount();
-            }
-
-            measuredWidth = Math.max(measuredWidth,
-                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
-                            + mPaddingLeft + mPaddingRight);
-            totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() +
-                    lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing));
-            first = false;
+            lp.hide = true;
         }
 
+        totalHeight = mPaddingTop + mPaddingBottom;
+        boolean first = true;
+        int linesRemaining = mMaxDisplayedLines;
+
+        // Starting from the bottom: we measure every view as if it were the only one. If it still
+        // fits, we take it, otherwise we stop there.
+        for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
+            if (getChildAt(i).getVisibility() == GONE) {
+                continue;
+            }
+            final View child = getChildAt(i);
+            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+            MessagingChild messagingChild = null;
+            if (child instanceof MessagingChild) {
+                messagingChild = (MessagingChild) child;
+                messagingChild.setMaxDisplayedLines(linesRemaining);
+            }
+            int spacing = first ? 0 : mSpacing;
+            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
+                    - mPaddingTop - mPaddingBottom + spacing);
+
+            final int childHeight = child.getMeasuredHeight();
+            int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
+                    lp.bottomMargin + spacing);
+            first = false;
+            int measureType = MessagingChild.MEASURED_NORMAL;
+            if (messagingChild != null) {
+                measureType = messagingChild.getMeasuredType();
+                linesRemaining -= messagingChild.getConsumedLines();
+            }
+            boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED;
+            boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL;
+            if (newHeight <= targetHeight && !isTooSmall) {
+                totalHeight = newHeight;
+                measuredWidth = Math.max(measuredWidth,
+                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
+                                + mPaddingLeft + mPaddingRight);
+                lp.hide = false;
+                if (isShortened || linesRemaining <= 0) {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
 
         setMeasuredDimension(
                 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
                         widthMeasureSpec),
-                resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight),
-                        heightMeasureSpec));
-        mLastMeasuredWidth = widthSize;
+                Math.max(getSuggestedMinimumHeight(), totalHeight));
     }
 
     @Override
@@ -221,14 +155,23 @@
         childTop = mPaddingTop;
 
         boolean first = true;
-
+        final boolean shown = isShown();
         for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            if (child.getVisibility() == GONE || lp.hide) {
+            if (child.getVisibility() == GONE) {
                 continue;
             }
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            MessagingChild messagingChild = (MessagingChild) child;
+            if (lp.hide) {
+                if (shown && lp.visibleBefore) {
+                    messagingChild.hideAnimated();
+                }
+                lp.visibleBefore = false;
+                continue;
+            } else {
+                lp.visibleBefore = true;
+            }
 
             final int childWidth = child.getMeasuredWidth();
             final int childHeight = child.getMeasuredHeight();
@@ -251,14 +194,16 @@
 
             first = false;
         }
-        mLastMeasuredWidth = NOT_MEASURED_BEFORE;
     }
 
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
         if (lp.hide) {
-            return true;
+            MessagingChild messagingChild = (MessagingChild) child;
+            if (!messagingChild.isHidingAnimated()) {
+                return true;
+            }
         }
         return super.drawChild(canvas, child, drawingTime);
     }
@@ -284,31 +229,37 @@
     }
 
     /**
-     * Sets how many lines should be indented to avoid a floating image.
+     * Sets how many lines should be displayed at most
      */
     @RemotableViewMethod
-    public void setNumIndentLines(int numberLines) {
-        mIndentLines = numberLines;
+    public void setMaxDisplayedLines(int numberLines) {
+        mMaxDisplayedLines = numberLines;
     }
 
-    /**
-     * Set id of the child that's also visible in the contracted layout.
-     */
-    @RemotableViewMethod
-    public void setContractedChildId(int contractedChildId) {
-        mContractedChildId = contractedChildId;
+    public void setMessagingLayout(MessagingLayout layout) {
+        mMessagingLayout = layout;
     }
 
-    /**
-     * Get id of the child that's also visible in the contracted layout.
-     */
-    public int getContractedChildId() {
-        return mContractedChildId;
+    public MessagingLayout getMessagingLayout() {
+        return mMessagingLayout;
+    }
+
+    public interface MessagingChild {
+        int MEASURED_NORMAL = 0;
+        int MEASURED_SHORTENED = 1;
+        int MEASURED_TOO_SMALL = 2;
+
+        int getMeasuredType();
+        int getConsumedLines();
+        void setMaxDisplayedLines(int lines);
+        void hideAnimated();
+        boolean isHidingAnimated();
     }
 
     public static class LayoutParams extends MarginLayoutParams {
 
-        boolean hide = false;
+        public boolean hide = false;
+        public boolean visibleBefore = false;
 
         public LayoutParams(Context c, AttributeSet attrs) {
             super(c, attrs);
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
new file mode 100644
index 0000000..f09621f
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -0,0 +1,197 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.Objects;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingMessage extends ImageFloatingTextView implements
+        MessagingLinearLayout.MessagingChild {
+
+    private static Pools.SimplePool<MessagingMessage> sInstancePool
+            = new Pools.SynchronizedPool<>(10);
+    private Notification.MessagingStyle.Message mMessage;
+    private MessagingGroup mGroup;
+    private boolean mIsHistoric;
+    private boolean mIsHidingAnimated;
+
+    public MessagingMessage(@NonNull Context context) {
+        super(context);
+    }
+
+    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    private void setMessage(Notification.MessagingStyle.Message message) {
+        mMessage = message;
+        setText(message.getText());
+    }
+
+    public Notification.MessagingStyle.Message getMessage() {
+        return mMessage;
+    }
+
+    boolean sameAs(Notification.MessagingStyle.Message message) {
+        if (!Objects.equals(message.getText(), mMessage.getText())) {
+            return false;
+        }
+        if (!Objects.equals(message.getSender(), mMessage.getSender())) {
+            return false;
+        }
+        if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) {
+            return false;
+        }
+        return true;
+    }
+
+    boolean sameAs(MessagingMessage message) {
+        return sameAs(message.getMessage());
+    }
+
+    static MessagingMessage createMessage(MessagingLayout layout,
+            Notification.MessagingStyle.Message m) {
+        MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
+        MessagingMessage createdMessage = sInstancePool.acquire();
+        if (createdMessage == null) {
+            createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate(
+                    R.layout.notification_template_messaging_message, messagingLinearLayout,
+                    false);
+        }
+        createdMessage.setMessage(m);
+        return createdMessage;
+    }
+
+    public void removeMessage() {
+        mGroup.removeMessage(this);
+    }
+
+    public void recycle() {
+        mGroup = null;
+        mMessage = null;
+        setAlpha(1.0f);
+        setTranslationY(0);
+        sInstancePool.release(this);
+    }
+
+    public void setMessagingGroup(MessagingGroup group) {
+        mGroup = group;
+    }
+
+    public static void dropCache() {
+        sInstancePool = new Pools.SynchronizedPool<>(10);
+    }
+
+    public void setIsHistoric(boolean isHistoric) {
+        mIsHistoric = isHistoric;
+    }
+
+    public MessagingGroup getGroup() {
+        return mGroup;
+    }
+
+    @Override
+    public int getMeasuredType() {
+        boolean measuredTooSmall = getMeasuredHeight()
+                < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+        if (measuredTooSmall) {
+            return MEASURED_TOO_SMALL;
+        } else {
+            Layout layout = getLayout();
+            if (layout == null) {
+                return MEASURED_TOO_SMALL;
+            }
+            if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
+                return MEASURED_SHORTENED;
+            } else {
+                return MEASURED_NORMAL;
+            }
+        }
+    }
+
+    @Override
+    public void hideAnimated() {
+        setIsHidingAnimated(true);
+        mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false));
+    }
+
+    private void setIsHidingAnimated(boolean isHiding) {
+        ViewParent parent = getParent();
+        mIsHidingAnimated = isHiding;
+        invalidate();
+        if (parent instanceof ViewGroup) {
+            ((ViewGroup) parent).invalidate();
+        }
+    }
+
+    @Override
+    public boolean isHidingAnimated() {
+        return mIsHidingAnimated;
+    }
+
+    @Override
+    public void setMaxDisplayedLines(int lines) {
+        setMaxLines(lines);
+    }
+
+    @Override
+    public int getConsumedLines() {
+        return getLineCount();
+    }
+
+    public int getLayoutHeight() {
+        Layout layout = getLayout();
+        if (layout == null) {
+            return 0;
+        }
+        return layout.getHeight();
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/widget/MessagingPropertyAnimator.java b/core/java/com/android/internal/widget/MessagingPropertyAnimator.java
new file mode 100644
index 0000000..7c3ab7f
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingPropertyAnimator.java
@@ -0,0 +1,233 @@
+/*
+ * 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.internal.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.IntProperty;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import com.android.internal.R;
+
+/**
+ * A listener that automatically starts animations when the layout bounds change.
+ */
+public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
+    static final long APPEAR_ANIMATION_LENGTH = 210;
+    private static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+    public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+    private static final int TAG_LOCAL_TRANSLATION_ANIMATOR = R.id.tag_local_translation_y_animator;
+    private static final int TAG_LOCAL_TRANSLATION_Y = R.id.tag_local_translation_y;
+    private static final int TAG_LAYOUT_TOP = R.id.tag_layout_top;
+    private static final int TAG_ALPHA_ANIMATOR = R.id.tag_alpha_animator;
+    private static final ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS =
+            view -> view.getId() == com.android.internal.R.id.notification_messaging;
+    private static final IntProperty<View> LOCAL_TRANSLATION_Y =
+            new IntProperty<View>("localTranslationY") {
+                @Override
+                public void setValue(View object, int value) {
+                    setLocalTranslationY(object, value);
+                }
+
+                @Override
+                public Integer get(View object) {
+                    return getLocalTranslationY(object);
+                }
+            };
+
+    @Override
+    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+            int oldTop, int oldRight, int oldBottom) {
+        int oldHeight = oldBottom - oldTop;
+        Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+        if (layoutTop != null) {
+            oldTop = layoutTop;
+        }
+        int topChange = oldTop - top;
+        if (oldHeight == 0 || topChange == 0 || !v.isShown() || isGone(v)) {
+            // First layout
+            return;
+        }
+        if (layoutTop != null) {
+            v.setTagInternal(TAG_LAYOUT_TOP, top);
+        }
+        int newHeight = bottom - top;
+        int heightDifference = oldHeight - newHeight;
+        // Only add the difference if the height changes and it's getting smaller
+        heightDifference = Math.max(heightDifference, 0);
+        startLocalTranslationFrom(v, topChange + heightDifference + getLocalTranslationY(v));
+    }
+
+    private boolean isGone(View view) {
+        if (view.getVisibility() == View.GONE) {
+            return true;
+        }
+        final ViewGroup.LayoutParams lp = view.getLayoutParams();
+        if (lp instanceof MessagingLinearLayout.LayoutParams
+                && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+            return true;
+        }
+        return false;
+    }
+
+    public static void startLocalTranslationFrom(View v, int startTranslation) {
+        startLocalTranslationFrom(v, startTranslation, MessagingLayout.FAST_OUT_SLOW_IN);
+    }
+
+    public static void startLocalTranslationFrom(View v, int startTranslation,
+            Interpolator interpolator) {
+        startLocalTranslation(v, startTranslation, 0, interpolator);
+    }
+
+    public static void startLocalTranslationTo(View v, int endTranslation,
+            Interpolator interpolator) {
+        startLocalTranslation(v, getLocalTranslationY(v), endTranslation, interpolator);
+    }
+
+    public static int getLocalTranslationY(View v) {
+        Integer tag = (Integer) v.getTag(TAG_LOCAL_TRANSLATION_Y);
+        if (tag == null) {
+            return 0;
+        }
+        return tag;
+    }
+
+    private static void setLocalTranslationY(View v, int value) {
+        v.setTagInternal(TAG_LOCAL_TRANSLATION_Y, value);
+        updateTopAndBottom(v);
+    }
+
+    private static void updateTopAndBottom(View v) {
+        int layoutTop = (int) v.getTag(TAG_LAYOUT_TOP);
+        int localTranslation = getLocalTranslationY(v);
+        int height = v.getHeight();
+        v.setTop(layoutTop + localTranslation);
+        v.setBottom(layoutTop + height + localTranslation);
+    }
+
+    private static void startLocalTranslation(final View v, int start, int end,
+            Interpolator interpolator) {
+        ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR);
+        if (existing != null) {
+            existing.cancel();
+        }
+        ObjectAnimator animator = ObjectAnimator.ofInt(v, LOCAL_TRANSLATION_Y, start, end);
+        Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+        if (layoutTop == null) {
+            layoutTop = v.getTop();
+            v.setTagInternal(TAG_LAYOUT_TOP, layoutTop);
+        }
+        setLocalTranslationY(v, start);
+        animator.setInterpolator(interpolator);
+        animator.setDuration(APPEAR_ANIMATION_LENGTH);
+        animator.addListener(new AnimatorListenerAdapter() {
+            public boolean mCancelled;
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, null);
+                setClippingDeactivated(v, false);
+                if (!mCancelled) {
+                    setLocalTranslationY(v, 0);
+                    v.setTagInternal(TAG_LAYOUT_TOP, null);
+                }
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+        });
+        setClippingDeactivated(v, true);
+        v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, animator);
+        animator.start();
+    }
+
+    public static void fadeIn(final View v) {
+        ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_ALPHA_ANIMATOR);
+        if (existing != null) {
+            existing.cancel();
+        }
+        if (v.getVisibility() == View.INVISIBLE) {
+            v.setVisibility(View.VISIBLE);
+        }
+        ObjectAnimator animator = ObjectAnimator.ofFloat(v, View.ALPHA,
+                0.0f, 1.0f);
+        v.setAlpha(0.0f);
+        animator.setInterpolator(ALPHA_IN);
+        animator.setDuration(APPEAR_ANIMATION_LENGTH);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                v.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+                updateLayerType(v, false /* animating */);
+            }
+        });
+        updateLayerType(v, true /* animating */);
+        v.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+        animator.start();
+    }
+
+    private static void updateLayerType(View view, boolean animating) {
+        if (view.hasOverlappingRendering() && animating) {
+            view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
+            view.setLayerType(View.LAYER_TYPE_NONE, null);
+        }
+    }
+
+    public static void fadeOut(final View view, Runnable endAction) {
+        ObjectAnimator existing = (ObjectAnimator) view.getTag(TAG_ALPHA_ANIMATOR);
+        if (existing != null) {
+            existing.cancel();
+        }
+        ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA,
+                view.getAlpha(), 0.0f);
+        animator.setInterpolator(ALPHA_OUT);
+        animator.setDuration(APPEAR_ANIMATION_LENGTH);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                view.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+                updateLayerType(view, false /* animating */);
+                if (endAction != null) {
+                    endAction.run();
+                }
+            }
+        });
+        updateLayerType(view, true /* animating */);
+        view.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+        animator.start();
+    }
+
+    public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
+        ViewClippingUtil.setClippingDeactivated(transformedView, deactivated,
+                CLIPPING_PARAMETERS);
+    }
+
+    public static boolean isAnimatingTranslation(View v) {
+        return v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR) != null;
+    }
+
+    public static boolean isAnimatingAlpha(View v) {
+        return v.getTag(TAG_ALPHA_ANIMATOR) != null;
+    }
+}
diff --git a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
new file mode 100644
index 0000000..e352b45
--- /dev/null
+++ b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
@@ -0,0 +1,68 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+/**
+ * A LinearLayout that sets it's height again after the last measure pass. This is needed for
+ * MessagingLayouts where groups need to be able to snap it's height to.
+ */
+@RemoteViews.RemoteView
+public class RemeasuringLinearLayout extends LinearLayout {
+
+    public RemeasuringLinearLayout(Context context) {
+        super(context);
+    }
+
+    public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public RemeasuringLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int count = getChildCount();
+        int height = 0;
+        for (int i = 0; i < count; ++i) {
+            final View child = getChildAt(i);
+            if (child == null || child.getVisibility() == View.GONE) {
+                continue;
+            }
+
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            height = Math.max(height, height + child.getMeasuredHeight() + lp.topMargin +
+                    lp.bottomMargin);
+        }
+        setMeasuredDimension(getMeasuredWidth(), height);
+    }
+}
diff --git a/core/java/com/android/internal/widget/ViewClippingUtil.java b/core/java/com/android/internal/widget/ViewClippingUtil.java
new file mode 100644
index 0000000..59bbed4
--- /dev/null
+++ b/core/java/com/android/internal/widget/ViewClippingUtil.java
@@ -0,0 +1,108 @@
+/*
+ * 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.internal.widget;
+
+import android.util.ArraySet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import com.android.internal.R;
+
+/**
+ * A utility class that allows to clip views and their parents to allow for better transitions
+ */
+public class ViewClippingUtil {
+    private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
+    private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
+    private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
+
+    public static void setClippingDeactivated(final View transformedView, boolean deactivated,
+        ClippingParameters clippingParameters) {
+        if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+            return;
+        }
+        if (!(transformedView.getParent() instanceof ViewGroup)) {
+            return;
+        }
+        ViewGroup parent = (ViewGroup) transformedView.getParent();
+        while (true) {
+            if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+                return;
+            }
+            ArraySet<View> clipSet = (ArraySet<View>) parent.getTag(CLIP_CLIPPING_SET);
+            if (clipSet == null) {
+                clipSet = new ArraySet<>();
+                parent.setTagInternal(CLIP_CLIPPING_SET, clipSet);
+            }
+            Boolean clipChildren = (Boolean) parent.getTag(CLIP_CHILDREN_TAG);
+            if (clipChildren == null) {
+                clipChildren = parent.getClipChildren();
+                parent.setTagInternal(CLIP_CHILDREN_TAG, clipChildren);
+            }
+            Boolean clipToPadding = (Boolean) parent.getTag(CLIP_TO_PADDING);
+            if (clipToPadding == null) {
+                clipToPadding = parent.getClipToPadding();
+                parent.setTagInternal(CLIP_TO_PADDING, clipToPadding);
+            }
+            if (!deactivated) {
+                clipSet.remove(transformedView);
+                if (clipSet.isEmpty()) {
+                    parent.setClipChildren(clipChildren);
+                    parent.setClipToPadding(clipToPadding);
+                    parent.setTagInternal(CLIP_CLIPPING_SET, null);
+                    clippingParameters.onClippingStateChanged(parent, true);
+                }
+            } else {
+                clipSet.add(transformedView);
+                parent.setClipChildren(false);
+                parent.setClipToPadding(false);
+                clippingParameters.onClippingStateChanged(parent, false);
+            }
+            if (clippingParameters.shouldFinish(parent)) {
+                return;
+            }
+            final ViewParent viewParent = parent.getParent();
+            if (viewParent instanceof ViewGroup) {
+                parent = (ViewGroup) viewParent;
+            } else {
+                return;
+            }
+        }
+    }
+
+    public interface ClippingParameters {
+        /**
+         * Should we stop clipping at this view? If true is returned, {@param view} is the last view
+         * where clipping is activated / deactivated.
+         */
+        boolean shouldFinish(View view);
+
+        /**
+         * Is it allowed to enable clipping on this view.
+         */
+        default boolean isClippingEnablingAllowed(View view) {
+            return !MessagingPropertyAnimator.isAnimatingTranslation(view);
+        }
+
+        /**
+         * A method that is called whenever the view starts clipping again / stops clipping to the
+         * children and padding.
+         */
+        default void onClippingStateChanged(View view, boolean isClipping) {};
+    }
+}
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index f0ac79a..d18c172 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -554,18 +554,6 @@
     }
 }
 
-static void android_os_Parcel_clearFileDescriptor(JNIEnv* env, jclass clazz, jobject object)
-{
-    if (object == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return;
-    }
-    int fd = jniGetFDFromFileDescriptor(env, object);
-    if (fd >= 0) {
-        jniSetFileDescriptorOfFD(env, object, -1);
-    }
-}
-
 static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
 {
     Parcel* parcel = new Parcel();
@@ -811,7 +799,6 @@
     {"openFileDescriptor",        "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_openFileDescriptor},
     {"dupFileDescriptor",         "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_dupFileDescriptor},
     {"closeFileDescriptor",       "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_closeFileDescriptor},
-    {"clearFileDescriptor",       "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_clearFileDescriptor},
 
     {"nativeCreate",              "()J", (void*)android_os_Parcel_create},
     {"nativeFreeBuffer",          "(J)J", (void*)android_os_Parcel_freeBuffer},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4a4de24..49de135 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3840,14 +3840,6 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name="com.android.server.updates.TzDataInstallReceiver"
-                android:permission="android.permission.UPDATE_CONFIG">
-            <intent-filter>
-                <action android:name="android.intent.action.UPDATE_TZDATA" />
-                <data android:scheme="content" android:host="*" android:mimeType="*/*" />
-            </intent-filter>
-        </receiver>
-
         <receiver android:name="com.android.server.updates.CertificateTransparencyLogInstallReceiver"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
diff --git a/core/res/res/drawable/ic_reply_notification_large.xml b/core/res/res/drawable/ic_reply_notification_large.xml
new file mode 100644
index 0000000..e75afddf
--- /dev/null
+++ b/core/res/res/drawable/ic_reply_notification_large.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ 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
+  -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:inset="8dp">
+    <vector android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24.0"
+            android:viewportWidth="24.0">
+        <path
+            android:fillColor="#FFFFFFFF"
+            android:pathData="M10.0,9.0L10.0,5.0l-7.0,7.0 7.0,7.0l0.0,-4.1c5.0,0.0 8.5,1.6 11.0,5.1 -1.0,-5.0 -4.0,-10.0 -11.0,-11.0z"/>
+        <path
+            android:fillColor="#00000000"
+            android:pathData="M0 0h24v24H0z"/>
+    </vector>
+</inset>
diff --git a/core/res/res/drawable/messaging_message_background.xml b/core/res/res/drawable/messaging_message_background.xml
new file mode 100644
index 0000000..8a2096a
--- /dev/null
+++ b/core/res/res/drawable/messaging_message_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle"
+    android:tint="#14000000">
+    <corners android:radius="4dp" />
+    <padding android:bottom="6dp"
+        android:left="8dp"
+        android:right="8dp"
+        android:top="6dp" />
+</shape>
diff --git a/core/res/res/layout/chooser_row.xml b/core/res/res/layout/chooser_row.xml
index 6c1271d..cf81260 100644
--- a/core/res/res/layout/chooser_row.xml
+++ b/core/res/res/layout/chooser_row.xml
@@ -22,8 +22,7 @@
               android:layout_height="100dp"
               android:gravity="start|top"
               android:paddingStart="@dimen/chooser_grid_padding"
-              android:paddingEnd="@dimen/chooser_grid_padding"
-              android:weightSum="4">
+              android:paddingEnd="@dimen/chooser_grid_padding">
 
 </LinearLayout>
 
diff --git a/core/res/res/layout/notification_material_action.xml b/core/res/res/layout/notification_material_action.xml
index 548ee05..3c9f6ee 100644
--- a/core/res/res/layout/notification_material_action.xml
+++ b/core/res/res/layout/notification_material_action.xml
@@ -25,6 +25,7 @@
     android:layout_marginStart="4dp"
     android:textColor="@color/notification_default_color"
     android:singleLine="true"
+    android:textAlignment="viewStart"
     android:ellipsize="end"
     android:background="@drawable/notification_material_action_background"
     />
diff --git a/core/res/res/layout/notification_material_action_tombstone.xml b/core/res/res/layout/notification_material_action_tombstone.xml
index 1f59ea0..817f298 100644
--- a/core/res/res/layout/notification_material_action_tombstone.xml
+++ b/core/res/res/layout/notification_material_action_tombstone.xml
@@ -26,6 +26,7 @@
     android:textColor="#555555"
     android:singleLine="true"
     android:ellipsize="end"
+    android:textAlignment="viewStart"
     android:alpha="0.5"
     android:enabled="false"
     android:background="@drawable/notification_material_action_background"
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index fd5154a..f72230b 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -14,13 +14,17 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.MessagingLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/status_bar_latest_event_content"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:tag="messaging"
     >
-    <include layout="@layout/notification_template_header" />
+    <include layout="@layout/notification_template_header"
+             android:layout_width="wrap_content"
+             android:layout_height="@dimen/notification_header_height"
+             android:layout_marginEnd="56dp"/>
     <LinearLayout
             android:id="@+id/notification_action_list_margin_target"
             android:layout_width="match_parent"
@@ -39,7 +43,6 @@
             android:paddingEnd="@dimen/notification_content_margin_end"
             android:minHeight="@dimen/notification_min_content_height"
             android:layout_marginBottom="@dimen/notification_content_margin_bottom"
-            android:clipToPadding="false"
             android:orientation="vertical"
             >
             <include layout="@layout/notification_template_part_line1"
@@ -50,31 +53,14 @@
                 android:id="@+id/notification_messaging"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:spacing="@dimen/notification_messaging_spacing" >
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text0"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text1"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text2"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text3"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text4"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text5"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text6"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-            </com.android.internal.widget.MessagingLinearLayout>
+                android:layout_marginTop="8dp"
+                android:spacing="@dimen/notification_messaging_spacing" />
         </LinearLayout>
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
-    <include layout="@layout/notification_template_right_icon" />
-</FrameLayout>
+    <include layout="@layout/notification_template_right_icon"
+             android:layout_width="wrap_content"
+             android:layout_height="wrap_content"
+             android:layout_marginTop="18dp"
+             android:layout_gravity="top|end"/>
+</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml
new file mode 100644
index 0000000..8973ceb
--- /dev/null
+++ b/core/res/res/layout/notification_template_messaging_group.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 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
+  -->
+<com.android.internal.widget.MessagingGroup
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/status_bar_latest_event_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal" >
+    <ImageView
+        android:id="@+id/message_icon"
+        android:layout_width="@dimen/messaging_avatar_size"
+        android:layout_height="@dimen/messaging_avatar_size"
+        android:layout_marginEnd="8dp"
+        android:scaleType="centerCrop"
+        android:importantForAccessibility="no" />
+    <com.android.internal.widget.RemeasuringLinearLayout
+        android:id="@+id/message_group_and_sender_container"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <com.android.internal.widget.MessagingLinearLayout
+            android:id="@+id/group_message_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:spacing="2dp"
+            android:layout_weight="1"/>
+        <com.android.internal.widget.ImageFloatingTextView
+            android:id="@+id/message_name"
+            style="@style/Widget.Material.Notification.MessagingName"
+            android:layout_width="wrap_content"
+            android:paddingStart="8dp"
+            android:paddingEnd="8dp"
+            android:paddingTop="2dp"
+        />
+    </com.android.internal.widget.RemeasuringLinearLayout>
+</com.android.internal.widget.MessagingGroup>
diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_message.xml
new file mode 100644
index 0000000..ab6466c
--- /dev/null
+++ b/core/res/res/layout/notification_template_messaging_message.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<com.android.internal.widget.MessagingMessage
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/message_text"
+    style="@style/Widget.Material.Notification.MessagingText"
+/>
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
index d379256..8fb2887 100644
--- a/core/res/res/layout/notification_template_right_icon.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -19,12 +19,12 @@
              android:id="@+id/right_icon_container"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
+             android:layout_marginTop="36dp"
              android:layout_gravity="top|end">
     <ImageView android:id="@+id/right_icon"
                android:layout_width="@dimen/notification_right_icon_size"
                android:layout_height="@dimen/notification_right_icon_size"
                android:layout_gravity="top|end"
-               android:layout_marginTop="36dp"
                android:layout_marginEnd="@dimen/notification_content_margin_end"
                android:scaleType="centerCrop"
                android:importantForAccessibility="no" />
@@ -32,7 +32,7 @@
                android:layout_width="16dp"
                android:layout_height="16dp"
                android:layout_gravity="top|end"
-               android:layout_marginTop="64dp"
+               android:layout_marginTop="28dp"
                android:layout_marginEnd="12dp"
                android:background="@drawable/notification_reply_background"
                android:src="@drawable/ic_reply_notification"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 947fcf1..946216c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -605,6 +605,8 @@
     <!-- The size of the right icon image when on low ram -->
     <dimen name="notification_right_icon_size_low_ram">40dp</dimen>
 
+    <dimen name="messaging_avatar_size">24dp</dimen>
+
     <!-- Max width/height of the autofill data set picker as a fraction of the screen width/height -->
     <dimen name="autofill_dataset_picker_max_size">90%</dimen>
 
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index cd3624d..b40117e 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -139,6 +139,27 @@
   <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}. -->
   <item type="id" name="accessibilityActionMoveWindow" />
 
+  <!-- A tag used to save an animator in local y translation -->
+  <item type="id" name="tag_local_translation_y_animator" />
+
+  <!-- A tag used to save the local translation y -->
+  <item type="id" name="tag_local_translation_y" />
+
+  <!-- A tag used to save the original top of a view -->
+  <item type="id" name="tag_layout_top" />
+
+  <!-- A tag used to save an animator in alpha -->
+  <item type="id" name="tag_alpha_animator" />
+
+  <!-- A tag used to save the clip children in a tag -->
+  <item type="id" name="clip_children_tag" />
+
+  <!-- A tag used to save the set of deactivated children that clip -->
+  <item type="id" name="clip_children_set_tag" />
+
+  <!-- A tag used to save the clip to padding in a tag -->
+  <item type="id" name="clip_to_padding_tag" />
+
   <!-- Action used to manually trigger an autofill request -->
   <item type="id" name="autofill" />
 
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index cddf99a..2cd4dcb 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -505,8 +505,17 @@
         <item name="layout_width">match_parent</item>
         <item name="layout_height">wrap_content</item>
         <item name="ellipsize">end</item>
-        <item name="visibility">gone</item>
         <item name="textAppearance">@style/TextAppearance.Material.Notification</item>
+        <item name="background">@drawable/messaging_message_background</item>
+    </style>
+
+    <style name="Widget.Material.Notification.MessagingName" parent="Widget.Material.Light.TextView">
+        <item name="layout_width">wrap_content</item>
+        <item name="layout_height">wrap_content</item>
+        <item name="ellipsize">end</item>
+        <item name="textAppearance">@style/TextAppearance.Material.Notification</item>
+        <item name="textColor">@color/notification_primary_text_color_light</item>
+        <item name="textSize">12sp</item>
     </style>
 
     <!-- Widget Styles -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 32758e8..fd0012d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3105,6 +3105,22 @@
   <java-symbol type="dimen" name="chooser_service_spacing" />
   <java-symbol type="bool" name="config_showSysuiShutdown" />
 
+  <java-symbol type="layout" name="notification_template_messaging_message" />
+  <java-symbol type="layout" name="notification_template_messaging_group" />
+  <java-symbol type="id" name="message_text" />
+  <java-symbol type="id" name="message_name" />
+  <java-symbol type="id" name="message_icon" />
+  <java-symbol type="id" name="group_message_container" />
+  <java-symbol type="id" name="tag_local_translation_y_animator" />
+  <java-symbol type="id" name="tag_local_translation_y" />
+  <java-symbol type="id" name="tag_layout_top" />
+  <java-symbol type="id" name="tag_alpha_animator" />
+  <java-symbol type="id" name="clip_children_set_tag" />
+  <java-symbol type="id" name="clip_to_padding_tag" />
+  <java-symbol type="id" name="clip_children_tag" />
+  <java-symbol type="drawable" name="ic_reply_notification_large" />
+  <java-symbol type="dimen" name="messaging_avatar_size" />
+
   <java-symbol type="integer" name="config_stableDeviceDisplayWidth" />
   <java-symbol type="integer" name="config_stableDeviceDisplayHeight" />
   <java-symbol type="bool" name="config_display_no_service_when_sim_unready" />
diff --git a/core/tests/coretests/res/font/samplexmlfont.xml b/core/tests/coretests/res/font/samplexmlfont.xml
index f1d14ff..dc2319e 100644
--- a/core/tests/coretests/res/font/samplexmlfont.xml
+++ b/core/tests/coretests/res/font/samplexmlfont.xml
@@ -1,10 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <font-family xmlns:android="http://schemas.android.com/apk/res/android">
-    <font android:fontStyle="normal" android:fontWeight="400" android:fontVariationSettings="'wdth' 0.8"
-          android:font="@font/samplefont" android:ttcIndex="0"/>
-    <font android:fontStyle="italic" android:fontWeight="400" android:fontVariationSettings="'contrast' 0.5"
-          android:font="@font/samplefont2" android:ttcIndex="1" />
-    <font android:fontStyle="normal" android:fontWeight="800" android:fontVariationSettings="'wdth' 500.0, 'wght' 300.0"
-          android:font="@font/samplefont3" android:ttcIndex="2" />
+    <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/samplefont" />
+    <font android:fontStyle="italic" android:fontWeight="400" android:font="@font/samplefont2" />
+    <font android:fontStyle="normal" android:fontWeight="800" android:font="@font/samplefont3" />
     <font android:fontStyle="italic" android:fontWeight="800" android:font="@font/samplefont4" />
 </font-family>
diff --git a/core/tests/coretests/res/font/samplexmlfontforparsing.xml b/core/tests/coretests/res/font/samplexmlfontforparsing.xml
new file mode 100644
index 0000000..98e3213
--- /dev/null
+++ b/core/tests/coretests/res/font/samplexmlfontforparsing.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+    <font android:fontStyle="normal" android:fontWeight="400" android:fontVariationSettings="'wdth' 0.8"
+          android:font="@font/samplefont" android:ttcIndex="0" />
+    <font android:fontStyle="italic" android:fontWeight="400" android:fontVariationSettings="'cntr' 0.5"
+          android:font="@font/samplefont2" android:ttcIndex="1" />
+    <font android:fontStyle="normal" android:fontWeight="800" android:fontVariationSettings="'wdth' 500.0, 'wght' 300.0"
+          android:font="@font/samplefont3" android:ttcIndex="2" />
+    <font android:fontStyle="italic" android:fontWeight="800" android:font="@font/samplefont4" />
+</font-family>
diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
index a12a0b8..42ff2e9 100644
--- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
+++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
@@ -58,7 +58,7 @@
 
     @Test
     public void testParse() throws XmlPullParserException, IOException {
-        XmlResourceParser parser = mResources.getXml(R.font.samplexmlfont);
+        XmlResourceParser parser = mResources.getXml(R.font.samplexmlfontforparsing);
 
         FamilyResourceEntry result = FontResourcesParser.parse(parser, mResources);
 
@@ -76,7 +76,7 @@
         assertEquals(400, font2.getWeight());
         assertEquals(1, font2.getItalic());
         assertEquals(1, font2.getTtcIndex());
-        assertEquals("'contrast' 0.5", font2.getVariationSettings());
+        assertEquals("'cntr' 0.5", font2.getVariationSettings());
         assertEquals("res/font/samplefont2.ttf", font2.getFileName());
         FontFileResourceEntry font3 = fileEntries[2];
         assertEquals(800, font3.getWeight());
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index ebe0527..1002939 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -180,7 +180,6 @@
                     Settings.Global.DNS_RESOLVER_MIN_SAMPLES,
                     Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
                     Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
-                    Settings.Global.DNS_TLS_DISABLED,
                     Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
                     Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE,
                     Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index bbdbdb1..0245570 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -49,6 +49,11 @@
 
 import static org.hamcrest.Matchers.anyOf;
 import static org.hamcrest.Matchers.is;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Activity;
 import android.app.Instrumentation;
@@ -633,15 +638,21 @@
 
     @Test
     public void testSetSelectionAndActionMode() throws Throwable {
+        final TextView textView = mActivity.findViewById(R.id.textview);
+        final ActionMode.Callback amCallback = mock(ActionMode.Callback.class);
+        when(amCallback.onCreateActionMode(any(ActionMode.class), any(Menu.class)))
+                .thenReturn(true);
+        when(amCallback.onPrepareActionMode(any(ActionMode.class), any(Menu.class)))
+                .thenReturn(true);
+        textView.setCustomSelectionActionModeCallback(amCallback);
+
         final String text = "abc def";
         onView(withId(R.id.textview)).perform(replaceText(text));
-
-        final TextView textView = mActivity.findViewById(R.id.textview);
         mActivityRule.runOnUiThread(
                 () -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
         mInstrumentation.waitForIdleSync();
         // Don't automatically start action mode.
-        // TODO: Implement assertActionModeNotStarted()
+        verify(amCallback, never()).onCreateActionMode(any(ActionMode.class), any(Menu.class));
         // Make sure that "Select All" is included in the selection action mode when the entire text
         // is not selected.
         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
index dfe8511..3919fdd 100644
--- a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
@@ -23,11 +23,11 @@
 import android.content.Context;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
+import android.text.Layout;
 import android.view.LayoutInflater;
 import android.view.View.MeasureSpec;
 
 import com.android.frameworks.coretests.R;
-import com.google.common.base.Function;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,33 +52,28 @@
 
     @Test
     public void testSingleChild() {
-        FakeImageFloatingTextView child = fakeChild((i) -> 3);
+        FakeImageFloatingTextView child = fakeChild(3);
 
-        mView.setNumIndentLines(2);
         mView.addView(child);
 
         mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
         mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
 
-        assertEquals(3, child.getNumIndentLines());
         assertFalse(child.isHidden());
         assertEquals(150, mView.getMeasuredHeight());
     }
 
     @Test
     public void testLargeSmall() {
-        FakeImageFloatingTextView child1 = fakeChild((i) -> 3);
-        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+        FakeImageFloatingTextView child1 = fakeChild(3);
+        FakeImageFloatingTextView child2 = fakeChild(1);
 
-        mView.setNumIndentLines(2);
         mView.addView(child1);
         mView.addView(child2);
 
         mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
         mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
 
-        assertEquals(3, child1.getNumIndentLines());
-        assertEquals(0, child2.getNumIndentLines());
         assertFalse("child1 should not be hidden", child1.isHidden());
         assertFalse("child2 should not be hidden", child2.isHidden());
         assertEquals(205, mView.getMeasuredHeight());
@@ -86,18 +81,15 @@
 
     @Test
     public void testSmallSmall() {
-        FakeImageFloatingTextView child1 = fakeChild((i) -> 1);
-        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+        FakeImageFloatingTextView child1 = fakeChild(1);
+        FakeImageFloatingTextView child2 = fakeChild(1);
 
-        mView.setNumIndentLines(2);
         mView.addView(child1);
         mView.addView(child2);
 
         mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
         mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
 
-        assertEquals(2, child1.getNumIndentLines());
-        assertEquals(1, child2.getNumIndentLines());
         assertFalse("child1 should not be hidden", child1.isHidden());
         assertFalse("child2 should not be hidden", child2.isHidden());
         assertEquals(105, mView.getMeasuredHeight());
@@ -105,17 +97,15 @@
 
     @Test
     public void testLargeLarge() {
-        FakeImageFloatingTextView child1 = fakeChild((i) -> 7);
-        FakeImageFloatingTextView child2 = fakeChild((i) -> 7);
+        FakeImageFloatingTextView child1 = fakeChild(7);
+        FakeImageFloatingTextView child2 = fakeChild(7);
 
-        mView.setNumIndentLines(2);
         mView.addView(child1);
         mView.addView(child2);
 
         mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
         mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
 
-        assertEquals(3, child2.getNumIndentLines());
         assertTrue("child1 should be hidden", child1.isHidden());
         assertFalse("child2 should not be hidden", child2.isHidden());
         assertEquals(350, mView.getMeasuredHeight());
@@ -123,10 +113,9 @@
 
     @Test
     public void testLargeSmall_largeWrapsWith3indentbutNotFullHeight_andHitsMax() {
-        FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 7 : 6);
-        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+        FakeImageFloatingTextView child1 = fakeChild(7);
+        FakeImageFloatingTextView child2 = fakeChild(1);
 
-        mView.setNumIndentLines(2);
         mView.addView(child1);
         mView.addView(child2);
 
@@ -135,51 +124,18 @@
 
         assertFalse("child1 should not be hidden", child1.isHidden());
         assertFalse("child2 should not be hidden", child2.isHidden());
-        assertEquals(355, mView.getMeasuredHeight());
-        assertEquals(3, child1.getNumIndentLines());
-        assertEquals(0, child2.getNumIndentLines());
+        assertEquals(355, mView.getMeasuredHeight());;
     }
 
-    @Test
-    public void testLargeSmall_largeWrapsWith3indentbutnot3() {
-        FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 4 : 3);
-        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
-
-        mView.setNumIndentLines(2);
-        mView.addView(child1);
-        mView.addView(child2);
-
-        mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
-        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
-
-        assertFalse("child1 should not be hidden", child1.isHidden());
-        assertFalse("child2 should not be hidden", child2.isHidden());
-        assertEquals(255, mView.getMeasuredHeight());
-        assertEquals(3, child1.getNumIndentLines());
-        assertEquals(0, child2.getNumIndentLines());
-    }
-
-    private class FakeImageFloatingTextView extends ImageFloatingTextView {
+    private class FakeImageFloatingTextView extends MessagingMessage {
 
         public static final int LINE_HEIGHT = 50;
-        private final Function<Integer, Integer> mLinesForIndent;
-        private int mNumIndentLines;
+        private final int mNumLines;
 
         public FakeImageFloatingTextView(Context context,
-                Function<Integer, Integer> linesForIndent) {
+                int linesForIndent) {
             super(context, null, 0, 0);
-            mLinesForIndent = linesForIndent;
-        }
-
-        @Override
-        public boolean setNumIndentLines(int lines) {
-            boolean changed = (mNumIndentLines != lines);
-            mNumIndentLines = lines;
-            return changed;
-        }
-
-        public int getNumIndentLines() {
-            return mNumIndentLines;
+            mNumLines = linesForIndent;
         }
 
         @Override
@@ -195,6 +151,20 @@
                             heightMeasureSpec)));
         }
 
+        public int getMeasuredType() {
+            boolean measuredTooSmall = getMeasuredHeight()
+                    < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+            if (measuredTooSmall) {
+                return MEASURED_TOO_SMALL;
+            } else {
+                if (getMeasuredHeight() == getDesiredHeight()) {
+                    return MEASURED_NORMAL;
+                } else {
+                    return MEASURED_SHORTENED;
+                }
+            }
+        }
+
         private int clampToMultiplesOfLineHeight(int size) {
             if (size <= LINE_HEIGHT) {
                 return size;
@@ -204,7 +174,7 @@
 
         @Override
         public int getLineCount() {
-            return mLinesForIndent.apply(mNumIndentLines);
+            return mNumLines;
         }
 
         public int getDesiredHeight() {
@@ -229,7 +199,7 @@
         }
     }
 
-    private FakeImageFloatingTextView fakeChild(Function<Integer,Integer> linesForIndent) {
-        return new FakeImageFloatingTextView(mContext, linesForIndent);
+    private FakeImageFloatingTextView fakeChild(int numLines) {
+        return new FakeImageFloatingTextView(mContext, numLines);
     }
 }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 82aa803..f7e0b46 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -328,6 +328,7 @@
     <privapp-permissions package="com.android.systemui">
         <permission name="android.permission.BATTERY_STATS"/>
         <permission name="android.permission.BIND_APPWIDGET"/>
+        <permission name="android.permission.BIND_SLICE" />
         <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
         <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 4ddd4b5..fbd9b7a 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -234,7 +234,7 @@
     static int overrideSpotShadowStrength;
 
     static ProfileType getProfileType();
-    static RenderPipelineType getRenderPipelineType();
+    ANDROID_API static RenderPipelineType getRenderPipelineType();
     static bool isSkiaEnabled();
 
     ANDROID_API static bool enableHighContrastText;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 70dfa86..508869a 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -521,7 +521,11 @@
                          float sweepAngle, bool useCenter, const SkPaint& paint) {
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
     SkRect arc = SkRect::MakeLTRB(left, top, right, bottom);
-    mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
+    if (fabs(sweepAngle) >= 360.0f) {
+        mCanvas->drawOval(arc, paint);
+    } else {
+        mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
+    }
 }
 
 void SkiaCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 1d85c972..597336b 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -35,6 +35,7 @@
         "libutils",
         "libbinder",
         "libmedia",
+        "libmedia_omx",
         "libmediametrics",
         "libmediadrm",
         "libmidi",
diff --git a/packages/MtpDocumentsProvider/res/values-mr/strings.xml b/packages/MtpDocumentsProvider/res/values-mr/strings.xml
index 89a9d14..d581e10 100644
--- a/packages/MtpDocumentsProvider/res/values-mr/strings.xml
+++ b/packages/MtpDocumentsProvider/res/values-mr/strings.xml
@@ -19,7 +19,7 @@
     <string name="app_label" msgid="6271216747302322594">"MTP होस्ट"</string>
     <string name="downloads_app_label" msgid="7120690641874849726">"डाउनलोड"</string>
     <string name="root_name" msgid="5819495383921089536">"<xliff:g id="DEVICE_MODEL">%1$s</xliff:g> <xliff:g id="STORAGE_NAME">%2$s</xliff:g>"</string>
-    <string name="accessing_notification_title" msgid="3030133609230917944">"<xliff:g id="DEVICE_MODEL">%1$s</xliff:g> मधून फायलींंमध्ये प्रवेश करीत आहे"</string>
+    <string name="accessing_notification_title" msgid="3030133609230917944">"<xliff:g id="DEVICE_MODEL">%1$s</xliff:g> मधून फायलींंमध्ये प्रवेश करत आहे"</string>
     <string name="error_busy_device" msgid="3997316850357386589">"दुसरे डिव्हाइस व्यस्त आहे. ते उपलब्‍ध होईपर्यंत तुम्ही फायली ट्रांसफर करू शकत नाही."</string>
     <string name="error_locked_device" msgid="7557872102188356147">"कोणत्याही फायली आढळल्या नाहीत. दुसरे डिव्हाइस कदाचित बंद असू शकते. तसे असल्यास, ते अनलॉक करा आणि पुन्हा प्रयत्न करा."</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-mr/strings.xml b/packages/PrintSpooler/res/values-mr/strings.xml
index 05eb853..862d193 100644
--- a/packages/PrintSpooler/res/values-mr/strings.xml
+++ b/packages/PrintSpooler/res/values-mr/strings.xml
@@ -34,7 +34,7 @@
     <string name="print_preview" msgid="8010217796057763343">"मुद्रण पूर्वावलोकन"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"पूर्वावलोकनासाठी पीडीएफ व्ह्यूअर इंस्टॉल करा"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"प्रिंटिंग अ‍ॅप क्रॅश झाले"</string>
-    <string name="generating_print_job" msgid="3119608742651698916">"मुद्रण कार्य व्‍युत्‍पन्न करीत आहे"</string>
+    <string name="generating_print_job" msgid="3119608742651698916">"मुद्रण कार्य व्‍युत्‍पन्न करत आहे"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"पीडीएफ म्‍हणून सेव्ह करा"</string>
     <string name="all_printers" msgid="5018829726861876202">"सर्व प्रिंटर..."</string>
     <string name="print_dialog" msgid="32628687461331979">"मुद्रण संवाद"</string>
@@ -79,8 +79,8 @@
       <item quantity="one"><xliff:g id="COUNT_1">%1$s</xliff:g> प्रिंटर शोधण्यासाठी इंस्टॉल करा</item>
       <item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> प्रिंटर शोधण्यासाठी इंस्टॉल करा</item>
     </plurals>
-    <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> मुद्रण करीत आहे"</string>
-    <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> रद्द करीत आहे"</string>
+    <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> मुद्रण करत आहे"</string>
+    <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> रद्द करत आहे"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"प्रिंटर एरर <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"प्रिंटरने <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> अवरोधित केले"</string>
     <string name="cancel" msgid="4373674107267141885">"रद्द करा"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index a3430fc..2a4983a 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‎‏‎‎Couldn\'t pair with ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ because of an incorrect PIN or passkey.‎‏‎‎‏‎"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎Can\'t communicate with ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎Pairing rejected by ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎Computer‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎Headset‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎Phone‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‏‏‎‎‏‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎Imaging‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎Headphone‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎Input Peripheral‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‎‎‎Bluetooth‎‏‎‎‏‎"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‎‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎Wifi off.‎‏‎‎‏‎"</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‎‎Wifi disconnected.‎‏‎‎‏‎"</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‏‎‎‏‏‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎Wifi one bar.‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index ea62383..f351e70 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"No se pudo sincronizar con <xliff:g id="DEVICE_NAME">%1$s</xliff:g> debido a que el PIN o la clave de acceso son incorrectos."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"No se puede establecer la comunicación con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"Vínculo rechazado por <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"Computadora"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"Auriculares"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"Teléfono"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"Imágenes"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"Auriculares"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"Periférico de entrada"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi inhabilitado"</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fi desconectado"</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Una barra de Wi-Fi"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 42ee581..17d7191 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -22,7 +22,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="wifi_fail_to_scan" msgid="1265540342578081461">"नेटवर्कसाठी स्कॅन करू शकत नाही"</string>
     <string name="wifi_security_none" msgid="7985461072596594400">"काहीही नाही"</string>
-    <string name="wifi_remembered" msgid="4955746899347821096">"जतन केले"</string>
+    <string name="wifi_remembered" msgid="4955746899347821096">"सेव्ह केले"</string>
     <string name="wifi_disabled_generic" msgid="4259794910584943386">"अक्षम"</string>
     <string name="wifi_disabled_network_failure" msgid="2364951338436007124">"IP कॉन्फिगरेशन अयशस्वी"</string>
     <string name="wifi_disabled_by_recommendation_provider" msgid="5168315140978066096">"कमी दर्जाच्या नेटवर्कमुळे कनेक्ट केलेले नाही"</string>
@@ -34,7 +34,7 @@
     <string name="wifi_not_in_range" msgid="1136191511238508967">"परिक्षेत्रामध्ये नाही"</string>
     <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"स्वयंचलितपणे कनेक्ट करणार नाही"</string>
     <string name="wifi_no_internet" msgid="3880396223819116454">"इंटरनेट अॅक्सेस नाही"</string>
-    <string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे जतन केले"</string>
+    <string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे सेव्ह केले"</string>
     <string name="connected_via_network_scorer" msgid="5713793306870815341">"%1$s द्वारे स्वयंचलितपणे कनेक्ट केले"</string>
     <string name="connected_via_network_scorer_default" msgid="7867260222020343104">"नेटवर्क रेटिंग प्रदात्याद्वारे स्वयंचलितपणे कनेक्ट केले"</string>
     <string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s द्वारे कनेक्‍ट केले"</string>
@@ -52,7 +52,7 @@
     <string name="preference_summary_default_combination" msgid="8532964268242666060">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="bluetooth_disconnected" msgid="6557104142667339895">"डिस्कनेक्ट केले"</string>
     <string name="bluetooth_disconnecting" msgid="8913264760027764974">"डिस्कनेक्ट करत आहे..."</string>
-    <string name="bluetooth_connecting" msgid="8555009514614320497">"कनेक्ट करीत आहे..."</string>
+    <string name="bluetooth_connecting" msgid="8555009514614320497">"कनेक्ट करत आहे..."</string>
     <string name="bluetooth_connected" msgid="6038755206916626419">"कनेक्ट केले"</string>
     <string name="bluetooth_pairing" msgid="1426882272690346242">"जोडत आहे…"</string>
     <string name="bluetooth_connected_no_headset" msgid="2866994875046035609">"कनेक्ट केले (फोन नाही)"</string>
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"अयोग्य पिन किंवा पासकीमुळे <xliff:g id="DEVICE_NAME">%1$s</xliff:g> सह जोडू शकलो नाही."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> शी संप्रेषण करू शकत नाही."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> द्वारे पेअरींग नाकारले."</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"संगणक"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"हेडसेट"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"फोन"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"इमेज"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"हेडफोन"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"इनपुट परिधीय"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"ब्लूटूथ"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"वाय फाय बंद."</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"वाय फाय डिस्कनेक्ट झाले."</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"वाय फाय एक बार."</string>
@@ -135,7 +142,7 @@
     <string name="tts_play_example_summary" msgid="8029071615047894486">"उच्चार संश्लेषणाचे एक छोटेसे प्रात्यक्षिक प्ले करा"</string>
     <string name="tts_install_data_title" msgid="4264378440508149986">"व्हॉइस डेटा इंस्टॉल करा"</string>
     <string name="tts_install_data_summary" msgid="5742135732511822589">"उच्चार संश्लेषणासाठी आवश्यक आवाज डेटा इंस्टॉल करा"</string>
-    <string name="tts_engine_security_warning" msgid="8786238102020223650">"हे उच्चार संश्लेषण इंजिन संकेतशब्द आणि क्रेडिट कार्ड नंबर यासारख्या वैयक्तिक मजकुरासह, बोलला जाणारा सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. हे <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> इंजिनवरून येते. या उच्चार संश्लेषण इंजिनचा वापर सक्षम करायचा?"</string>
+    <string name="tts_engine_security_warning" msgid="8786238102020223650">"हे उच्चार संश्लेषण इंजिन पासवर्ड आणि क्रेडिट कार्ड नंबर यासारख्या वैयक्तिक मजकुरासह, बोलला जाणारा सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. हे <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> इंजिनवरून येते. या उच्चार संश्लेषण इंजिनचा वापर सक्षम करायचा?"</string>
     <string name="tts_engine_network_required" msgid="1190837151485314743">"या भाषेस टेक्‍स्‍ट टू स्‍पीचसाठी एका नेटवर्क कनेक्शनची आवश्यकता आहे."</string>
     <string name="tts_default_sample_string" msgid="4040835213373086322">"हे उच्चार संश्लेषणाचे एक उदाहरण आहे"</string>
     <string name="tts_status_title" msgid="7268566550242584413">"डीफॉल्ट भाषा स्थिती"</string>
@@ -245,7 +252,7 @@
     <string name="debug_debugging_category" msgid="6781250159513471316">"डीबग करणे"</string>
     <string name="debug_app" msgid="8349591734751384446">"डीबग अॅप निवडा"</string>
     <string name="debug_app_not_set" msgid="718752499586403499">"कोणतेही डीबग अॅप्लिकेशन सेट नाही"</string>
-    <string name="debug_app_set" msgid="2063077997870280017">"अॅप्लिकेशन डीबग करीत आहे: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="debug_app_set" msgid="2063077997870280017">"अॅप्लिकेशन डीबग करत आहे: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="select_application" msgid="5156029161289091703">"अॅप्लिकेशन निवडा"</string>
     <string name="no_application" msgid="2813387563129153880">"काहीही नाही"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"डीबगरची प्रतीक्षा करा"</string>
@@ -303,11 +310,11 @@
     <string name="force_resizable_activities_summary" msgid="6667493494706124459">"manifest मूल्यांकडे दुर्लक्ष करून, एकाधिक-विंडोसाठी सर्व क्रियाकलापांचा आकार बदलण्यायोग्य करा."</string>
     <string name="enable_freeform_support" msgid="1461893351278940416">"freeform विंडो सक्षम करा"</string>
     <string name="enable_freeform_support_summary" msgid="8247310463288834487">"प्रायोगिक मुक्तस्वरूपाच्या विंडोसाठी समर्थन सक्षम करा."</string>
-    <string name="local_backup_password_title" msgid="3860471654439418822">"डेस्कटॉप बॅकअप संकेतशब्द"</string>
+    <string name="local_backup_password_title" msgid="3860471654439418822">"डेस्कटॉप बॅकअप पासवर्ड"</string>
     <string name="local_backup_password_summary_none" msgid="6951095485537767956">"डेस्कटॉप पूर्ण बॅक अप सध्या संरक्षित नाहीत"</string>
-    <string name="local_backup_password_summary_change" msgid="5376206246809190364">"डेस्कटॉपच्या पूर्ण बॅकअपसाठी असलेला संकेतशब्द बदलण्यासाठी किंवा काढण्यासाठी टॅप  करा"</string>
+    <string name="local_backup_password_summary_change" msgid="5376206246809190364">"डेस्कटॉपच्या पूर्ण बॅकअपसाठी असलेला पासवर्ड बदलण्यासाठी किंवा काढण्यासाठी टॅप  करा"</string>
     <string name="local_backup_password_toast_success" msgid="582016086228434290">"नवीन बॅक अप पासवर्ड सेट झाला"</string>
-    <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"नवीन संकेतशब्द आणि पुष्टीकरण जुळत नाही"</string>
+    <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"नवीन पासवर्ड आणि पुष्टीकरण जुळत नाही"</string>
     <string name="local_backup_password_toast_validation_failure" msgid="5646377234895626531">"बॅक अप पासवर्ड सेट करणे अयशस्वी"</string>
   <string-array name="color_mode_names">
     <item msgid="2425514299220523812">"सशक्त (डीफॉल्ट)"</item>
@@ -382,13 +389,13 @@
     <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"सानुकूल करा (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string>
     <string name="help_feedback_label" msgid="6815040660801785649">"मदत आणि अभिप्राय"</string>
     <string name="content_description_menu_button" msgid="8182594799812351266">"मेनू"</string>
-    <string name="retail_demo_reset_message" msgid="118771671364131297">"डेमो मोडमध्ये फॅक्टरी रीसेट करण्यासाठी पासवर्ड प्रविष्ट करा"</string>
+    <string name="retail_demo_reset_message" msgid="118771671364131297">"डेमो मोडमध्ये फॅक्टरी रीसेट करण्यासाठी पासवर्ड एंटर करा"</string>
     <string name="retail_demo_reset_next" msgid="8356731459226304963">"पुढील"</string>
-    <string name="retail_demo_reset_title" msgid="696589204029930100">"संकेतशब्द आवश्यक"</string>
+    <string name="retail_demo_reset_title" msgid="696589204029930100">"पासवर्ड आवश्यक"</string>
     <string name="active_input_method_subtypes" msgid="3596398805424733238">"सक्रिय इनपुट पद्धती"</string>
     <string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"सिस्टम भाषा वापरा"</string>
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> साठी सेटिंग्ज उघडण्यात अयशस्वी"</string>
-    <string name="ime_security_warning" msgid="4135828934735934248">"ही इनपुट पद्धत संकेतशब्द आणि क्रेडिट कार्ड नंबर यासह, आपण टाइप करता तो सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. ही <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> अॅपवरून येते. ही इनपुट पद्धत वापरायची?"</string>
+    <string name="ime_security_warning" msgid="4135828934735934248">"ही इनपुट पद्धत पासवर्ड आणि क्रेडिट कार्ड नंबर यासह, आपण टाइप करता तो सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. ही <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> अॅपवरून येते. ही इनपुट पद्धत वापरायची?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"टीप: रीबूट केल्यानंतर, तुम्ही आपला फोन अनलॉक करे पर्यंत हे अॅप सुरू होऊ शकत नाही"</string>
     <string name="ims_reg_title" msgid="7609782759207241443">"IMS नोंदणी स्थिती"</string>
     <string name="ims_reg_status_registered" msgid="933003316932739188">"नोंदवलेले"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 360848f..4bb16ff 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -546,11 +546,13 @@
     <!-- [CHAR LIMIT=NONE] Label for displaying Bluetooth Audio Codec Parameters while streaming -->
     <string name="bluetooth_select_a2dp_codec_streaming_label">Streaming: <xliff:g id="streaming_parameter">%1$s</xliff:g></string>
 
-    <!-- Title of the developer option for DNS over TLS. -->
-    <string name="dns_tls">DNS over TLS</string>
-    <!-- Summary to explain the developer option for DNS over TLS.  This allows the user to
-    request that the system attempt TLS with all DNS servers, or none. -->
-    <string name="dns_tls_summary">If enabled, attempt DNS over TLS on port 853.</string>
+    <!-- Developer option setting for Private DNS -->
+    <string name="select_private_dns_configuration_title">Private DNS</string>
+    <string name="select_private_dns_configuration_dialog_title">Select Private DNS Mode</string>
+    <string name="private_dns_mode_off">Off</string>
+    <string name="private_dns_mode_opportunistic">Opportunistic</string>
+    <string name="private_dns_mode_provider">Private DNS provider hostname</string>
+    <string name="private_dns_mode_provider_hostname_hint">Enter hostname of DNS provider</string>
 
     <!-- setting Checkbox summary whether to show options for wireless display certification  -->
     <string name="wifi_display_certification_summary">Show options for wireless display certification</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 1cb255b..a8262c8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -20,6 +20,7 @@
 import android.content.res.XmlResourceParser;
 import android.icu.text.TimeZoneFormat;
 import android.icu.text.TimeZoneNames;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.text.BidiFormatter;
 import android.support.v4.text.TextDirectionHeuristicsCompat;
 import android.text.SpannableString;
@@ -32,6 +33,8 @@
 
 import com.android.settingslib.R;
 
+import libcore.util.TimeZoneFinder;
+
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.util.ArrayList;
@@ -43,7 +46,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
-import libcore.util.TimeZoneFinder;
 
 /**
  * ZoneGetter is the utility class to get time zone and zone list, and both of them have display
@@ -350,7 +352,8 @@
         return gmtText;
     }
 
-    private static final class ZoneGetterData {
+    @VisibleForTesting
+    public static final class ZoneGetterData {
         public final String[] olsonIdsToDisplay;
         public final CharSequence[] gmtOffsetTexts;
         public final TimeZone[] timeZones;
@@ -377,9 +380,13 @@
             }
 
             // Create a lookup of local zone IDs.
-            List<String> zoneIds =
-                    TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(locale.getCountry());
+            final List<String> zoneIds = lookupTimeZoneIdsByCountry(locale.getCountry());
             localZoneIds = new HashSet<>(zoneIds);
         }
+
+        @VisibleForTesting
+        public List<String> lookupTimeZoneIdsByCountry(String country) {
+            return TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(country);
+        }
     }
 }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7b8d0db..29ecac0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -120,6 +120,7 @@
     <uses-permission android:name="android.permission.TRUST_LISTENER" />
     <uses-permission android:name="android.permission.USE_FINGERPRINT" />
     <uses-permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT" />
+    <uses-permission android:name="android.permission.BIND_SLICE" />
 
     <!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
     <uses-permission android:name="android.permission.SET_WALLPAPER"/>
@@ -222,7 +223,7 @@
              -->
         <service android:name="SystemUIService"
             android:exported="true"
-            />
+        />
 
         <!-- Recents depends on every user having their own SystemUI process, so on user switch,
              ensure that the process is created by starting this service.
@@ -568,6 +569,11 @@
                 android:resource="@xml/fileprovider" />
         </provider>
 
+        <provider android:name=".keyguard.KeyguardSliceProvider"
+                  android:authorities="com.android.systemui.keyguard"
+                  android:exported="true">
+        </provider>
+
         <receiver
             android:name=".statusbar.KeyboardShortcutsReceiver">
             <intent-filter>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
index 39cba74..3018a02 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
@@ -33,8 +33,10 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         style="@style/Keyguard.TextView"
+        android:singleLine="true"
         android:ellipsize="marquee"
         android:visibility="gone"
+        android:gravity="center"
         androidprv:allCaps="@bool/kg_use_all_caps" />
 
     <com.android.keyguard.EmergencyButton
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
index 97c8965..947f27d 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
@@ -32,6 +32,7 @@
             android:id="@+id/keyguard_sim"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:tint="@color/background_protected"
             android:src="@drawable/ic_lockscreen_sim"/>
 
     <include layout="@layout/keyguard_message_area"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
index d4c5d74..6f270b4 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
@@ -33,6 +33,7 @@
             android:id="@+id/keyguard_sim"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:tint="@color/background_protected"
             android:src="@drawable/ic_lockscreen_sim"/>
 
     <include layout="@layout/keyguard_message_area"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index 56fb73f..020cfee 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -18,34 +18,37 @@
 -->
 
 <!-- This is a view that shows general status information in Keyguard. -->
-<LinearLayout
+<com.android.keyguard.KeyguardSliceView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
+    android:layout_marginStart="16dp"
+    android:layout_marginEnd="16dp"
+    android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:gravity="center">
-    <com.android.systemui.statusbar.policy.DateView
-        android:id="@+id/date_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textColor="?attr/wallpaperTextColor"
-        style="@style/widget_label"
-        android:letterSpacing="0.05"
-        android:gravity="center"
-        />
-    <TextView android:id="@+id/alarm_status"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:drawablePadding="6dp"
-        android:drawableStart="@drawable/ic_access_alarms_big"
-        android:drawableTint="?attr/wallpaperTextColorSecondary"
-        android:drawableTintMode="src_in"
-        android:textColor="?attr/wallpaperTextColorSecondary"
-        android:letterSpacing="0.05"
-        style="@style/widget_label"
-        android:layout_marginStart="6dp"
-        android:gravity="center"
-        android:visibility="gone"
-        />
-</LinearLayout>
+    android:layout_marginTop="@dimen/date_owner_info_margin"
+    android:layout_gravity="center_horizontal"
+    android:paddingTop="4dp"
+    android:clipToPadding="false"
+    android:orientation="vertical"
+    android:layout_centerHorizontal="true">
+    <TextView android:id="@+id/title"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:singleLine="true"
+              android:ellipsize="end"
+              android:fadingEdge="horizontal"
+              android:gravity="center"
+              android:textSize="22sp"
+              android:textColor="?attr/wallpaperTextColor"
+    />
+    <TextView android:id="@+id/text"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:singleLine="true"
+              android:gravity="center"
+              android:visibility="gone"
+              android:textSize="16sp"
+              android:textColor="?attr/wallpaperTextColor"
+              android:layout_marginTop="4dp"
+              android:ellipsize="end"
+    />
+</com.android.keyguard.KeyguardSliceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9901f6f..c678111f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -95,6 +95,10 @@
     <!-- Height of a heads up notification in the status bar -->
     <dimen name="notification_max_heads_up_height_increased">188dp</dimen>
 
+    <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound
+         and the notification won't use this much, but is measured with wrap_content -->
+    <dimen name="notification_messaging_actions_min_height">196dp</dimen>
+
     <!-- a threshold in dp per second that is considered fast scrolling -->
     <dimen name="scroll_fast_threshold">1500dp</dimen>
 
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index e5f8029..cfd95b4 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -66,9 +66,6 @@
 
     <!-- For notification icons for which targetSdk < L, this caches whether the icon is grayscale -->
     <item type="id" name="icon_is_grayscale" />
-    <item type="id" name="clip_children_tag" />
-    <item type="id" name="clip_children_set_tag" />
-    <item type="id" name="clip_to_padding_tag" />
     <item type="id" name="image_icon_tag" />
     <item type="id" name="contains_transformed_view" />
     <item type="id" name="is_clicked_heads_up_tag" />
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
new file mode 100644
index 0000000..c18f9b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -0,0 +1,160 @@
+/*
+ * 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.keyguard;
+
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.KeyguardSliceProvider;
+
+/**
+ * View visible under the clock on the lock screen and AoD.
+ */
+public class KeyguardSliceView extends LinearLayout {
+
+    private final Uri mKeyguardSliceUri;
+    private TextView mTitle;
+    private TextView mText;
+    private Slice mSlice;
+    private PendingIntent mSliceAction;
+    private int mTextColor;
+    private float mDarkAmount = 0;
+
+    private final ContentObserver mObserver;
+
+    public KeyguardSliceView(Context context) {
+        this(context, null, 0);
+    }
+
+    public KeyguardSliceView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mObserver = new KeyguardSliceObserver(new Handler());
+        mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTitle = findViewById(R.id.title);
+        mText = findViewById(R.id.text);
+        mTextColor = mTitle.getCurrentTextColor();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        // Set initial content
+        showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+
+        // Make sure we always have the most current slice
+        getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
+                false /* notifyDescendants */, mObserver);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        getContext().getContentResolver().unregisterContentObserver(mObserver);
+    }
+
+    private void showSlice(Slice slice) {
+        // Items will be wrapped into an action when they have tap targets.
+        SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+        if (actionSlice != null) {
+            mSlice = actionSlice.getSlice();
+            mSliceAction = actionSlice.getAction();
+        } else {
+            mSlice = slice;
+            mSliceAction = null;
+        }
+
+        if (mSlice == null) {
+            setVisibility(GONE);
+            return;
+        }
+
+        SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null);
+        if (title == null) {
+            mTitle.setVisibility(GONE);
+        } else {
+            mTitle.setVisibility(VISIBLE);
+            mTitle.setText(title.getText());
+        }
+
+        SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE);
+        if (text == null) {
+            mText.setVisibility(GONE);
+        } else {
+            mText.setVisibility(VISIBLE);
+            mText.setText(text.getText());
+        }
+
+        final int visibility = title == null && text == null ? GONE : VISIBLE;
+        if (visibility != getVisibility()) {
+            setVisibility(visibility);
+        }
+    }
+
+    public void setDark(float darkAmount) {
+        mDarkAmount = darkAmount;
+        updateTextColors();
+    }
+
+    public void setTextColor(int textColor) {
+        mTextColor = textColor;
+    }
+
+    private void updateTextColors() {
+        final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+        mTitle.setTextColor(blendedColor);
+        mText.setTextColor(blendedColor);
+    }
+
+    private class KeyguardSliceObserver extends ContentObserver {
+        KeyguardSliceObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            this.onChange(selfChange, null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index bc2a59d..78cf2b9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,11 +19,9 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
-import android.graphics.PorterDuff;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -42,8 +40,8 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.settingslib.Utils;
 import com.android.systemui.ChargingView;
-import com.android.systemui.statusbar.policy.DateView;
 
 import java.util.Locale;
 
@@ -55,13 +53,11 @@
     private final LockPatternUtils mLockPatternUtils;
     private final AlarmManager mAlarmManager;
 
-    private TextView mAlarmStatusView;
-    private DateView mDateView;
     private TextClock mClockView;
     private TextView mOwnerInfo;
     private ViewGroup mClockContainer;
     private ChargingView mBatteryDoze;
-    private View mKeyguardStatusArea;
+    private KeyguardSliceView mKeyguardSlice;
     private Runnable mPendingMarqueeStart;
     private Handler mHandler;
 
@@ -69,8 +65,6 @@
     private boolean mPulsing;
     private float mDarkAmount = 0;
     private int mTextColor;
-    private int mDateTextColor;
-    private int mAlarmTextColor;
 
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
 
@@ -141,7 +135,6 @@
 
     private void setEnableMarqueeImpl(boolean enabled) {
         if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
-        if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled);
         if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled);
     }
 
@@ -149,8 +142,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mClockContainer = findViewById(R.id.keyguard_clock_container);
-        mAlarmStatusView = findViewById(R.id.alarm_status);
-        mDateView = findViewById(R.id.date_view);
         mClockView = findViewById(R.id.clock_view);
         mClockView.setShowCurrentUserTime(true);
         if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) {
@@ -158,11 +149,9 @@
         }
         mOwnerInfo = findViewById(R.id.owner_info);
         mBatteryDoze = findViewById(R.id.battery_doze);
-        mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
-        mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardStatusArea};
+        mKeyguardSlice = findViewById(R.id.keyguard_status_area);
+        mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice};
         mTextColor = mClockView.getCurrentTextColor();
-        mDateTextColor = mDateView.getCurrentTextColor();
-        mAlarmTextColor = mAlarmStatusView.getCurrentTextColor();
 
         boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
         setEnableMarquee(shouldMarquee);
@@ -184,8 +173,6 @@
         layoutParams.bottomMargin = getResources().getDimensionPixelSize(
                 R.dimen.bottom_text_spacing_digital);
         mClockView.setLayoutParams(layoutParams);
-        mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
-                getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
         if (mOwnerInfo != null) {
             mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                     getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
@@ -193,8 +180,6 @@
     }
 
     public void refreshTime() {
-        mDateView.setDatePattern(Patterns.dateViewSkel);
-
         mClockView.setFormat12Hour(Patterns.clockView12);
         mClockView.setFormat24Hour(Patterns.clockView24);
     }
@@ -205,23 +190,11 @@
         Patterns.update(mContext, nextAlarm != null);
 
         refreshTime();
-        refreshAlarmStatus(nextAlarm);
-    }
-
-    void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) {
-        if (nextAlarm != null) {
-            String alarm = formatNextAlarm(mContext, nextAlarm);
-            mAlarmStatusView.setText(alarm);
-            mAlarmStatusView.setContentDescription(
-                    getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm));
-            mAlarmStatusView.setVisibility(View.VISIBLE);
-        } else {
-            mAlarmStatusView.setVisibility(View.GONE);
-        }
     }
 
     public int getClockBottom() {
-        return mKeyguardStatusArea.getBottom();
+        return mKeyguardSlice.getVisibility() == VISIBLE ? mKeyguardSlice.getBottom()
+                : mClockView.getBottom();
     }
 
     public float getClockTextSize() {
@@ -341,11 +314,8 @@
 
         updateDozeVisibleViews();
         mBatteryDoze.setDark(dark);
+        mKeyguardSlice.setDark(darkAmount);
         mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
-        mDateView.setTextColor(ColorUtils.blendARGB(mDateTextColor, Color.WHITE, darkAmount));
-        int blendedAlarmColor = ColorUtils.blendARGB(mAlarmTextColor, Color.WHITE, darkAmount);
-        mAlarmStatusView.setTextColor(blendedAlarmColor);
-        mAlarmStatusView.setCompoundDrawableTintList(ColorStateList.valueOf(blendedAlarmColor));
     }
 
     public void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
index 5d0a9d7..03b0082 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
@@ -33,8 +33,10 @@
 
     @Override
     public void setDozeScreenState(int state) {
-        if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
+        if (state == Display.STATE_DOZE) {
             state = Display.STATE_ON;
+        } else if (state == Display.STATE_DOZE_SUSPEND) {
+            state = Display.STATE_ON_SUSPEND;
         }
         super.setDozeScreenState(state);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 98b1106..6650cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -92,6 +92,7 @@
 
     @Override
     protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dumpOnHandler(fd, pw, args);
         if (mDozeMachine != null) {
             mDozeMachine.dump(pw);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
new file mode 100644
index 0000000..03018f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -0,0 +1,159 @@
+/*
+ * 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.keyguard;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.icu.text.DateFormat;
+import android.icu.text.DisplayContext;
+import android.net.Uri;
+import android.os.Handler;
+import android.app.slice.Slice;
+import android.app.slice.SliceProvider;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Simple Slice provider that shows the current date.
+ */
+public class KeyguardSliceProvider extends SliceProvider {
+
+    public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
+
+    private final Date mCurrentTime = new Date();
+    protected final Uri mSliceUri;
+    private final Handler mHandler;
+    private String mDatePattern;
+    private DateFormat mDateFormat;
+    private String mLastText;
+    private boolean mRegistered;
+    private boolean mRegisteredEveryMinute;
+
+    /**
+     * Receiver responsible for time ticking and updating the date format.
+     */
+    @VisibleForTesting
+    final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_TIME_TICK.equals(action)
+                    || Intent.ACTION_DATE_CHANGED.equals(action)
+                    || Intent.ACTION_TIME_CHANGED.equals(action)
+                    || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
+                    || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+                if (Intent.ACTION_LOCALE_CHANGED.equals(action)
+                        || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
+                    // need to get a fresh date format
+                    mHandler.post(KeyguardSliceProvider.this::cleanDateFormat);
+                }
+                mHandler.post(KeyguardSliceProvider.this::updateClock);
+            }
+        }
+    };
+
+    public KeyguardSliceProvider() {
+        this(new Handler());
+    }
+
+    @VisibleForTesting
+    KeyguardSliceProvider(Handler handler) {
+        mHandler = handler;
+        mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
+    }
+
+    @Override
+    public Slice onBindSlice(Uri sliceUri) {
+        return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+    }
+
+    @Override
+    public boolean onCreate() {
+
+        mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
+
+        registerClockUpdate(false /* everyMinute */);
+        updateClock();
+        return true;
+    }
+
+    protected void registerClockUpdate(boolean everyMinute) {
+        if (mRegistered) {
+            if (mRegisteredEveryMinute == everyMinute) {
+                return;
+            } else {
+                unregisterClockUpdate();
+            }
+        }
+
+        IntentFilter filter = new IntentFilter();
+        if (everyMinute) {
+            filter.addAction(Intent.ACTION_TIME_TICK);
+        }
+        filter.addAction(Intent.ACTION_DATE_CHANGED);
+        filter.addAction(Intent.ACTION_TIME_CHANGED);
+        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+        getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
+                null /* scheduler */);
+        mRegistered = true;
+        mRegisteredEveryMinute = everyMinute;
+    }
+
+    protected void unregisterClockUpdate() {
+        if (!mRegistered) {
+            return;
+        }
+        getContext().unregisterReceiver(mIntentReceiver);
+        mRegistered = false;
+    }
+
+    @VisibleForTesting
+    boolean isRegistered() {
+        return mRegistered;
+    }
+
+    protected void updateClock() {
+        final String text = getFormattedDate();
+        if (!text.equals(mLastText)) {
+            mLastText = text;
+            getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+        }
+    }
+
+    protected String getFormattedDate() {
+        if (mDateFormat == null) {
+            final Locale l = Locale.getDefault();
+            DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);
+            format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
+            mDateFormat = format;
+        }
+        mCurrentTime.setTime(System.currentTimeMillis());
+        return mDateFormat.format(mCurrentTime);
+    }
+
+    @VisibleForTesting
+    void cleanDateFormat() {
+        mDateFormat = null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 6c5f4b2..8ff950e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -69,6 +69,7 @@
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationInflater;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -453,6 +454,11 @@
         } else {
             headsUpheight = mMaxHeadsUpHeight;
         }
+        NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
+                NotificationContentView.VISIBLE_TYPE_HEADSUP);
+        if (headsUpWrapper != null) {
+            headsUpheight = Math.max(headsUpheight, headsUpWrapper.getMinLayoutHeight());
+        }
         layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
                 mNotificationAmbientHeight);
     }
@@ -1256,16 +1262,21 @@
     }
 
     private void initDimens() {
-        mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
-        mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
-        mNotificationMinHeightLarge = getFontScaledHeight(
+        mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_min_height_legacy);
+        mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_min_height);
+        mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_min_height_increased);
-        mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
-        mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
-        mMaxHeadsUpHeightLegacy = getFontScaledHeight(
+        mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_max_height);
+        mNotificationAmbientHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_ambient_height);
+        mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height_legacy);
-        mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
-        mMaxHeadsUpHeightIncreased = getFontScaledHeight(
+        mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_max_heads_up_height);
+        mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height_increased);
 
         Resources res = getResources();
@@ -1280,17 +1291,6 @@
     }
 
     /**
-     * @param dimenId the dimen to look up
-     * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
-     */
-    private int getFontScaledHeight(int dimenId) {
-        int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId);
-        float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity /
-                getResources().getDisplayMetrics().density);
-        return (int) (dimensionPixelSize * factor);
-    }
-
-    /**
      * Resets this view so it can be re-used for an updated notification.
      */
     public void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 5353005..09b11c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -35,7 +35,8 @@
 /**
  * A view that can be transformed to and from.
  */
-public class ViewTransformationHelper implements TransformableView {
+public class ViewTransformationHelper implements TransformableView,
+        TransformState.TransformInfo {
 
     private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
 
@@ -59,7 +60,7 @@
     public TransformState getCurrentState(int fadingView) {
         View view = mTransformedViews.get(fadingView);
         if (view != null && view.getVisibility() != View.GONE) {
-            return TransformState.createFrom(view);
+            return TransformState.createFrom(view, this);
         }
         return null;
     }
@@ -88,6 +89,7 @@
                         endRunnable.run();
                     }
                     setVisible(false);
+                    mViewTransformationAnimation = null;
                 } else {
                     abortTransformations();
                 }
@@ -245,7 +247,7 @@
     }
 
     public void resetTransformedView(View view) {
-        TransformState state = TransformState.createFrom(view);
+        TransformState state = TransformState.createFrom(view, this);
         state.setVisible(true /* visible */, true /* force */);
         state.recycle();
     }
@@ -257,6 +259,11 @@
         return new ArraySet<>(mTransformedViews.values());
     }
 
+    @Override
+    public boolean isAnimating() {
+        return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning();
+    }
+
     public static abstract class CustomTransformation {
         /**
          * Transform a state to the given view
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index 92f26d6..8227b77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -39,8 +39,8 @@
     private Icon mIcon;
 
     @Override
-    public void initFrom(View view) {
-        super.initFrom(view);
+    public void initFrom(View view, TransformInfo transformInfo) {
+        super.initFrom(view, transformInfo);
         if (view instanceof ImageView) {
             mIcon = (Icon) view.getTag(ICON_TAG);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
new file mode 100644
index 0000000..4c2a2f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -0,0 +1,405 @@
+/*
+ * 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.notification;
+
+import android.content.res.Resources;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingLayout;
+import com.android.internal.widget.MessagingLinearLayout;
+import com.android.internal.widget.MessagingMessage;
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A transform state of the action list
+*/
+public class MessagingLayoutTransformState extends TransformState {
+
+    private static Pools.SimplePool<MessagingLayoutTransformState> sInstancePool
+            = new Pools.SimplePool<>(40);
+    private MessagingLinearLayout mMessageContainer;
+    private MessagingLayout mMessagingLayout;
+    private HashMap<MessagingGroup, MessagingGroup> mGroupMap = new HashMap<>();
+    private float mRelativeTranslationOffset;
+
+    public static MessagingLayoutTransformState obtain() {
+        MessagingLayoutTransformState instance = sInstancePool.acquire();
+        if (instance != null) {
+            return instance;
+        }
+        return new MessagingLayoutTransformState();
+    }
+
+    @Override
+    public void initFrom(View view, TransformInfo transformInfo) {
+        super.initFrom(view, transformInfo);
+        if (mTransformedView instanceof MessagingLinearLayout) {
+            mMessageContainer = (MessagingLinearLayout) mTransformedView;
+            mMessagingLayout = mMessageContainer.getMessagingLayout();
+            Resources resources = view.getContext().getResources();
+            mRelativeTranslationOffset = resources.getDisplayMetrics().density * 8;
+        }
+    }
+
+    @Override
+    public boolean transformViewTo(TransformState otherState, float transformationAmount) {
+        if (otherState instanceof MessagingLayoutTransformState) {
+            // It's a party! Let's transform between these two layouts!
+            transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+                    true /* to */);
+            return true;
+        } else {
+            return super.transformViewTo(otherState, transformationAmount);
+        }
+    }
+
+    @Override
+    public void transformViewFrom(TransformState otherState, float transformationAmount) {
+        if (otherState instanceof MessagingLayoutTransformState) {
+            // It's a party! Let's transform between these two layouts!
+            transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+                    false /* to */);
+        } else {
+            super.transformViewFrom(otherState, transformationAmount);
+        }
+    }
+
+    private void transformViewInternal(MessagingLayoutTransformState mlt,
+            float transformationAmount, boolean to) {
+        ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
+                mMessagingLayout.getMessagingGroups());
+        ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
+                mlt.mMessagingLayout.getMessagingGroups());
+        HashMap<MessagingGroup, MessagingGroup> pairs = findPairs(ownGroups, otherGroups);
+        MessagingGroup lastPairedGroup = null;
+        float currentTranslation = 0;
+        float transformationDistanceRemaining = 0;
+        for (int i = ownGroups.size() - 1; i >= 0; i--) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            MessagingGroup matchingGroup = pairs.get(ownGroup);
+            if (!isGone(ownGroup)) {
+                if (matchingGroup != null) {
+                    transformGroups(ownGroup, matchingGroup, transformationAmount, to);
+                    if (lastPairedGroup == null) {
+                        lastPairedGroup = ownGroup;
+                        if (to){
+                            float totalTranslation = ownGroup.getTop() - matchingGroup.getTop();
+                            transformationDistanceRemaining
+                                    = matchingGroup.getAvatar().getTranslationY();
+                            currentTranslation = transformationDistanceRemaining - totalTranslation;
+                        } else {
+                            float totalTranslation = matchingGroup.getTop() - ownGroup.getTop();
+                            currentTranslation = ownGroup.getAvatar().getTranslationY();
+                            transformationDistanceRemaining = currentTranslation - totalTranslation;
+                        }
+                    }
+                } else {
+                    float groupTransformationAmount = transformationAmount;
+                    if (lastPairedGroup != null) {
+                        adaptGroupAppear(ownGroup, transformationAmount, currentTranslation,
+                                to);
+                        int distance = lastPairedGroup.getTop() - ownGroup.getTop();
+                        float transformationDistance = mTransformInfo.isAnimating()
+                                ? distance
+                                : ownGroup.getHeight() * 0.75f;
+                        float translationProgress = transformationDistanceRemaining
+                                - (distance - transformationDistance);
+                        groupTransformationAmount =
+                                translationProgress / transformationDistance;
+                        groupTransformationAmount = Math.max(0.0f, Math.min(1.0f,
+                                groupTransformationAmount));
+                        if (to) {
+                            groupTransformationAmount = 1.0f - groupTransformationAmount;
+                        }
+                    }
+                    if (to) {
+                        disappear(ownGroup, groupTransformationAmount);
+                    } else {
+                        appear(ownGroup, groupTransformationAmount);
+                    }
+                }
+            }
+        }
+    }
+
+    private void appear(MessagingGroup ownGroup, float transformationAmount) {
+        MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+        for (int j = 0; j < ownMessages.getChildCount(); j++) {
+            View child = ownMessages.getChildAt(j);
+            if (isGone(child)) {
+                continue;
+            }
+            appear(child, transformationAmount);
+            setClippingDeactivated(child, true);
+        }
+        appear(ownGroup.getAvatar(), transformationAmount);
+        appear(ownGroup.getSender(), transformationAmount);
+        setClippingDeactivated(ownGroup.getSender(), true);
+        setClippingDeactivated(ownGroup.getAvatar(), true);
+    }
+
+    private void adaptGroupAppear(MessagingGroup ownGroup, float transformationAmount,
+            float overallTranslation, boolean to) {
+        float relativeOffset;
+        if (to) {
+            relativeOffset = transformationAmount * mRelativeTranslationOffset;
+        } else {
+            relativeOffset = (1.0f - transformationAmount) * mRelativeTranslationOffset;
+        }
+        if (ownGroup.getSender().getVisibility() != View.GONE) {
+            relativeOffset *= 0.5f;
+        }
+        ownGroup.getMessageContainer().setTranslationY(relativeOffset);
+        ownGroup.setTranslationY(overallTranslation * 0.85f);
+    }
+
+    private void disappear(MessagingGroup ownGroup, float transformationAmount) {
+        MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+        for (int j = 0; j < ownMessages.getChildCount(); j++) {
+            View child = ownMessages.getChildAt(j);
+            if (isGone(child)) {
+                continue;
+            }
+            disappear(child, transformationAmount);
+            setClippingDeactivated(child, true);
+        }
+        disappear(ownGroup.getAvatar(), transformationAmount);
+        disappear(ownGroup.getSender(), transformationAmount);
+        setClippingDeactivated(ownGroup.getSender(), true);
+        setClippingDeactivated(ownGroup.getAvatar(), true);
+    }
+
+    private void appear(View child, float transformationAmount) {
+        if (child.getVisibility() == View.GONE) {
+            return;
+        }
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.appear(transformationAmount, null);
+        ownState.recycle();
+    }
+
+    private void disappear(View child, float transformationAmount) {
+        if (child.getVisibility() == View.GONE) {
+            return;
+        }
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.disappear(transformationAmount, null);
+        ownState.recycle();
+    }
+
+    private ArrayList<MessagingGroup> filterHiddenGroups(
+            ArrayList<MessagingGroup> groups) {
+        ArrayList<MessagingGroup> result = new ArrayList<>(groups);
+        for (int i = 0; i < result.size(); i++) {
+            MessagingGroup messagingGroup = result.get(i);
+            if (isGone(messagingGroup)) {
+                result.remove(i);
+                i--;
+            }
+        }
+        return result;
+    }
+
+    private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup,
+            float transformationAmount, boolean to) {
+        transformView(transformationAmount, to, ownGroup.getSender(), otherGroup.getSender(),
+                true /* sameAsAny */);
+        transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(),
+                true /* sameAsAny */);
+        MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+        MessagingLinearLayout otherMessages = otherGroup.getMessageContainer();
+        float previousTranslation = 0;
+        for (int i = 0; i < ownMessages.getChildCount(); i++) {
+            View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i);
+            if (isGone(child)) {
+                continue;
+            }
+            int otherIndex = otherMessages.getChildCount() - 1 - i;
+            View otherChild = null;
+            if (otherIndex >= 0) {
+                otherChild = otherMessages.getChildAt(otherIndex);
+                if (isGone(otherChild)) {
+                    otherChild = null;
+                }
+            }
+            if (otherChild == null) {
+                float distanceToTop = child.getTop() + child.getHeight() + previousTranslation;
+                transformationAmount = distanceToTop / child.getHeight();
+                transformationAmount = Math.max(0.0f, Math.min(1.0f, transformationAmount));
+                if (to) {
+                    transformationAmount = 1.0f - transformationAmount;
+                }
+            }
+            transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */);
+            if (otherChild == null) {
+                child.setTranslationY(previousTranslation);
+                setClippingDeactivated(child, true);
+            } else if (to) {
+                float totalTranslation = child.getTop() + ownGroup.getTop()
+                        - otherChild.getTop() - otherChild.getTop();
+                previousTranslation = otherChild.getTranslationY() - totalTranslation;
+            } else {
+                previousTranslation = child.getTranslationY();
+            }
+        }
+    }
+
+    private void transformView(float transformationAmount, boolean to, View ownView,
+            View otherView, boolean sameAsAny) {
+        TransformState ownState = TransformState.createFrom(ownView, mTransformInfo);
+        if (!mTransformInfo.isAnimating()) {
+            ownState.setDefaultInterpolator(Interpolators.LINEAR);
+        }
+        ownState.setIsSameAsAnyView(sameAsAny);
+        if (to) {
+            if (otherView != null) {
+                TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+                ownState.transformViewTo(otherState, transformationAmount);
+                otherState.recycle();
+            } else {
+                ownState.disappear(transformationAmount, null);
+            }
+        } else {
+            if (otherView != null) {
+                TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+                ownState.transformViewFrom(otherState, transformationAmount);
+                otherState.recycle();
+            } else {
+                ownState.appear(transformationAmount, null);
+            }
+        }
+        ownState.recycle();
+    }
+
+    private HashMap<MessagingGroup, MessagingGroup> findPairs(ArrayList<MessagingGroup> ownGroups,
+            ArrayList<MessagingGroup> otherGroups) {
+        mGroupMap.clear();
+        int lastMatch = Integer.MAX_VALUE;
+        for (int i = ownGroups.size() - 1; i >= 0; i--) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            MessagingGroup bestMatch = null;
+            int bestCompatibility = 0;
+            for (int j = Math.min(otherGroups.size(), lastMatch) - 1; j >= 0; j--) {
+                MessagingGroup otherGroup = otherGroups.get(j);
+                int compatibility = ownGroup.calculateGroupCompatibility(otherGroup);
+                if (compatibility > bestCompatibility) {
+                    bestCompatibility = compatibility;
+                    bestMatch = otherGroup;
+                    lastMatch = j;
+                }
+            }
+            if (bestMatch != null) {
+                mGroupMap.put(ownGroup, bestMatch);
+            }
+        }
+        return mGroupMap;
+    }
+
+    private boolean isGone(View view) {
+        if (view.getVisibility() == View.GONE) {
+            return true;
+        }
+        final ViewGroup.LayoutParams lp = view.getLayoutParams();
+        if (lp instanceof MessagingLinearLayout.LayoutParams
+                && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void setVisible(boolean visible, boolean force) {
+        resetTransformedView();
+        ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+        for (int i = 0; i < ownGroups.size(); i++) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            if (!isGone(ownGroup)) {
+                MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+                for (int j = 0; j < ownMessages.getChildCount(); j++) {
+                    MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j);
+                    setVisible(child, visible, force);
+                }
+                setVisible(ownGroup.getAvatar(), visible, force);
+                setVisible(ownGroup.getSender(), visible, force);
+            }
+        }
+    }
+
+    private void setVisible(View child, boolean visible, boolean force) {
+        if (isGone(child) || MessagingPropertyAnimator.isAnimatingAlpha(child)) {
+            return;
+        }
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.setVisible(visible, force);
+        ownState.recycle();
+    }
+
+    @Override
+    protected void resetTransformedView() {
+        super.resetTransformedView();
+        ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+        for (int i = 0; i < ownGroups.size(); i++) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            if (!isGone(ownGroup)) {
+                MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+                for (int j = 0; j < ownMessages.getChildCount(); j++) {
+                    View child = ownMessages.getChildAt(j);
+                    if (isGone(child)) {
+                        continue;
+                    }
+                    resetTransformedView(child);
+                    setClippingDeactivated(child, false);
+                }
+                resetTransformedView(ownGroup.getAvatar());
+                resetTransformedView(ownGroup.getSender());
+                setClippingDeactivated(ownGroup.getAvatar(), false);
+                setClippingDeactivated(ownGroup.getSender(), false);
+                ownGroup.setTranslationY(0);
+                ownGroup.getMessageContainer().setTranslationY(0);
+            }
+        }
+    }
+
+    private void resetTransformedView(View child) {
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.resetTransformedView();
+        ownState.recycle();
+    }
+
+    @Override
+    protected void reset() {
+        super.reset();
+        mMessageContainer = null;
+        mMessagingLayout = null;
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        mGroupMap.clear();;
+        sInstancePool.release(this);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index f6ee1ca..fb5644f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.notification;
 
+import com.android.internal.widget.MessagingLayout;
 import com.android.internal.widget.MessagingLinearLayout;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.TransformableView;
 
@@ -32,41 +34,20 @@
  */
 public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
 
-    private View mContractedMessage;
-    private ArrayList<View> mHistoricMessages = new ArrayList<View>();
+    private final int mMinHeightWithActions;
+    private MessagingLayout mMessagingLayout;
+    private MessagingLinearLayout mMessagingLinearLayout;
 
     protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
             ExpandableNotificationRow row) {
         super(ctx, view, row);
+        mMessagingLayout = (MessagingLayout) view;
+        mMinHeightWithActions = NotificationUtils.getFontScaledHeight(ctx,
+                R.dimen.notification_messaging_actions_min_height);
     }
 
     private void resolveViews() {
-        mContractedMessage = null;
-
-        View container = mView.findViewById(com.android.internal.R.id.notification_messaging);
-        if (container instanceof MessagingLinearLayout
-                && ((MessagingLinearLayout) container).getChildCount() > 0) {
-            MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container;
-
-            int childCount = messagingContainer.getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                View child = messagingContainer.getChildAt(i);
-
-                if (child.getVisibility() == View.GONE
-                        && child instanceof TextView
-                        && !TextUtils.isEmpty(((TextView) child).getText())) {
-                    mHistoricMessages.add(child);
-                }
-
-                // Only consider the first visible child - transforming to a position other than the
-                // first looks bad because we have to move across other messages that are fading in.
-                if (child.getId() == messagingContainer.getContractedChildId()) {
-                    mContractedMessage = child;
-                } else if (child.getVisibility() == View.VISIBLE) {
-                    break;
-                }
-            }
-        }
+        mMessagingLinearLayout = mMessagingLayout.getMessagingLinearLayout();
     }
 
     @Override
@@ -81,16 +62,22 @@
     protected void updateTransformedTypes() {
         // This also clears the existing types
         super.updateTransformedTypes();
-        if (mContractedMessage != null) {
-            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
-                    mContractedMessage);
+        if (mMessagingLinearLayout != null) {
+            mTransformationHelper.addTransformedView(mMessagingLinearLayout.getId(),
+                    mMessagingLinearLayout);
         }
     }
 
     @Override
     public void setRemoteInputVisible(boolean visible) {
-        for (int i = 0; i < mHistoricMessages.size(); i++) {
-            mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+        mMessagingLayout.showHistoricMessages(visible);
+    }
+
+    @Override
+    public int getMinLayoutHeight() {
+        if (mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE) {
+            return mMinHeightWithActions;
         }
+        return super.getMinLayoutHeight();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index bb979eb..fd085d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -41,7 +41,7 @@
     private ProgressBar mProgressBar;
     private TextView mTitle;
     private TextView mText;
-    private View mActionsContainer;
+    protected View mActionsContainer;
     private View mReplyAction;
     private Rect mTmpRect = new Rect();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index 3115361..af393c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -66,4 +66,14 @@
                 Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
     }
 
+    /**
+     * @param dimenId the dimen to look up
+     * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
+     */
+    public static int getFontScaledHeight(Context context, int dimenId) {
+        int dimensionPixelSize = context.getResources().getDimensionPixelSize(dimenId);
+        float factor = Math.max(1.0f, context.getResources().getDisplayMetrics().scaledDensity /
+                context.getResources().getDisplayMetrics().density);
+        return (int) (dimensionPixelSize * factor);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 5200d69..1cd5f15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -190,4 +190,8 @@
     public boolean disallowSingleClick(float x, float y) {
         return false;
     }
+
+    public int getMinLayoutHeight() {
+        return 0;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
index c4aabe4..178c813 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
@@ -33,8 +33,8 @@
     private TextView mText;
 
     @Override
-    public void initFrom(View view) {
-        super.initFrom(view);
+    public void initFrom(View view, TransformInfo transformInfo) {
+        super.initFrom(view, transformInfo);
         if (view instanceof TextView) {
             mText = (TextView) view;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index bafedff..ad07af0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -26,6 +26,8 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
@@ -43,23 +45,46 @@
     public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
 
     private static final float UNDEFINED = -1f;
-    private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
-    private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
-    private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
     private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag;
     private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag;
     private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag;
     private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag;
     private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
+    private static ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS
+            = new ViewClippingUtil.ClippingParameters() {
+        @Override
+        public boolean shouldFinish(View view) {
+            if (view instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+                return !row.isChildInGroup();
+            }
+            return false;
+        }
+
+        @Override
+        public void onClippingStateChanged(View view, boolean isClipping) {
+            if (view instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+                if (isClipping) {
+                    row.setClipToActualHeight(true);
+                } else if (row.isChildInGroup()) {
+                    row.setClipToActualHeight(false);
+                }
+            }
+        }
+    };
 
     protected View mTransformedView;
+    protected TransformInfo mTransformInfo;
     private int[] mOwnPosition = new int[2];
     private boolean mSameAsAny;
     private float mTransformationEndY = UNDEFINED;
     private float mTransformationEndX = UNDEFINED;
+    private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
 
-    public void initFrom(View view) {
+    public void initFrom(View view, TransformInfo transformInfo) {
         mTransformedView = view;
+        mTransformInfo = transformInfo;
     }
 
     /**
@@ -108,13 +133,16 @@
         final View transformedView = mTransformedView;
         boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
         boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
-        boolean transformScale = transformScale(otherState);
+        boolean differentHeight = otherState.getViewHeight() != getViewHeight();
+        boolean differentWidth = otherState.getViewWidth() != getViewWidth();
+        boolean transformScale = transformScale(otherState) && (differentHeight || differentWidth);
         // lets animate the positions correctly
         if (transformationAmount == 0.0f
                 || transformX && getTransformationStartX() == UNDEFINED
                 || transformY && getTransformationStartY() == UNDEFINED
-                || transformScale && getTransformationStartScaleX() == UNDEFINED
-                || transformScale && getTransformationStartScaleY() == UNDEFINED) {
+                || transformScale && getTransformationStartScaleX() == UNDEFINED && differentWidth
+                || transformScale && getTransformationStartScaleY() == UNDEFINED
+                        && differentHeight) {
             int[] otherPosition;
             if (transformationAmount != 0.0f) {
                 otherPosition = otherState.getLaidOutLocationOnScreen();
@@ -132,14 +160,14 @@
                 }
                 // we also want to animate the scale if we're the same
                 View otherView = otherState.getTransformedView();
-                if (transformScale && otherState.getViewWidth() != getViewWidth()) {
+                if (transformScale && differentWidth) {
                     setTransformationStartScaleX(otherState.getViewWidth() * otherView.getScaleX()
                             / (float) getViewWidth());
                     transformedView.setPivotX(0);
                 } else {
                     setTransformationStartScaleX(UNDEFINED);
                 }
-                if (transformScale && otherState.getViewHeight() != getViewHeight()) {
+                if (transformScale && differentHeight) {
                     setTransformationStartScaleY(otherState.getViewHeight() * otherView.getScaleY()
                             / (float) getViewHeight());
                     transformedView.setPivotY(0);
@@ -159,7 +187,7 @@
             }
             setClippingDeactivated(transformedView, true);
         }
-        float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+        float interpolatedValue = mDefaultInterpolator.getInterpolation(
                 transformationAmount);
         if (transformX) {
             float interpolation = interpolatedValue;
@@ -297,7 +325,7 @@
             }
             setClippingDeactivated(transformedView, true);
         }
-        float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+        float interpolatedValue = mDefaultInterpolator.getInterpolation(
                 transformationAmount);
         int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
         int[] ownPosition = getLaidOutLocationOnScreen();
@@ -354,59 +382,8 @@
         }
     }
 
-    public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
-        if (!(transformedView.getParent() instanceof ViewGroup)) {
-            return;
-        }
-        ViewGroup view = (ViewGroup) transformedView.getParent();
-        while (true) {
-            ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
-            if (clipSet == null) {
-                clipSet = new ArraySet<>();
-                view.setTag(CLIP_CLIPPING_SET, clipSet);
-            }
-            Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
-            if (clipChildren == null) {
-                clipChildren = view.getClipChildren();
-                view.setTag(CLIP_CHILDREN_TAG, clipChildren);
-            }
-            Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
-            if (clipToPadding == null) {
-                clipToPadding = view.getClipToPadding();
-                view.setTag(CLIP_TO_PADDING, clipToPadding);
-            }
-            ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
-                    ? (ExpandableNotificationRow) view
-                    : null;
-            if (!deactivated) {
-                clipSet.remove(transformedView);
-                if (clipSet.isEmpty()) {
-                    view.setClipChildren(clipChildren);
-                    view.setClipToPadding(clipToPadding);
-                    view.setTag(CLIP_CLIPPING_SET, null);
-                    if (row != null) {
-                        row.setClipToActualHeight(true);
-                    }
-                }
-            } else {
-                clipSet.add(transformedView);
-                view.setClipChildren(false);
-                view.setClipToPadding(false);
-                if (row != null && row.isChildInGroup()) {
-                    // We still want to clip to the parent's height
-                    row.setClipToActualHeight(false);
-                }
-            }
-            if (row != null && !row.isChildInGroup()) {
-                return;
-            }
-            final ViewParent parent = view.getParent();
-            if (parent instanceof ViewGroup) {
-                view = (ViewGroup) parent;
-            } else {
-                return;
-            }
-        }
+    protected void setClippingDeactivated(final View transformedView, boolean deactivated) {
+        ViewClippingUtil.setClippingDeactivated(transformedView, deactivated, CLIPPING_PARAMETERS);
     }
 
     public int[] getLaidOutLocationOnScreen() {
@@ -423,6 +400,9 @@
         // remove scale
         mOwnPosition[0] -= (1.0f - mTransformedView.getScaleX()) * mTransformedView.getPivotX();
         mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY();
+
+        // Remove local translations
+        mOwnPosition[1] -= MessagingPropertyAnimator.getLocalTranslationY(mTransformedView);
         return mOwnPosition;
     }
 
@@ -444,20 +424,26 @@
         CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
     }
 
-    public static TransformState createFrom(View view) {
+    public static TransformState createFrom(View view,
+            TransformInfo transformInfo) {
         if (view instanceof TextView) {
             TextViewTransformState result = TextViewTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
             return result;
         }
         if (view.getId() == com.android.internal.R.id.actions_container) {
             ActionListTransformState result = ActionListTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
+            return result;
+        }
+        if (view.getId() == com.android.internal.R.id.notification_messaging) {
+            MessagingLayoutTransformState result = MessagingLayoutTransformState.obtain();
+            result.initFrom(view, transformInfo);
             return result;
         }
         if (view instanceof ImageView) {
             ImageTransformState result = ImageTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
             if (view.getId() == com.android.internal.R.id.reply_icon_action) {
                 ((TransformState) result).setIsSameAsAnyView(true);
             }
@@ -465,15 +451,15 @@
         }
         if (view instanceof ProgressBar) {
             ProgressTransformState result = ProgressTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
             return result;
         }
         TransformState result = obtain();
-        result.initFrom(view);
+        result.initFrom(view, transformInfo);
         return result;
     }
 
-    private void setIsSameAsAnyView(boolean sameAsAny) {
+    public void setIsSameAsAnyView(boolean sameAsAny) {
         mSameAsAny = sameAsAny;
     }
 
@@ -533,6 +519,7 @@
         mSameAsAny = false;
         mTransformationEndX = UNDEFINED;
         mTransformationEndY = UNDEFINED;
+        mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
     }
 
     public void setVisible(boolean visible, boolean force) {
@@ -578,4 +565,12 @@
     public View getTransformedView() {
         return mTransformedView;
     }
+
+    public void setDefaultInterpolator(Interpolator interpolator) {
+        mDefaultInterpolator = interpolator;
+    }
+
+    public interface TransformInfo {
+        boolean isAnimating();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index d226fed..1c361ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -175,9 +175,8 @@
     private boolean isLight(int vis, int barMode, int flag) {
         boolean isTransparentBar = (barMode == MODE_TRANSPARENT
                 || barMode == MODE_LIGHTS_OUT_TRANSPARENT);
-        boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave();
         boolean light = (vis & flag) != 0;
-        return allowLight && light;
+        return isTransparentBar && light;
     }
 
     private boolean animateChange() {
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 4a58c6d..46e3aa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -146,6 +146,8 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingMessage;
 import com.android.keyguard.KeyguardHostView.OnDismissAction;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -273,7 +275,7 @@
     public static final boolean ENABLE_CHILD_NOTIFICATIONS
             = SystemProperties.getBoolean("debug.child_notifs", true);
     public static final boolean FORCE_REMOTE_INPUT_HISTORY =
-            SystemProperties.getBoolean("debug.force_remoteinput_history", false);
+            SystemProperties.getBoolean("debug.force_remoteinput_history", true);
     private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
 
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -1232,6 +1234,8 @@
     }
 
     public void onDensityOrFontScaleChanged() {
+        MessagingMessage.dropCache();
+        MessagingGroup.dropCache();
         // start old BaseStatusBar.onDensityOrFontScaleChanged().
         if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
             updateNotificationsOnDensityOrFontScaleChanged();
@@ -1685,8 +1689,9 @@
             clearCurrentMediaNotification();
             updateMediaMetaData(true, true);
         }
-        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) {
-            Entry entry = mNotificationData.get(key);
+        Entry entry = mNotificationData.get(key);
+        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)
+                && entry.row != null && !entry.row.isDismissed()) {
             StatusBarNotification sbn = entry.notification;
 
             Notification.Builder b = Notification.Builder
@@ -1722,6 +1727,7 @@
                 deferRemoval = false;
             }
             if (updated) {
+                Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
                 mKeysKeptForRemoteInput.add(entry.key);
                 return;
             }
@@ -1731,7 +1737,6 @@
             mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
             return;
         }
-        Entry entry = mNotificationData.get(key);
 
         if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
                 && (entry.row != null && !entry.row.isDismissed())) {
@@ -3278,12 +3283,8 @@
     }
 
     void checkBarMode(int mode, int windowState, BarTransitions transitions) {
-        final boolean powerSave = mBatteryController.isPowerSave();
         final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive
-                && windowState != WINDOW_STATE_HIDDEN && !powerSave;
-        if (powerSave && getBarState() == StatusBarState.SHADE) {
-            mode = MODE_WARNING;
-        }
+                && windowState != WINDOW_STATE_HIDDEN;
         transitions.transitionTo(mode, anim);
     }
 
@@ -7117,7 +7118,7 @@
         mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
     }
 
-    private void updateLockscreenNotificationSetting() {
+    protected void updateLockscreenNotificationSetting() {
         final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                 1,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
index 720e9f0..a17a95f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
@@ -70,7 +70,7 @@
     @Test
     public void forwards_setDozeScreenState_doze_suspend() throws Exception {
         mWrapper.setDozeScreenState(Display.STATE_DOZE_SUSPEND);
-        verify(mInner).setDozeScreenState(Display.STATE_ON);
+        verify(mInner).setDozeScreenState(Display.STATE_ON_SUSPEND);
     }
 
     @Test
@@ -95,4 +95,4 @@
 
         assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
index d78e739..ed93561 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
@@ -74,6 +74,12 @@
     }
 
     @Test
+    public void forwards_setDozeScreenState_on_suspend() throws Exception {
+        mWrapper.setDozeScreenState(Display.STATE_ON_SUSPEND);
+        verify(mInner).setDozeScreenState(Display.STATE_ON_SUSPEND);
+    }
+
+    @Test
     public void forwards_requestWakeUp() throws Exception {
         mWrapper.requestWakeUp();
         verify(mInner).requestWakeUp();
@@ -95,4 +101,4 @@
 
         assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
new file mode 100644
index 0000000..4437aac
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.keyguard;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+public class KeyguardSliceProviderTest extends SysuiTestCase {
+
+    private TestableKeyguardSliceProvider mProvider;
+
+    @Before
+    public void setup() {
+        mProvider = new TestableKeyguardSliceProvider();
+        mProvider.attachInfo(getContext(), null);
+    }
+
+    @Test
+    public void registersClockUpdate() {
+        Assert.assertTrue("registerClockUpdate should have been called during initialization.",
+                mProvider.isRegistered());
+    }
+
+    @Test
+    public void unregisterClockUpdate() {
+        mProvider.unregisterClockUpdate();
+        Assert.assertFalse("Clock updates should have been unregistered.",
+                mProvider.isRegistered());
+    }
+
+    @Test
+    public void returnsValidSlice() {
+        Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
+        SliceItem text = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+                null /* nonHints */);
+        Assert.assertNotNull("Slice must provide a title.", text);
+    }
+
+    @Test
+    public void cleansDateFormat() {
+        mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIMEZONE_CHANGED));
+        TestableLooper.get(this).processAllMessages();
+        Assert.assertEquals("Date format should have been cleaned.", 1 /* expected */,
+                mProvider.mCleanDateFormatInvokations);
+    }
+
+    @Test
+    public void updatesClock() {
+        mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK));
+        TestableLooper.get(this).processAllMessages();
+        Assert.assertEquals("Clock should have been updated.", 2 /* expected */,
+                mProvider.mUpdateClockInvokations);
+    }
+
+    private class TestableKeyguardSliceProvider extends KeyguardSliceProvider {
+        int mCleanDateFormatInvokations;
+        int mUpdateClockInvokations;
+
+        TestableKeyguardSliceProvider() {
+            super(new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper()));
+        }
+
+        @Override
+        void cleanDateFormat() {
+            super.cleanDateFormat();
+            mCleanDateFormatInvokations++;
+        }
+
+        @Override
+        protected void updateClock() {
+            super.updateClock();
+            mUpdateClockInvokations++;
+        }
+    }
+
+}
diff --git a/proto/src/ipconnectivity.proto b/proto/src/ipconnectivity.proto
index 437da8f..327f17d 100644
--- a/proto/src/ipconnectivity.proto
+++ b/proto/src/ipconnectivity.proto
@@ -50,9 +50,10 @@
   optional int32 value = 2;
 };
 
-// An event record when the system default network disconnects or the system
-// switches to a new default network.
-// Next tag: 10.
+// An event recorded when the system default network disconnects or the system
+// switches to a new default network. An event is also recorded to cover gaps
+// without a default network.
+// Next tag: 12
 message DefaultNetworkEvent {
 
   // Reason why this network stopped being the default.
@@ -72,26 +73,34 @@
   };
 
   // Duration in milliseconds when this network was the default.
-  // Since version 4
+  // Since P.
   optional int64 default_network_duration_ms = 5;
 
-  // Duration in milliseconds without a default network before this network
-  // became the default.
-  // Since version 4
-  optional int64 no_default_network_duration_ms = 6;
+  // Duration in milliseconds when this default network Internet access was
+  // validated. This field is equal to 0 for DefaultNetworkEvents representing
+  // lack of a default network.
+  // Since P.
+  optional int64 validation_duration_ms = 11;
 
   // Network score of this network when it became the default network.
-  // Since version 4
+  // Or 0 if this event represents a period without a default network.
+  // Since P.
   optional int64 initial_score = 7;
 
   // Network score of this network when it stopped being the default network.
-  // Since version 4
+  // Or 0 if this event represents a period without a default network.
+  // Since P.
   optional int64 final_score = 8;
 
   // Best available information about IP support of this default network.
-  // Since version 4
+  // Or NONE if this event represents a period without a default network.
+  // Since P.
   optional IPSupport ip_support = 9;
 
+  // LinkLayer of the previous default network. Ignores any previous period
+  // without a default network.
+  // Since P
+  optional LinkLayer previous_default_network_link_layer = 10;
 
   // Deprecated fields
 
@@ -112,6 +121,11 @@
   // TRANSPORT_* constants as defined in NetworkCapabilities.
   // Deprecated since version 3. Replaced by top-level transports field.
   repeated int32 transport_types = 4 [deprecated = true];
+
+  // Duration in milliseconds without a default network. This field is non-zero
+  // only for DefaultNetworkEvents representing lack of a default network.
+  // Since P.
+  optional int64 no_default_network_duration_ms = 6 [deprecated = true];
 };
 
 // Logs IpReachabilityMonitor probe events and NUD_FAILED events.
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index a3c927a..d3dcf5a 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4443,6 +4443,10 @@
     // OS: O MR
     COLOR_MODE_SETTINGS = 1143;
 
+    // Enclosing category for group of APP_TRANSITION_FOO events,
+    // logged when we cancel an app transition.
+    APP_TRANSITION_CANCELLED = 1144;
+
     // ---- End O-MR1 Constants, all O-MR1 constants go above this line ----
 
     // OPEN: Settings > Network & Internet > Mobile network
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 1b61866..ea0ed27 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -38,7 +38,7 @@
 import android.content.pm.PackageManager;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
-import android.hardware.health.V2_0.HealthInfo;
+import android.hardware.health.V1_0.HealthInfo;
 import android.hardware.health.V2_0.IHealthInfoCallback;
 import android.hardware.health.V2_0.IHealth;
 import android.hardware.health.V2_0.Result;
@@ -49,6 +49,7 @@
 import android.os.Binder;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBatteryPropertiesListener;
 import android.os.IBatteryPropertiesRegistrar;
 import android.os.IBinder;
@@ -75,6 +76,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -176,6 +178,7 @@
     private HealthServiceWrapper mHealthServiceWrapper;
     private HealthHalCallback mHealthHalCallback;
     private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+    private HandlerThread mHandlerThread;
 
     public BatteryService(Context context) {
         super(context);
@@ -308,16 +311,16 @@
     private boolean isPoweredLocked(int plugTypeSet) {
         // assume we are powered if battery state is unknown so
         // the "stay on while plugged in" option will work.
-        if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+        if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.legacy.chargerAcOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.chargerAcOnline) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.legacy.chargerUsbOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.chargerUsbOnline) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.legacy.chargerWirelessOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.chargerWirelessOnline) {
             return true;
         }
         return false;
@@ -334,15 +337,15 @@
          *   (becomes <= mLowBatteryWarningLevel).
          */
         return !plugged
-                && mHealthInfo.legacy.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
-                && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel
+                && mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+                && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel
                 && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
     }
 
     private void shutdownIfNoPowerLocked() {
         // shut down gracefully if our battery is critically low and we are not powered.
         // wait until the system has booted before attempting to display the shutdown dialog.
-        if (mHealthInfo.legacy.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
+        if (mHealthInfo.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
@@ -363,7 +366,7 @@
         // shut down gracefully if temperature is too high (> 68.0C by default)
         // wait until the system has booted before attempting to display the
         // shutdown dialog.
-        if (mHealthInfo.legacy.batteryTemperature > mShutdownBatteryTemperature) {
+        if (mHealthInfo.batteryTemperature > mShutdownBatteryTemperature) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
@@ -396,37 +399,34 @@
     }
 
     private static void copy(HealthInfo dst, HealthInfo src) {
-        dst.legacy.chargerAcOnline = src.legacy.chargerAcOnline;
-        dst.legacy.chargerUsbOnline = src.legacy.chargerUsbOnline;
-        dst.legacy.chargerWirelessOnline = src.legacy.chargerWirelessOnline;
-        dst.legacy.maxChargingCurrent = src.legacy.maxChargingCurrent;
-        dst.legacy.maxChargingVoltage = src.legacy.maxChargingVoltage;
-        dst.legacy.batteryStatus = src.legacy.batteryStatus;
-        dst.legacy.batteryHealth = src.legacy.batteryHealth;
-        dst.legacy.batteryPresent = src.legacy.batteryPresent;
-        dst.legacy.batteryLevel = src.legacy.batteryLevel;
-        dst.legacy.batteryVoltage = src.legacy.batteryVoltage;
-        dst.legacy.batteryTemperature = src.legacy.batteryTemperature;
-        dst.legacy.batteryCurrent = src.legacy.batteryCurrent;
-        dst.legacy.batteryCycleCount = src.legacy.batteryCycleCount;
-        dst.legacy.batteryFullCharge = src.legacy.batteryFullCharge;
-        dst.legacy.batteryChargeCounter = src.legacy.batteryChargeCounter;
-        dst.legacy.batteryTechnology = src.legacy.batteryTechnology;
-        dst.batteryCurrentAverage = src.batteryCurrentAverage;
-        dst.batteryCapacity = src.batteryCapacity;
-        dst.energyCounter = src.energyCounter;
+        dst.chargerAcOnline = src.chargerAcOnline;
+        dst.chargerUsbOnline = src.chargerUsbOnline;
+        dst.chargerWirelessOnline = src.chargerWirelessOnline;
+        dst.maxChargingCurrent = src.maxChargingCurrent;
+        dst.maxChargingVoltage = src.maxChargingVoltage;
+        dst.batteryStatus = src.batteryStatus;
+        dst.batteryHealth = src.batteryHealth;
+        dst.batteryPresent = src.batteryPresent;
+        dst.batteryLevel = src.batteryLevel;
+        dst.batteryVoltage = src.batteryVoltage;
+        dst.batteryTemperature = src.batteryTemperature;
+        dst.batteryCurrent = src.batteryCurrent;
+        dst.batteryCycleCount = src.batteryCycleCount;
+        dst.batteryFullCharge = src.batteryFullCharge;
+        dst.batteryChargeCounter = src.batteryChargeCounter;
+        dst.batteryTechnology = src.batteryTechnology;
     }
 
     private void processValuesLocked(boolean force) {
         boolean logOutlier = false;
         long dischargeDuration = 0;
 
-        mBatteryLevelCritical = (mHealthInfo.legacy.batteryLevel <= mCriticalBatteryLevel);
-        if (mHealthInfo.legacy.chargerAcOnline) {
+        mBatteryLevelCritical = (mHealthInfo.batteryLevel <= mCriticalBatteryLevel);
+        if (mHealthInfo.chargerAcOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
-        } else if (mHealthInfo.legacy.chargerUsbOnline) {
+        } else if (mHealthInfo.chargerUsbOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
-        } else if (mHealthInfo.legacy.chargerWirelessOnline) {
+        } else if (mHealthInfo.chargerWirelessOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
         } else {
             mPlugType = BATTERY_PLUGGED_NONE;
@@ -441,10 +441,10 @@
 
         // Let the battery stats keep track of the current level.
         try {
-            mBatteryStats.setBatteryState(mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth,
-                    mPlugType, mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryTemperature,
-                    mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryChargeCounter,
-                    mHealthInfo.legacy.batteryFullCharge);
+            mBatteryStats.setBatteryState(mHealthInfo.batteryStatus, mHealthInfo.batteryHealth,
+                    mPlugType, mHealthInfo.batteryLevel, mHealthInfo.batteryTemperature,
+                    mHealthInfo.batteryVoltage, mHealthInfo.batteryChargeCounter,
+                    mHealthInfo.batteryFullCharge);
         } catch (RemoteException e) {
             // Should never happen.
         }
@@ -452,16 +452,16 @@
         shutdownIfNoPowerLocked();
         shutdownIfOverTempLocked();
 
-        if (force || (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
-                mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
-                mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
-                mHealthInfo.legacy.batteryLevel != mLastBatteryLevel ||
+        if (force || (mHealthInfo.batteryStatus != mLastBatteryStatus ||
+                mHealthInfo.batteryHealth != mLastBatteryHealth ||
+                mHealthInfo.batteryPresent != mLastBatteryPresent ||
+                mHealthInfo.batteryLevel != mLastBatteryLevel ||
                 mPlugType != mLastPlugType ||
-                mHealthInfo.legacy.batteryVoltage != mLastBatteryVoltage ||
-                mHealthInfo.legacy.batteryTemperature != mLastBatteryTemperature ||
-                mHealthInfo.legacy.maxChargingCurrent != mLastMaxChargingCurrent ||
-                mHealthInfo.legacy.maxChargingVoltage != mLastMaxChargingVoltage ||
-                mHealthInfo.legacy.batteryChargeCounter != mLastChargeCounter ||
+                mHealthInfo.batteryVoltage != mLastBatteryVoltage ||
+                mHealthInfo.batteryTemperature != mLastBatteryTemperature ||
+                mHealthInfo.maxChargingCurrent != mLastMaxChargingCurrent ||
+                mHealthInfo.maxChargingVoltage != mLastMaxChargingVoltage ||
+                mHealthInfo.batteryChargeCounter != mLastChargeCounter ||
                 mInvalidCharger != mLastInvalidCharger)) {
 
             if (mPlugType != mLastPlugType) {
@@ -470,33 +470,33 @@
 
                     // There's no value in this data unless we've discharged at least once and the
                     // battery level has changed; so don't log until it does.
-                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.legacy.batteryLevel) {
+                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.batteryLevel) {
                         dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
                         logOutlier = true;
                         EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
-                                mDischargeStartLevel, mHealthInfo.legacy.batteryLevel);
+                                mDischargeStartLevel, mHealthInfo.batteryLevel);
                         // make sure we see a discharge event before logging again
                         mDischargeStartTime = 0;
                     }
                 } else if (mPlugType == BATTERY_PLUGGED_NONE) {
                     // charging -> discharging or we just powered up
                     mDischargeStartTime = SystemClock.elapsedRealtime();
-                    mDischargeStartLevel = mHealthInfo.legacy.batteryLevel;
+                    mDischargeStartLevel = mHealthInfo.batteryLevel;
                 }
             }
-            if (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
-                    mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
-                    mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
+            if (mHealthInfo.batteryStatus != mLastBatteryStatus ||
+                    mHealthInfo.batteryHealth != mLastBatteryHealth ||
+                    mHealthInfo.batteryPresent != mLastBatteryPresent ||
                     mPlugType != mLastPlugType) {
                 EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
-                        mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth, mHealthInfo.legacy.batteryPresent ? 1 : 0,
-                        mPlugType, mHealthInfo.legacy.batteryTechnology);
+                        mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0,
+                        mPlugType, mHealthInfo.batteryTechnology);
             }
-            if (mHealthInfo.legacy.batteryLevel != mLastBatteryLevel) {
+            if (mHealthInfo.batteryLevel != mLastBatteryLevel) {
                 // Don't do this just from voltage or temperature changes, that is
                 // too noisy.
                 EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
-                        mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryTemperature);
+                        mHealthInfo.batteryLevel, mHealthInfo.batteryVoltage, mHealthInfo.batteryTemperature);
             }
             if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
                     mPlugType == BATTERY_PLUGGED_NONE) {
@@ -509,16 +509,16 @@
             if (!mBatteryLevelLow) {
                 // Should we now switch in to low battery mode?
                 if (mPlugType == BATTERY_PLUGGED_NONE
-                        && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel) {
+                        && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) {
                     mBatteryLevelLow = true;
                 }
             } else {
                 // Should we now switch out of low battery mode?
                 if (mPlugType != BATTERY_PLUGGED_NONE) {
                     mBatteryLevelLow = false;
-                } else if (mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel)  {
+                } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel)  {
                     mBatteryLevelLow = false;
-                } else if (force && mHealthInfo.legacy.batteryLevel >= mLowBatteryWarningLevel) {
+                } else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) {
                     // If being forced, the previous state doesn't matter, we will just
                     // absolutely check to see if we are now above the warning level.
                     mBatteryLevelLow = false;
@@ -565,7 +565,7 @@
                     }
                 });
             } else if (mSentLowBatteryBroadcast &&
-                    mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
+                    mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
                 mSentLowBatteryBroadcast = false;
                 final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -591,16 +591,16 @@
                 logOutlierLocked(dischargeDuration);
             }
 
-            mLastBatteryStatus = mHealthInfo.legacy.batteryStatus;
-            mLastBatteryHealth = mHealthInfo.legacy.batteryHealth;
-            mLastBatteryPresent = mHealthInfo.legacy.batteryPresent;
-            mLastBatteryLevel = mHealthInfo.legacy.batteryLevel;
+            mLastBatteryStatus = mHealthInfo.batteryStatus;
+            mLastBatteryHealth = mHealthInfo.batteryHealth;
+            mLastBatteryPresent = mHealthInfo.batteryPresent;
+            mLastBatteryLevel = mHealthInfo.batteryLevel;
             mLastPlugType = mPlugType;
-            mLastBatteryVoltage = mHealthInfo.legacy.batteryVoltage;
-            mLastBatteryTemperature = mHealthInfo.legacy.batteryTemperature;
-            mLastMaxChargingCurrent = mHealthInfo.legacy.maxChargingCurrent;
-            mLastMaxChargingVoltage = mHealthInfo.legacy.maxChargingVoltage;
-            mLastChargeCounter = mHealthInfo.legacy.batteryChargeCounter;
+            mLastBatteryVoltage = mHealthInfo.batteryVoltage;
+            mLastBatteryTemperature = mHealthInfo.batteryTemperature;
+            mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrent;
+            mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltage;
+            mLastChargeCounter = mHealthInfo.batteryChargeCounter;
             mLastBatteryLevelCritical = mBatteryLevelCritical;
             mLastInvalidCharger = mInvalidCharger;
         }
@@ -612,23 +612,23 @@
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                 | Intent.FLAG_RECEIVER_REPLACE_PENDING);
 
-        int icon = getIconLocked(mHealthInfo.legacy.batteryLevel);
+        int icon = getIconLocked(mHealthInfo.batteryLevel);
 
         intent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
-        intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.legacy.batteryStatus);
-        intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.legacy.batteryHealth);
-        intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.legacy.batteryPresent);
-        intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.legacy.batteryLevel);
+        intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.batteryStatus);
+        intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.batteryHealth);
+        intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.batteryPresent);
+        intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.batteryLevel);
         intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
         intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
-        intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.legacy.batteryVoltage);
-        intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
-        intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
+        intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.batteryVoltage);
+        intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperature);
+        intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.batteryTechnology);
         intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
-        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
-        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
-        intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
+        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent);
+        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage);
+        intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
         if (DEBUG) {
             Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
                     + ", info:" + mHealthInfo.toString());
@@ -692,14 +692,14 @@
                 long durationThreshold = Long.parseLong(durationThresholdString);
                 int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
                 if (duration <= durationThreshold &&
-                        mDischargeStartLevel - mHealthInfo.legacy.batteryLevel >= dischargeThreshold) {
+                        mDischargeStartLevel - mHealthInfo.batteryLevel >= dischargeThreshold) {
                     // If the discharge cycle is bad enough we want to know about it.
                     logBatteryStatsLocked();
                 }
                 if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
                         " discharge threshold: " + dischargeThreshold);
                 if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
-                        (mDischargeStartLevel - mHealthInfo.legacy.batteryLevel));
+                        (mDischargeStartLevel - mHealthInfo.batteryLevel));
             } catch (NumberFormatException e) {
                 Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
                         durationThresholdString + " or " + dischargeThresholdString);
@@ -708,14 +708,14 @@
     }
 
     private int getIconLocked(int level) {
-        if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
+        if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
             return com.android.internal.R.drawable.stat_sys_battery_charge;
-        } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+        } else if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
             return com.android.internal.R.drawable.stat_sys_battery;
-        } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
-                || mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+        } else if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
+                || mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
             if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
-                    && mHealthInfo.legacy.batteryLevel >= 100) {
+                    && mHealthInfo.batteryLevel >= 100) {
                 return com.android.internal.R.drawable.stat_sys_battery_charge;
             } else {
                 return com.android.internal.R.drawable.stat_sys_battery;
@@ -779,9 +779,9 @@
                 if (!mUpdatesStopped) {
                     copy(mLastHealthInfo, mHealthInfo);
                 }
-                mHealthInfo.legacy.chargerAcOnline = false;
-                mHealthInfo.legacy.chargerUsbOnline = false;
-                mHealthInfo.legacy.chargerWirelessOnline = false;
+                mHealthInfo.chargerAcOnline = false;
+                mHealthInfo.chargerUsbOnline = false;
+                mHealthInfo.chargerWirelessOnline = false;
                 long ident = Binder.clearCallingIdentity();
                 try {
                     mUpdatesStopped = true;
@@ -813,25 +813,25 @@
                     boolean update = true;
                     switch (key) {
                         case "present":
-                            mHealthInfo.legacy.batteryPresent = Integer.parseInt(value) != 0;
+                            mHealthInfo.batteryPresent = Integer.parseInt(value) != 0;
                             break;
                         case "ac":
-                            mHealthInfo.legacy.chargerAcOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.chargerAcOnline = Integer.parseInt(value) != 0;
                             break;
                         case "usb":
-                            mHealthInfo.legacy.chargerUsbOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.chargerUsbOnline = Integer.parseInt(value) != 0;
                             break;
                         case "wireless":
-                            mHealthInfo.legacy.chargerWirelessOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.chargerWirelessOnline = Integer.parseInt(value) != 0;
                             break;
                         case "status":
-                            mHealthInfo.legacy.batteryStatus = Integer.parseInt(value);
+                            mHealthInfo.batteryStatus = Integer.parseInt(value);
                             break;
                         case "level":
-                            mHealthInfo.legacy.batteryLevel = Integer.parseInt(value);
+                            mHealthInfo.batteryLevel = Integer.parseInt(value);
                             break;
                         case "temp":
-                            mHealthInfo.legacy.batteryTemperature = Integer.parseInt(value);
+                            mHealthInfo.batteryTemperature = Integer.parseInt(value);
                             break;
                         case "invalid":
                             mInvalidCharger = Integer.parseInt(value);
@@ -890,20 +890,20 @@
                 if (mUpdatesStopped) {
                     pw.println("  (UPDATES STOPPED -- use 'reset' to restart)");
                 }
-                pw.println("  AC powered: " + mHealthInfo.legacy.chargerAcOnline);
-                pw.println("  USB powered: " + mHealthInfo.legacy.chargerUsbOnline);
-                pw.println("  Wireless powered: " + mHealthInfo.legacy.chargerWirelessOnline);
-                pw.println("  Max charging current: " + mHealthInfo.legacy.maxChargingCurrent);
-                pw.println("  Max charging voltage: " + mHealthInfo.legacy.maxChargingVoltage);
-                pw.println("  Charge counter: " + mHealthInfo.legacy.batteryChargeCounter);
-                pw.println("  status: " + mHealthInfo.legacy.batteryStatus);
-                pw.println("  health: " + mHealthInfo.legacy.batteryHealth);
-                pw.println("  present: " + mHealthInfo.legacy.batteryPresent);
-                pw.println("  level: " + mHealthInfo.legacy.batteryLevel);
+                pw.println("  AC powered: " + mHealthInfo.chargerAcOnline);
+                pw.println("  USB powered: " + mHealthInfo.chargerUsbOnline);
+                pw.println("  Wireless powered: " + mHealthInfo.chargerWirelessOnline);
+                pw.println("  Max charging current: " + mHealthInfo.maxChargingCurrent);
+                pw.println("  Max charging voltage: " + mHealthInfo.maxChargingVoltage);
+                pw.println("  Charge counter: " + mHealthInfo.batteryChargeCounter);
+                pw.println("  status: " + mHealthInfo.batteryStatus);
+                pw.println("  health: " + mHealthInfo.batteryHealth);
+                pw.println("  present: " + mHealthInfo.batteryPresent);
+                pw.println("  level: " + mHealthInfo.batteryLevel);
                 pw.println("  scale: " + BATTERY_SCALE);
-                pw.println("  voltage: " + mHealthInfo.legacy.batteryVoltage);
-                pw.println("  temperature: " + mHealthInfo.legacy.batteryTemperature);
-                pw.println("  technology: " + mHealthInfo.legacy.batteryTechnology);
+                pw.println("  voltage: " + mHealthInfo.batteryVoltage);
+                pw.println("  temperature: " + mHealthInfo.batteryTemperature);
+                pw.println("  technology: " + mHealthInfo.batteryTechnology);
             } else {
                 Shell shell = new Shell();
                 shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -917,25 +917,25 @@
         synchronized (mLock) {
             proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped);
             int batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_NONE;
-            if (mHealthInfo.legacy.chargerAcOnline) {
+            if (mHealthInfo.chargerAcOnline) {
                 batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_AC;
-            } else if (mHealthInfo.legacy.chargerUsbOnline) {
+            } else if (mHealthInfo.chargerUsbOnline) {
                 batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_USB;
-            } else if (mHealthInfo.legacy.chargerWirelessOnline) {
+            } else if (mHealthInfo.chargerWirelessOnline) {
                 batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_WIRELESS;
             }
             proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue);
-            proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
-            proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
-            proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
-            proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.legacy.batteryStatus);
-            proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.legacy.batteryHealth);
-            proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.legacy.batteryPresent);
-            proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.legacy.batteryLevel);
+            proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent);
+            proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage);
+            proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
+            proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.batteryStatus);
+            proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.batteryHealth);
+            proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.batteryPresent);
+            proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.batteryLevel);
             proto.write(BatteryServiceDumpProto.SCALE, BATTERY_SCALE);
-            proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.legacy.batteryVoltage);
-            proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
-            proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
+            proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.batteryVoltage);
+            proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.batteryTemperature);
+            proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.batteryTechnology);
         }
         proto.flush();
     }
@@ -976,8 +976,8 @@
          * Synchronize on BatteryService.
          */
         public void updateLightsLocked() {
-            final int level = mHealthInfo.legacy.batteryLevel;
-            final int status = mHealthInfo.legacy.batteryStatus;
+            final int level = mHealthInfo.batteryLevel;
+            final int status = mHealthInfo.batteryStatus;
             if (level < mLowBatteryWarningLevel) {
                 if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
                     // Solid red when battery is charging
@@ -1154,7 +1154,7 @@
         @Override
         public int getBatteryLevel() {
             synchronized (mLock) {
-                return mHealthInfo.legacy.batteryLevel;
+                return mHealthInfo.batteryLevel;
             }
         }
 
@@ -1195,14 +1195,13 @@
                 Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
 
         private final IServiceNotification mNotification = new Notification();
+        private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceRefresh");
         // These variables are fixed after init.
         private Callback mCallback;
         private IHealthSupplier mHealthSupplier;
         private String mInstanceName;
 
-        private final Object mLastServiceSetLock = new Object();
         // Last IHealth service received.
-        // set must be also be guarded with mLastServiceSetLock to ensure ordering.
         private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
 
         /**
@@ -1220,6 +1219,10 @@
          * Start monitoring registration of new IHealth services. Only instances that are in
          * {@code sAllInstances} and in device / framework manifest are used. This function should
          * only be called once.
+         *
+         * mCallback.onRegistration() is called synchronously (aka in init thread) before
+         * this method returns.
+         *
          * @throws RemoteException transaction error when talking to IServiceManager
          * @throws NoSuchElementException if one of the following cases:
          *         - No service manager;
@@ -1240,40 +1243,48 @@
             mCallback = callback;
             mHealthSupplier = healthSupplier;
 
-            traceBegin("HealthInitGetManager");
-            try {
-                manager = managerSupplier.get();
-            } finally {
-                traceEnd();
-            }
+            // Initialize mLastService and call callback for the first time (in init thread)
+            IHealth newService = null;
             for (String name : sAllInstances) {
-                traceBegin("HealthInitGetTransport_" + name);
+                traceBegin("HealthInitGetService_" + name);
                 try {
-                    if (manager.getTransport(IHealth.kInterfaceName, name) !=
-                            IServiceManager.Transport.EMPTY) {
-                        mInstanceName = name;
-                        break;
-                    }
+                    newService = healthSupplier.get(name);
+                } catch (NoSuchElementException ex) {
+                    /* ignored, handled below */
                 } finally {
                     traceEnd();
                 }
+                if (newService != null) {
+                    mInstanceName = name;
+                    mLastService.set(newService);
+                    break;
+                }
             }
 
-            if (mInstanceName == null) {
+            if (mInstanceName == null || newService == null) {
                 throw new NoSuchElementException(String.format(
                         "No IHealth service instance among %s is available. Perhaps no permission?",
                         sAllInstances.toString()));
             }
+            mCallback.onRegistration(null, newService, mInstanceName);
 
+            // Register for future service registrations
             traceBegin("HealthInitRegisterNotification");
+            mHandlerThread.start();
             try {
-                manager.registerForNotifications(IHealth.kInterfaceName, mInstanceName, mNotification);
+                managerSupplier.get().registerForNotifications(
+                        IHealth.kInterfaceName, mInstanceName, mNotification);
             } finally {
                 traceEnd();
             }
             Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName);
         }
 
+        @VisibleForTesting
+        HandlerThread getHandlerThread() {
+            return mHandlerThread;
+        }
+
         interface Callback {
             /**
              * This function is invoked asynchronously when a new and related IServiceNotification
@@ -1302,7 +1313,7 @@
          */
         interface IHealthSupplier {
             default IHealth get(String name) throws NoSuchElementException, RemoteException {
-                return IHealth.getService(name);
+                return IHealth.getService(name, true /* retry */);
             }
         }
 
@@ -1312,18 +1323,27 @@
                     boolean preexisting) {
                 if (!IHealth.kInterfaceName.equals(interfaceName)) return;
                 if (!mInstanceName.equals(instanceName)) return;
-                try {
-                    // ensures the order of multiple onRegistration on different threads.
-                    synchronized (mLastServiceSetLock) {
-                        IHealth newService = mHealthSupplier.get(instanceName);
-                        IHealth oldService = mLastService.getAndSet(newService);
-                        Slog.i(TAG, "health: new instance registered " + instanceName);
-                        mCallback.onRegistration(oldService, newService, instanceName);
+
+                // This runnable only runs on mHandlerThread and ordering is ensured, hence
+                // no locking is needed inside the runnable.
+                mHandlerThread.getThreadHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            IHealth newService = mHealthSupplier.get(mInstanceName);
+                            IHealth oldService = mLastService.getAndSet(newService);
+
+                            // preexisting may be inaccurate (race). Check for equality here.
+                            if (Objects.equals(newService, oldService)) return;
+
+                            Slog.i(TAG, "health: new instance registered " + mInstanceName);
+                            mCallback.onRegistration(oldService, newService, mInstanceName);
+                        } catch (NoSuchElementException | RemoteException ex) {
+                            Slog.e(TAG, "health: Cannot get instance '" + mInstanceName
+                                    + "': " + ex.getMessage() + ". Perhaps no permission?");
+                        }
                     }
-                } catch (NoSuchElementException | RemoteException ex) {
-                    Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " +
-                           ex.getMessage() + ". Perhaps no permission?");
-                }
+                });
             }
         }
     }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index fedf6fe..3e40dfd 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2123,9 +2123,14 @@
                         final boolean valid =
                                 (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
                         final boolean wasValidated = nai.lastValidated;
+                        final boolean wasDefault = isDefaultNetwork(nai);
                         if (DBG) log(nai.name() + " validation " + (valid ? "passed" : "failed") +
                                 (msg.obj == null ? "" : " with redirect to " + (String)msg.obj));
                         if (valid != nai.lastValidated) {
+                            if (wasDefault) {
+                                metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity(
+                                        SystemClock.elapsedRealtime(), valid);
+                            }
                             final int oldScore = nai.getCurrentScore();
                             nai.lastValidated = valid;
                             nai.everValidated |= valid;
@@ -2297,7 +2302,8 @@
                 // Let rematchAllNetworksAndRequests() below record a new default network event
                 // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence
                 // whose timestamps tell how long it takes to recover a default network.
-                metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(null, nai);
+                long now = SystemClock.elapsedRealtime();
+                metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(now, null, nai);
             }
             notifyIfacesChangedForNetworkStats();
             // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
@@ -5052,7 +5058,7 @@
             makeDefault(newNetwork);
             // Log 0 -> X and Y -> X default network transitions, where X is the new default.
             metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(
-                    newNetwork, oldDefaultNetwork);
+                    now, newNetwork, oldDefaultNetwork);
             // Have a new default network, release the transition wakelock in
             scheduleReleaseNetworkTransitionWakelock();
         }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index c60d7b0..8a15ded 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -20,6 +20,9 @@
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.SHUTDOWN;
+import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_NONE;
@@ -92,6 +95,7 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -1946,9 +1950,9 @@
     public void setDnsConfigurationForNetwork(int netId, String[] servers, String domains) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
 
-        ContentResolver resolver = mContext.getContentResolver();
+        final ContentResolver cr = mContext.getContentResolver();
 
-        int sampleValidity = Settings.Global.getInt(resolver,
+        int sampleValidity = Settings.Global.getInt(cr,
                 Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
                 DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
         if (sampleValidity < 0 || sampleValidity > 65535) {
@@ -1957,7 +1961,7 @@
             sampleValidity = DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS;
         }
 
-        int successThreshold = Settings.Global.getInt(resolver,
+        int successThreshold = Settings.Global.getInt(cr,
                 Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
                 DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
         if (successThreshold < 0 || successThreshold > 100) {
@@ -1966,9 +1970,9 @@
             successThreshold = DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT;
         }
 
-        int minSamples = Settings.Global.getInt(resolver,
+        int minSamples = Settings.Global.getInt(cr,
                 Settings.Global.DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
-        int maxSamples = Settings.Global.getInt(resolver,
+        int maxSamples = Settings.Global.getInt(cr,
                 Settings.Global.DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
         if (minSamples < 0 || minSamples > maxSamples || maxSamples > 64) {
             Slog.w(TAG, "Invalid sample count (min, max)=(" + minSamples + ", " + maxSamples +
@@ -1980,8 +1984,24 @@
 
         final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
         final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
-        final boolean useTls = Settings.Global.getInt(resolver,
-                Settings.Global.DNS_TLS_DISABLED, 0) == 0;
+        final boolean useTls = shouldUseTls(cr);
+        // TODO: Populate tlsHostname once it's decided how the hostname's IP
+        // addresses will be resolved:
+        //
+        //     [1] network-provided DNS servers are included here with the
+        //         hostname and netd will use the network-provided servers to
+        //         resolve the hostname and fix up its internal structures, or
+        //
+        //     [2] network-provided DNS servers are included here without the
+        //         hostname, the ConnectivityService layer resolves the given
+        //         hostname, and then reconfigures netd with this information.
+        //
+        // In practice, there will always be a need for ConnectivityService or
+        // the captive portal app to use the network-provided services to make
+        // some queries. This argues in favor of [1], in concert with another
+        // mechanism, perhaps setting a high bit in the netid, to indicate
+        // via existing DNS APIs which set of servers (network-provided or
+        // non-network-provided private DNS) should be queried.
         final String tlsHostname = "";
         final String[] tlsFingerprints = new String[0];
         try {
@@ -1992,6 +2012,15 @@
         }
     }
 
+    private static boolean shouldUseTls(ContentResolver cr) {
+        String privateDns = Settings.Global.getString(cr, Settings.Global.PRIVATE_DNS_MODE);
+        if (TextUtils.isEmpty(privateDns)) {
+            privateDns = PRIVATE_DNS_DEFAULT_MODE;
+        }
+        return privateDns.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC) ||
+               privateDns.startsWith(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
+    }
+
     @Override
     public void addVpnUidRanges(int netId, UidRange[] ranges) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 1924a86..a9f190c 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Build;
@@ -146,7 +148,7 @@
         SystemProperties.set(PROP_RESCUE_LEVEL, Integer.toString(level));
 
         EventLogTags.writeRescueLevel(level, triggerUid);
-        PackageManagerService.logCriticalInfo(Log.WARN, "Incremented rescue level to "
+        logCriticalInfo(Log.WARN, "Incremented rescue level to "
                 + levelToString(level) + " triggered by UID " + triggerUid);
     }
 
@@ -166,12 +168,12 @@
         try {
             executeRescueLevelInternal(context, level);
             EventLogTags.writeRescueSuccess(level);
-            PackageManagerService.logCriticalInfo(Log.DEBUG,
+            logCriticalInfo(Log.DEBUG,
                     "Finished rescue level " + levelToString(level));
         } catch (Throwable t) {
             final String msg = ExceptionUtils.getCompleteMessage(t);
             EventLogTags.writeRescueFailure(level, msg);
-            PackageManagerService.logCriticalInfo(Log.ERROR,
+            logCriticalInfo(Log.ERROR,
                     "Failed rescue level " + levelToString(level) + ": " + msg);
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index ceb2ad6..0a7d3fd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -92,6 +92,7 @@
     static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false;
     static final boolean DEBUG_PERMISSIONS_REVIEW = DEBUG_ALL || false;
     static final boolean DEBUG_WHITELISTS = DEBUG_ALL || false;
+    static final boolean DEBUG_METRICS = DEBUG_ALL || false;
 
     static final String POSTFIX_ADD_REMOVE = (APPEND_CATEGORY_NAME) ? "_AddRemove" : "";
     static final String POSTFIX_APP = (APPEND_CATEGORY_NAME) ? "_App" : "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1ee92b7..842a5b8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -401,6 +401,7 @@
 import com.android.server.AttributeCache;
 import com.android.server.DeviceIdleController;
 import com.android.server.IntentResolver;
+import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.NetworkManagementInternal;
@@ -1011,15 +1012,6 @@
     private static final int MAX_DUP_SUPPRESSED_STACKS = 5000;
 
     /**
-     * Strict Mode background batched logging state.
-     *
-     * The string buffer is guarded by itself, and its lock is also
-     * used to determine if another batched write is already
-     * in-flight.
-     */
-    private final StringBuilder mStrictModeBuffer = new StringBuilder();
-
-    /**
      * Keeps track of all IIntentReceivers that have been registered for broadcasts.
      * Hash keys are the receiver IBinder, hash value is a ReceiverList.
      */
@@ -14273,10 +14265,9 @@
             IBinder app,
             int violationMask,
             StrictMode.ViolationInfo info) {
-        ProcessRecord r = findAppProcess(app, "StrictMode");
-        if (r == null) {
-            return;
-        }
+        // We're okay if the ProcessRecord is missing; it probably means that
+        // we're reporting a violation from the system process itself.
+        final ProcessRecord r = findAppProcess(app, "StrictMode");
 
         if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) {
             Integer stackFingerprint = info.hashCode();
@@ -14338,18 +14329,15 @@
                 (process.info.flags & (ApplicationInfo.FLAG_SYSTEM |
                                        ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0;
         final String processName = process == null ? "unknown" : process.processName;
-        final String dropboxTag = isSystemApp ? "system_app_strictmode" : "data_app_strictmode";
         final DropBoxManager dbox = (DropBoxManager)
                 mContext.getSystemService(Context.DROPBOX_SERVICE);
 
         // Exit early if the dropbox isn't configured to accept this report type.
+        final String dropboxTag = processClass(process) + "_strictmode";
         if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
 
-        boolean bufferWasEmpty;
-        boolean needsFlush;
-        final StringBuilder sb = isSystemApp ? mStrictModeBuffer : new StringBuilder(1024);
+        final StringBuilder sb = new StringBuilder(1024);
         synchronized (sb) {
-            bufferWasEmpty = sb.length() == 0;
             appendDropBoxProcessHeaders(process, processName, sb);
             sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
             sb.append("System-App: ").append(isSystemApp).append("\n");
@@ -14381,67 +14369,12 @@
                 sb.append(info.getViolationDetails());
                 sb.append("\n");
             }
-
-            // Only buffer up to ~64k.  Various logging bits truncate
-            // things at 128k.
-            needsFlush = (sb.length() > 64 * 1024);
         }
 
-        // Flush immediately if the buffer's grown too large, or this
-        // is a non-system app.  Non-system apps are isolated with a
-        // different tag & policy and not batched.
-        //
-        // Batching is useful during internal testing with
-        // StrictMode settings turned up high.  Without batching,
-        // thousands of separate files could be created on boot.
-        if (!isSystemApp || needsFlush) {
-            new Thread("Error dump: " + dropboxTag) {
-                @Override
-                public void run() {
-                    String report;
-                    synchronized (sb) {
-                        report = sb.toString();
-                        sb.delete(0, sb.length());
-                        sb.trimToSize();
-                    }
-                    if (report.length() != 0) {
-                        dbox.addText(dropboxTag, report);
-                    }
-                }
-            }.start();
-            return;
-        }
-
-        // System app batching:
-        if (!bufferWasEmpty) {
-            // An existing dropbox-writing thread is outstanding, so
-            // we don't need to start it up.  The existing thread will
-            // catch the buffer appends we just did.
-            return;
-        }
-
-        // Worker thread to both batch writes and to avoid blocking the caller on I/O.
-        // (After this point, we shouldn't access AMS internal data structures.)
-        new Thread("Error dump: " + dropboxTag) {
-            @Override
-            public void run() {
-                // 5 second sleep to let stacks arrive and be batched together
-                try {
-                    Thread.sleep(5000);  // 5 seconds
-                } catch (InterruptedException e) {}
-
-                String errorReport;
-                synchronized (mStrictModeBuffer) {
-                    errorReport = mStrictModeBuffer.toString();
-                    if (errorReport.length() == 0) {
-                        return;
-                    }
-                    mStrictModeBuffer.delete(0, mStrictModeBuffer.length());
-                    mStrictModeBuffer.trimToSize();
-                }
-                dbox.addText(dropboxTag, errorReport);
-            }
-        }.start();
+        final String res = sb.toString();
+        IoThread.getHandler().post(() -> {
+            dbox.addText(dropboxTag, res);
+        });
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index 93c0f77..eb022b7 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -13,6 +13,7 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_BIND_APPLICATION_DELAY_MS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CALLING_PACKAGE_NAME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CANCELLED;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_IS_EPHEMERAL;
@@ -28,16 +29,22 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_METRICS;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.content.Context;
 import android.metrics.LogMaker;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.SystemClock;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
 
@@ -58,6 +65,8 @@
 
     private static final long INVALID_START_TIME = -1;
 
+    private static final int MSG_CHECK_VISIBILITY = 0;
+
     // Preallocated strings we are sending to tron, so we don't have to allocate a new one every
     // time we log.
     private static final String[] TRON_WINDOW_STATE_VARZ_STRINGS = {
@@ -78,6 +87,23 @@
 
     private final SparseArray<StackTransitionInfo> mStackTransitionInfo = new SparseArray<>();
     private final SparseArray<StackTransitionInfo> mLastStackTransitionInfo = new SparseArray<>();
+    private final H mHandler;
+    private final class H extends Handler {
+
+        public H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_CHECK_VISIBILITY:
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    checkVisibility((TaskRecord) args.arg1, (ActivityRecord) args.arg2);
+                    break;
+            }
+        }
+    };
 
     private final class StackTransitionInfo {
         private ActivityRecord launchedActivity;
@@ -91,10 +117,11 @@
         private boolean loggedStartingWindowDrawn;
     }
 
-    ActivityMetricsLogger(ActivityStackSupervisor supervisor, Context context) {
+    ActivityMetricsLogger(ActivityStackSupervisor supervisor, Context context, Looper looper) {
         mLastLogTimeSecs = SystemClock.elapsedRealtime() / 1000;
         mSupervisor = supervisor;
         mContext = context;
+        mHandler = new H(looper);
     }
 
     void logWindowState() {
@@ -145,6 +172,7 @@
      */
     void notifyActivityLaunching() {
         if (!isAnyTransitionActive()) {
+            if (DEBUG_METRICS) Slog.i(TAG, "notifyActivityLaunching");
             mCurrentTransitionStartTime = SystemClock.uptimeMillis();
             mLastTransitionStartTime = mCurrentTransitionStartTime;
         }
@@ -202,6 +230,12 @@
     private void notifyActivityLaunched(int resultCode, ActivityRecord launchedActivity,
             boolean processRunning, boolean processSwitch) {
 
+        if (DEBUG_METRICS) Slog.i(TAG, "notifyActivityLaunched"
+                + " resultCode=" + resultCode
+                + " launchedActivity=" + launchedActivity
+                + " processRunning=" + processRunning
+                + " processSwitch=" + processSwitch);
+
         // If we are already in an existing transition, only update the activity name, but not the
         // other attributes.
         final int stackId = launchedActivity != null && launchedActivity.getStack() != null
@@ -230,6 +264,8 @@
             return;
         }
 
+        if (DEBUG_METRICS) Slog.i(TAG, "notifyActivityLaunched successful");
+
         final StackTransitionInfo newInfo = new StackTransitionInfo();
         newInfo.launchedActivity = launchedActivity;
         newInfo.currentTransitionProcessRunning = processRunning;
@@ -243,6 +279,8 @@
      * Notifies the tracker that all windows of the app have been drawn.
      */
     void notifyWindowsDrawn(int stackId, long timestamp) {
+        if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn stackId=" + stackId);
+
         final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
         if (info == null || info.loggedWindowsDrawn) {
             return;
@@ -276,6 +314,7 @@
         if (!isAnyTransitionActive() || mLoggedTransitionStarting) {
             return;
         }
+        if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting");
         mCurrentTransitionDelayMs = calculateDelay(timestamp);
         mLoggedTransitionStarting = true;
         for (int index = stackIdReasons.size() - 1; index >= 0; index--) {
@@ -295,17 +334,37 @@
      * Notifies the tracker that the visibility of an app is changing.
      *
      * @param activityRecord the app that is changing its visibility
-     * @param visible whether it's going to be visible or not
      */
-    void notifyVisibilityChanged(ActivityRecord activityRecord, boolean visible) {
+    void notifyVisibilityChanged(ActivityRecord activityRecord) {
         final StackTransitionInfo info = mStackTransitionInfo.get(activityRecord.getStackId());
+        if (info == null) {
+            return;
+        }
+        if (info.launchedActivity != activityRecord) {
+            return;
+        }
+        final TaskRecord t = activityRecord.getTask();
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = t;
+        args.arg2 = activityRecord;
+        mHandler.obtainMessage(MSG_CHECK_VISIBILITY, args).sendToTarget();
+    }
 
-        // If we have an active transition that's waiting on a certain activity that will be
-        // invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary.
-        if (info != null && !visible && info.launchedActivity == activityRecord) {
-            mStackTransitionInfo.remove(activityRecord.getStackId());
-            if (mStackTransitionInfo.size() == 0) {
-                reset(true /* abort */);
+    private void checkVisibility(TaskRecord t, ActivityRecord r) {
+        synchronized (mSupervisor.mService) {
+
+            final StackTransitionInfo info = mStackTransitionInfo.get(r.getStackId());
+
+            // If we have an active transition that's waiting on a certain activity that will be
+            // invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary.
+            if (info != null && !t.isVisible()) {
+                if (DEBUG_METRICS) Slog.i(TAG, "notifyVisibilityChanged to invisible"
+                        + " activity=" + r);
+                logAppTransitionCancel(info);
+                mStackTransitionInfo.remove(r.getStackId());
+                if (mStackTransitionInfo.size() == 0) {
+                    reset(true /* abort */);
+                }
             }
         }
     }
@@ -341,6 +400,7 @@
     }
 
     private void reset(boolean abort) {
+        if (DEBUG_METRICS) Slog.i(TAG, "reset abort=" + abort);
         if (!abort && isAnyTransitionActive()) {
             logAppTransitionMultiEvents();
         }
@@ -361,7 +421,20 @@
         return (int) (timestamp - mCurrentTransitionStartTime);
     }
 
+    private void logAppTransitionCancel(StackTransitionInfo info) {
+        final int type = getTransitionType(info);
+        if (type == -1) {
+            return;
+        }
+        final LogMaker builder = new LogMaker(APP_TRANSITION_CANCELLED);
+        builder.setPackageName(info.launchedActivity.packageName);
+        builder.setType(type);
+        builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivity.info.name);
+        mMetricsLogger.write(builder);
+    }
+
     private void logAppTransitionMultiEvents() {
+        if (DEBUG_METRICS) Slog.i(TAG, "logging transition events");
         for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) {
             final StackTransitionInfo info = mStackTransitionInfo.valueAt(index);
             final int type = getTransitionType(info);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 481575e..1db2629 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1526,7 +1526,7 @@
 
     void setVisibility(boolean visible) {
         mWindowContainerController.setVisibility(visible, mDeferHidingClient);
-        mStackSupervisor.mActivityMetricsLogger.notifyVisibilityChanged(this, visible);
+        mStackSupervisor.mActivityMetricsLogger.notifyVisibilityChanged(this);
     }
 
     // TODO: Look into merging with #setVisibility()
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 43a2a9f..a0aeb63 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -575,7 +575,7 @@
         mService = service;
         mHandler = new ActivityStackSupervisorHandler(looper);
         mRunningTasks = createRunningTasks();
-        mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
+        mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext, looper);
         mKeyguardController = new KeyguardController(service, this);
 
         mLaunchingBoundsController = new LaunchingBoundsController();
@@ -4502,9 +4502,13 @@
                 mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */,
                         targetActivity);
                 mActivityMetricsLogger.notifyActivityLaunching();
-                mService.moveTaskToFrontLocked(task.taskId, 0, bOptions, true /* fromRecents */);
-                mActivityMetricsLogger.notifyActivityLaunched(ActivityManager.START_TASK_TO_FRONT,
-                        targetActivity);
+                try {
+                    mService.moveTaskToFrontLocked(task.taskId, 0, bOptions,
+                            true /* fromRecents */);
+                } finally {
+                    mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT,
+                            targetActivity);
+                }
 
                 // If we are launching the task in the docked stack, put it into resizing mode so
                 // the window renders full-screen with the background filling the void. Also only
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 1a4f9d4..c5514fb 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1036,6 +1036,16 @@
         return null;
     }
 
+    boolean isVisible() {
+        for (int i = mActivities.size() - 1; i >= 0; --i) {
+            final ActivityRecord r = mActivities.get(i);
+            if (r.visible) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     void getAllRunningVisibleActivitiesLocked(ArrayList<ActivityRecord> outActivities) {
         if (mStack != null) {
             for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 2df5dc9..9c649e3 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -89,6 +89,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemServiceManager;
 import com.android.server.pm.UserManagerService;
@@ -369,13 +370,17 @@
         // Prepare app storage before we go any further
         uss.mUnlockProgress.setProgress(5,
                     mInjector.getContext().getString(R.string.android_start_title));
-        mInjector.getUserManager().onBeforeUnlockUser(userId);
-        uss.mUnlockProgress.setProgress(20);
 
-        // Dispatch unlocked to system services; when fully dispatched,
-        // that calls through to the next "unlocked" phase
-        mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
-                .sendToTarget();
+        // Call onBeforeUnlockUser on a worker thread that allows disk I/O
+        FgThread.getHandler().post(() -> {
+            mInjector.getUserManager().onBeforeUnlockUser(userId);
+            uss.mUnlockProgress.setProgress(20);
+
+            // Dispatch unlocked to system services; when fully dispatched,
+            // that calls through to the next "unlocked" phase
+            mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
+                    .sendToTarget();
+        });
     }
 
     /**
@@ -1819,7 +1824,10 @@
             case SYSTEM_USER_UNLOCK_MSG:
                 final int userId = msg.arg1;
                 mInjector.getSystemServiceManager().unlockUser(userId);
-                mInjector.loadUserRecents(userId);
+                // Loads recents on a worker thread that allows disk I/O
+                FgThread.getHandler().post(() -> {
+                    mInjector.loadUserRecents(userId);
+                });
                 if (userId == UserHandle.USER_SYSTEM) {
                     mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
                 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5eb2a8d..3f23737 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -122,6 +122,7 @@
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.server.EventLogTags;
@@ -398,8 +399,9 @@
      * {@link AudioManager#RINGER_MODE_SILENT}, or
      * {@link AudioManager#RINGER_MODE_VIBRATE}.
      */
-    // protected by mSettingsLock
+    @GuardedBy("mSettingsLock")
     private int mRingerMode;  // internal ringer mode, affects muting of underlying streams
+    @GuardedBy("mSettingsLock")
     private int mRingerModeExternal = -1;  // reported ringer mode to outside clients (AudioManager)
 
     /** @see System#MODE_RINGER_STREAMS_AFFECTED */
@@ -929,8 +931,11 @@
         mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm,
                 "onAudioServerDied"));
         AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
-        final int forSys = mCameraSoundForced ?
-                AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+        final int forSys;
+        synchronized (mSettingsLock) {
+            forSys = mCameraSoundForced ?
+                    AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+        }
         mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys,
                 "onAudioServerDied"));
         AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys);
@@ -3797,6 +3802,7 @@
         return (mRingerModeMutedStreams & (1 << streamType)) != 0;
     }
 
+    @GuardedBy("mSettingsLock")
     private boolean updateRingerModeAffectedStreams() {
         int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver,
                 Settings.System.MODE_RINGER_STREAMS_AFFECTED,
@@ -3810,12 +3816,10 @@
             ringerModeAffectedStreams = mRingerModeDelegate
                     .getRingerModeAffectedStreams(ringerModeAffectedStreams);
         }
-        synchronized (mCameraSoundForced) {
-            if (mCameraSoundForced) {
-                ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
-            } else {
-                ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
-            }
+        if (mCameraSoundForced) {
+            ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+        } else {
+            ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
         }
         if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) {
             ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
@@ -4185,7 +4189,6 @@
     //  2   mSetModeDeathHandlers
     //  3     mSettingsLock
     //  4       VolumeStreamState.class
-    //  5         mCameraSoundForced
     public class VolumeStreamState {
         private final int mStreamType;
         private final int mIndexMin;
@@ -4252,27 +4255,28 @@
         }
 
         public void readSettings() {
-            synchronized (VolumeStreamState.class) {
-                // force maximum volume on all streams if fixed volume property is set
-                if (mUseFixedVolume) {
-                    mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
-                    return;
-                }
-                // do not read system stream volume from settings: this stream is always aliased
-                // to another stream type and its volume is never persisted. Values in settings can
-                // only be stale values
-                if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
-                        (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
-                    int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
-                    synchronized (mCameraSoundForced) {
+            synchronized (mSettingsLock) {
+                synchronized (VolumeStreamState.class) {
+                    // force maximum volume on all streams if fixed volume property is set
+                    if (mUseFixedVolume) {
+                        mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+                        return;
+                    }
+                    // do not read system stream volume from settings: this stream is always aliased
+                    // to another stream type and its volume is never persisted. Values in settings can
+                    // only be stale values
+                    if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
+                            (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
+                        int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
                         if (mCameraSoundForced) {
                             index = mIndexMax;
                         }
+                        mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
+                        return;
                     }
-                    mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
-                    return;
                 }
-
+            }
+            synchronized (VolumeStreamState.class) {
                 int remainingDevices = AudioSystem.DEVICE_OUT_ALL;
 
                 for (int i = 0; remainingDevices != 0; i++) {
@@ -4385,34 +4389,34 @@
         public boolean setIndex(int index, int device, String caller) {
             boolean changed = false;
             int oldIndex;
-            synchronized (VolumeStreamState.class) {
-                oldIndex = getIndex(device);
-                index = getValidIndex(index);
-                synchronized (mCameraSoundForced) {
+            synchronized (mSettingsLock) {
+                synchronized (VolumeStreamState.class) {
+                    oldIndex = getIndex(device);
+                    index = getValidIndex(index);
                     if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
                         index = mIndexMax;
                     }
-                }
-                mIndexMap.put(device, index);
+                    mIndexMap.put(device, index);
 
-                changed = oldIndex != index;
-                // Apply change to all streams using this one as alias if:
-                // - the index actually changed OR
-                // - there is no volume index stored for this device on alias stream.
-                // If changing volume of current device, also change volume of current
-                // device on aliased stream
-                final boolean currentDevice = (device == getDeviceForStream(mStreamType));
-                final int numStreamTypes = AudioSystem.getNumStreamTypes();
-                for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-                    final VolumeStreamState aliasStreamState = mStreamStates[streamType];
-                    if (streamType != mStreamType &&
-                            mStreamVolumeAlias[streamType] == mStreamType &&
-                            (changed || !aliasStreamState.hasIndexForDevice(device))) {
-                        final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
-                        aliasStreamState.setIndex(scaledIndex, device, caller);
-                        if (currentDevice) {
-                            aliasStreamState.setIndex(scaledIndex,
-                                    getDeviceForStream(streamType), caller);
+                    changed = oldIndex != index;
+                    // Apply change to all streams using this one as alias if:
+                    // - the index actually changed OR
+                    // - there is no volume index stored for this device on alias stream.
+                    // If changing volume of current device, also change volume of current
+                    // device on aliased stream
+                    final boolean currentDevice = (device == getDeviceForStream(mStreamType));
+                    final int numStreamTypes = AudioSystem.getNumStreamTypes();
+                    for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+                        final VolumeStreamState aliasStreamState = mStreamStates[streamType];
+                        if (streamType != mStreamType &&
+                                mStreamVolumeAlias[streamType] == mStreamType &&
+                                (changed || !aliasStreamState.hasIndexForDevice(device))) {
+                            final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
+                            aliasStreamState.setIndex(scaledIndex, device, caller);
+                            if (currentDevice) {
+                                aliasStreamState.setIndex(scaledIndex,
+                                        getDeviceForStream(streamType), caller);
+                            }
                         }
                     }
                 }
@@ -6022,13 +6026,8 @@
 
             boolean cameraSoundForced = readCameraSoundForced();
             synchronized (mSettingsLock) {
-                boolean cameraSoundForcedChanged = false;
-                synchronized (mCameraSoundForced) {
-                    if (cameraSoundForced != mCameraSoundForced) {
-                        mCameraSoundForced = cameraSoundForced;
-                        cameraSoundForcedChanged = true;
-                    }
-                }
+                final boolean cameraSoundForcedChanged = (cameraSoundForced != mCameraSoundForced);
+                mCameraSoundForced = cameraSoundForced;
                 if (cameraSoundForcedChanged) {
                     if (!mIsSingleVolume) {
                         VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
@@ -6408,11 +6407,12 @@
     //==========================================================================================
 
     // cached value of com.android.internal.R.bool.config_camera_sound_forced
-    private Boolean mCameraSoundForced;
+    @GuardedBy("mSettingsLock")
+    private boolean mCameraSoundForced;
 
     // called by android.hardware.Camera to populate CameraInfo.canDisableShutterSound
     public boolean isCameraSoundForced() {
-        synchronized (mCameraSoundForced) {
+        synchronized (mSettingsLock) {
             return mCameraSoundForced;
         }
     }
@@ -6734,7 +6734,9 @@
         public void setRingerModeDelegate(RingerModeDelegate delegate) {
             mRingerModeDelegate = delegate;
             if (mRingerModeDelegate != null) {
-                updateRingerModeAffectedStreams();
+                synchronized (mSettingsLock) {
+                    updateRingerModeAffectedStreams();
+                }
                 setRingerModeInternal(getRingerModeInternal(), TAG + ".setRingerModeDelegate");
             }
         }
diff --git a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
index 8981db1..28c3585 100644
--- a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
+++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
@@ -18,9 +18,11 @@
 
 import android.net.LinkProperties;
 import android.net.metrics.DefaultNetworkEvent;
-import android.net.metrics.IpConnectivityLog;
+import android.os.SystemClock;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.RingBuffer;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
 
 import java.io.PrintWriter;
@@ -35,19 +37,49 @@
 
     private static final int ROLLING_LOG_SIZE = 64;
 
+    public final long creationTimeMs = SystemClock.elapsedRealtime();
+
     // Event buffer used for metrics upload. The buffer is cleared when events are collected.
     @GuardedBy("this")
     private final List<DefaultNetworkEvent> mEvents = new ArrayList<>();
 
+    // Rolling event buffer used for dumpsys and bugreports.
+    @GuardedBy("this")
+    private final RingBuffer<DefaultNetworkEvent> mEventsLog =
+            new RingBuffer(DefaultNetworkEvent.class, ROLLING_LOG_SIZE);
+
+    // Information about the current status of the default network.
+    @GuardedBy("this")
+    private DefaultNetworkEvent mCurrentDefaultNetwork;
+    @GuardedBy("this")
+    private boolean mIsCurrentlyValid;
+    @GuardedBy("this")
+    private long mLastValidationTimeMs;
+    // Transport information about the last default network.
+    @GuardedBy("this")
+    private int mLastTransports;
+
+    public DefaultNetworkMetrics() {
+        newDefaultNetwork(creationTimeMs, null);
+    }
+
     public synchronized void listEvents(PrintWriter pw) {
+        pw.println("default network events:");
         long localTimeMs = System.currentTimeMillis();
-        for (DefaultNetworkEvent ev : mEvents) {
-            pw.println(ev);
+        long timeMs = SystemClock.elapsedRealtime();
+        for (DefaultNetworkEvent ev : mEventsLog.toArray()) {
+            printEvent(localTimeMs, pw, ev);
         }
+        mCurrentDefaultNetwork.updateDuration(timeMs);
+        if (mIsCurrentlyValid) {
+            updateValidationTime(timeMs);
+            mLastValidationTimeMs = timeMs;
+        }
+        printEvent(localTimeMs, pw, mCurrentDefaultNetwork);
     }
 
     public synchronized void listEventsAsProto(PrintWriter pw) {
-        for (DefaultNetworkEvent ev : mEvents) {
+        for (DefaultNetworkEvent ev : mEventsLog.toArray()) {
             pw.print(IpConnectivityEventBuilder.toProto(ev));
         }
     }
@@ -59,20 +91,75 @@
         mEvents.clear();
     }
 
-    public synchronized void logDefaultNetworkEvent(
-            NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
-        DefaultNetworkEvent ev = new DefaultNetworkEvent();
-        if (newNai != null) {
-            ev.netId = newNai.network().netId;
-            ev.transportTypes = newNai.networkCapabilities.getTransportTypes();
-        }
-        if (prevNai != null) {
-            ev.prevNetId = prevNai.network().netId;
-            final LinkProperties lp = prevNai.linkProperties;
-            ev.prevIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
-            ev.prevIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
+    public synchronized void logDefaultNetworkValidity(long timeMs, boolean isValid) {
+        if (!isValid && mIsCurrentlyValid) {
+            mIsCurrentlyValid = false;
+            updateValidationTime(timeMs);
         }
 
+        if (isValid && !mIsCurrentlyValid) {
+            mIsCurrentlyValid = true;
+            mLastValidationTimeMs = timeMs;
+        }
+    }
+
+    private void updateValidationTime(long timeMs) {
+        mCurrentDefaultNetwork.validatedMs += timeMs - mLastValidationTimeMs;
+    }
+
+    public synchronized void logDefaultNetworkEvent(
+            long timeMs, NetworkAgentInfo newNai, NetworkAgentInfo oldNai) {
+        logCurrentDefaultNetwork(timeMs, oldNai);
+        newDefaultNetwork(timeMs, newNai);
+    }
+
+    private void logCurrentDefaultNetwork(long timeMs, NetworkAgentInfo oldNai) {
+        DefaultNetworkEvent ev = mCurrentDefaultNetwork;
+        ev.updateDuration(timeMs);
+        ev.previousTransports = mLastTransports;
+        // oldNai is null if the system had no default network before the transition.
+        if (oldNai != null) {
+            // The system acquired a new default network.
+            fillLinkInfo(ev, oldNai);
+            ev.finalScore = oldNai.getCurrentScore();
+            ev.validatedMs = ev.durationMs;
+        }
+        // Only change transport of the previous default network if the event currently logged
+        // corresponds to an existing default network, and not to the absence of a default network.
+        // This allows to log pairs of transports for successive default networks regardless of
+        // whether or not the system experienced a period without any default network.
+        if (ev.transports != 0) {
+            mLastTransports = ev.transports;
+        }
         mEvents.add(ev);
+        mEventsLog.append(ev);
+    }
+
+    private void newDefaultNetwork(long timeMs, NetworkAgentInfo newNai) {
+        DefaultNetworkEvent ev = new DefaultNetworkEvent(timeMs);
+        ev.durationMs = timeMs;
+        // newNai is null if the system has no default network after the transition.
+        if (newNai != null) {
+            fillLinkInfo(ev, newNai);
+            ev.initialScore = newNai.getCurrentScore();
+            if (newNai.lastValidated) {
+                mIsCurrentlyValid = true;
+                mLastValidationTimeMs = timeMs;
+            }
+        }
+        mCurrentDefaultNetwork = ev;
+    }
+
+    private static void fillLinkInfo(DefaultNetworkEvent ev, NetworkAgentInfo nai) {
+        LinkProperties lp = nai.linkProperties;
+        ev.netId = nai.network().netId;
+        ev.transports |= BitUtils.packBits(nai.networkCapabilities.getTransportTypes());
+        ev.ipv4 |= lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
+        ev.ipv6 |= lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
+    }
+
+    private static void printEvent(long localTimeMs, PrintWriter pw, DefaultNetworkEvent ev) {
+        long localCreationTimeMs = localTimeMs - ev.durationMs;
+        pw.println(String.format("%tT.%tL: %s", localCreationTimeMs, localCreationTimeMs, ev));
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 3d71ecb..a011692b 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -25,6 +25,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 
+import android.net.ConnectivityManager;
 import android.net.ConnectivityMetricsEvent;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
@@ -135,11 +136,17 @@
     public static IpConnectivityEvent toProto(DefaultNetworkEvent in) {
         IpConnectivityLogClass.DefaultNetworkEvent ev =
                 new IpConnectivityLogClass.DefaultNetworkEvent();
-        ev.networkId = netIdOf(in.netId);
-        ev.previousNetworkId = netIdOf(in.prevNetId);
-        ev.transportTypes = in.transportTypes;
-        ev.previousNetworkIpSupport = ipSupportOf(in);
-        final IpConnectivityEvent out = buildEvent(in.netId, 0, null);
+        ev.finalScore = in.finalScore;
+        ev.initialScore = in.initialScore;
+        ev.ipSupport = ipSupportOf(in);
+        ev.defaultNetworkDurationMs = in.durationMs;
+        ev.validationDurationMs = in.validatedMs;
+        ev.previousDefaultNetworkLinkLayer = transportsToLinkLayer(in.previousTransports);
+        final IpConnectivityEvent out = buildEvent(in.netId, in.transports, null);
+        if (in.transports == 0) {
+            // Set link layer to NONE for events representing the absence of a default network.
+            out.linkLayer = IpConnectivityLogClass.NONE;
+        }
         out.setDefaultNetworkEvent(ev);
         return out;
     }
@@ -321,13 +328,13 @@
     }
 
     private static int ipSupportOf(DefaultNetworkEvent in) {
-        if (in.prevIPv4 && in.prevIPv6) {
+        if (in.ipv4 && in.ipv6) {
             return IpConnectivityLogClass.DefaultNetworkEvent.DUAL;
         }
-        if (in.prevIPv6) {
+        if (in.ipv6) {
             return IpConnectivityLogClass.DefaultNetworkEvent.IPV6;
         }
-        if (in.prevIPv4) {
+        if (in.ipv4) {
             return IpConnectivityLogClass.DefaultNetworkEvent.IPV4;
         }
         return IpConnectivityLogClass.DefaultNetworkEvent.NONE;
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 24217e6..5cc390a 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -45,6 +45,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.ToIntFunction;
 
@@ -214,86 +215,66 @@
     }
 
     /**
-     * Clears the event buffer and prints its content as a protobuf serialized byte array
+     * Clear the event buffer and prints its content as a protobuf serialized byte array
      * inside a base64 encoded string.
      */
-    private void cmdFlush(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void cmdFlush(PrintWriter pw) {
         pw.print(flushEncodedOutput());
     }
 
     /**
-     * Prints the content of the event buffer, either using the events ASCII representation
-     * or using protobuf text format.
+     * Print the content of the rolling event buffer in human readable format.
+     * Also print network dns/connect statistics and recent default network events.
      */
-    private void cmdList(FileDescriptor fd, PrintWriter pw, String[] args) {
-        final ArrayList<ConnectivityMetricsEvent> events;
-        synchronized (mLock) {
-            events = new ArrayList(mBuffer);
-        }
-
-        if (args.length > 1 && args[1].equals("proto")) {
-            for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
-                pw.print(ev.toString());
-            }
-            if (mNetdListener != null) {
-                mNetdListener.listAsProtos(pw);
-            }
-            mDefaultNetworkMetrics.listEventsAsProto(pw);
-            return;
-        }
-
+    private void cmdList(PrintWriter pw) {
+        pw.println("metrics events:");
+        final List<ConnectivityMetricsEvent> events = getEvents();
         for (ConnectivityMetricsEvent ev : events) {
             pw.println(ev.toString());
         }
+        pw.println("");
         if (mNetdListener != null) {
             mNetdListener.list(pw);
         }
+        pw.println("");
         mDefaultNetworkMetrics.listEvents(pw);
     }
 
-    /**
-     * Prints for bug reports the content of the rolling event log and the
-     * content of Netd event listener.
+    /*
+     * Print the content of the rolling event buffer in text proto format.
      */
-    private void cmdDumpsys(FileDescriptor fd, PrintWriter pw, String[] args) {
-        final ConnectivityMetricsEvent[] events;
-        synchronized (mLock) {
-            events = mEventLog.toArray();
-        }
-        for (ConnectivityMetricsEvent ev : events) {
-            pw.println(ev.toString());
+    private void cmdListAsProto(PrintWriter pw) {
+        final List<ConnectivityMetricsEvent> events = getEvents();
+        for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
+            pw.print(ev.toString());
         }
         if (mNetdListener != null) {
-            mNetdListener.list(pw);
+            mNetdListener.listAsProtos(pw);
         }
-        mDefaultNetworkMetrics.listEvents(pw);
+        mDefaultNetworkMetrics.listEventsAsProto(pw);
     }
 
-    private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
+    /*
+     * Return a copy of metrics events stored in buffer for metrics uploading.
+     */
+    private List<ConnectivityMetricsEvent> getEvents() {
         synchronized (mLock) {
-            pw.println("Buffered events: " + mBuffer.size());
-            pw.println("Buffer capacity: " + mCapacity);
-            pw.println("Dropped events: " + mDropped);
+            return Arrays.asList(mEventLog.toArray());
         }
-        if (mNetdListener != null) {
-            mNetdListener.dump(pw);
-        }
-    }
-
-    private void cmdDefault(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (args.length == 0) {
-            pw.println("No command");
-            return;
-        }
-        pw.println("Unknown command " + TextUtils.join(" ", args));
     }
 
     public final class Impl extends IIpConnectivityMetrics.Stub {
-        static final String CMD_FLUSH   = "flush";
-        static final String CMD_LIST    = "list";
-        static final String CMD_STATS   = "stats";
-        static final String CMD_DUMPSYS = "-a"; // dumpsys.cpp dumps services with "-a" as arguments
-        static final String CMD_DEFAULT = CMD_STATS;
+        // Dump and flushes the metrics event buffer in base64 encoded serialized proto output.
+        static final String CMD_FLUSH = "flush";
+        // Dump the rolling buffer of metrics event in human readable proto text format.
+        static final String CMD_PROTO = "proto";
+        // Dump the rolling buffer of metrics event and pretty print events using a human readable
+        // format. Also print network dns/connect statistics and default network event time series.
+        static final String CMD_LIST = "list";
+        // By default any other argument will fall into the default case which is remapped to the
+        // "list" command. This includes most notably bug reports collected by dumpsys.cpp with
+        // the "-a" argument.
+        static final String CMD_DEFAULT = CMD_LIST;
 
         @Override
         public int logEvent(ConnectivityMetricsEvent event) {
@@ -308,19 +289,15 @@
             final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT;
             switch (cmd) {
                 case CMD_FLUSH:
-                    cmdFlush(fd, pw, args);
+                    cmdFlush(pw);
                     return;
-                case CMD_DUMPSYS:
-                    cmdDumpsys(fd, pw, args);
+                case CMD_PROTO:
+                    cmdListAsProto(pw);
                     return;
-                case CMD_LIST:
-                    cmdList(fd, pw, args);
-                    return;
-                case CMD_STATS:
-                    cmdStats(fd, pw, args);
-                    return;
+                case CMD_LIST: // fallthrough
                 default:
-                    cmdDefault(fd, pw, args);
+                    cmdList(pw);
+                    return;
             }
         }
 
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 05c6e69..61b11e1 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -243,24 +243,21 @@
         mWakeupStats.clear();
     }
 
-    public synchronized void dump(PrintWriter writer) {
-        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        pw.println(TAG + ":");
-        pw.increaseIndent();
-        list(pw);
-        pw.decreaseIndent();
-    }
-
     public synchronized void list(PrintWriter pw) {
+        pw.println("dns/connect events:");
         for (int i = 0; i < mNetworkMetrics.size(); i++) {
             pw.println(mNetworkMetrics.valueAt(i).connectMetrics);
         }
         for (int i = 0; i < mNetworkMetrics.size(); i++) {
             pw.println(mNetworkMetrics.valueAt(i).dnsMetrics);
         }
+        pw.println("");
+        pw.println("network statistics:");
         for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) {
             pw.println(s);
         }
+        pw.println("");
+        pw.println("packet wakeup events:");
         for (int i = 0; i < mWakeupStats.size(); i++) {
             pw.println(mWakeupStats.valueAt(i));
         }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index f930b52..600bc42 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1120,6 +1120,27 @@
             // Dismiss the black surface without fanfare.
             mPowerState.setColorFadeLevel(1.0f);
             mPowerState.dismissColorFade();
+        } else if (target == Display.STATE_ON_SUSPEND) {
+            // Want screen full-power and suspended.
+            // Wait for brightness animation to complete beforehand unless already
+            // suspended because we may not be able to change it after suspension.
+            if (mScreenBrightnessRampAnimator.isAnimating()
+                    && mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+                return;
+            }
+
+            // If not already suspending, temporarily set the state to on until the
+            // screen on is unblocked, then suspend.
+            if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+                if (!setScreenState(Display.STATE_ON)) {
+                    return;
+                }
+                setScreenState(Display.STATE_ON_SUSPEND);
+            }
+
+            // Dismiss the black surface without fanfare.
+            mPowerState.setColorFadeLevel(1.0f);
+            mPowerState.dismissColorFade();
         } else {
             // Want screen off.
             mPendingScreenOff = true;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 9fdc35f..eb9ff58 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -148,6 +148,8 @@
                 return SurfaceControl.POWER_MODE_DOZE;
             case Display.STATE_DOZE_SUSPEND:
                 return SurfaceControl.POWER_MODE_DOZE_SUSPEND;
+            case Display.STATE_ON_SUSPEND:
+                return SurfaceControl.POWER_MODE_ON_SUSPEND;
             default:
                 return SurfaceControl.POWER_MODE_NORMAL;
         }
@@ -470,6 +472,10 @@
                                     || oldState == Display.STATE_DOZE_SUSPEND) {
                                 setDisplayState(Display.STATE_DOZE);
                                 currentState = Display.STATE_DOZE;
+                            } else if (state == Display.STATE_ON_SUSPEND
+                                    || oldState == Display.STATE_ON_SUSPEND) {
+                                setDisplayState(Display.STATE_ON);
+                                currentState = Display.STATE_ON;
                             } else {
                                 return; // old state and new state is off
                             }
diff --git a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java b/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
deleted file mode 100644
index 791ee82..0000000
--- a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * 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.server.media;
-
-import android.content.Context;
-import android.media.AudioManager.AudioPlaybackCallback;
-import android.media.AudioPlaybackConfiguration;
-import android.media.IAudioService;
-import android.media.IPlaybackConfigDispatcher;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.IntArray;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Monitors changes in audio playback, and notify the newly started audio playback through the
- * {@link OnAudioPlaybackStartedListener} and the activeness change through the
- * {@link OnAudioPlaybackActiveStateListener}.
- */
-class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub {
-    private static boolean DEBUG = MediaSessionService.DEBUG;
-    private static String TAG = "AudioPlaybackMonitor";
-
-    private static AudioPlaybackMonitor sInstance;
-
-    /**
-     * Called when audio playback is started for a given UID.
-     */
-    interface OnAudioPlaybackStartedListener {
-        void onAudioPlaybackStarted(int uid);
-    }
-
-    /**
-     * Called when audio player state is changed.
-     */
-    interface OnAudioPlayerActiveStateChangedListener {
-        void onAudioPlayerActiveStateChanged(int uid, boolean active);
-    }
-
-    private final Object mLock = new Object();
-    private final Context mContext;
-    private final List<OnAudioPlaybackStartedListener> mAudioPlaybackStartedListeners
-            = new ArrayList<>();
-    private final List<OnAudioPlayerActiveStateChangedListener>
-            mAudioPlayerActiveStateChangedListeners = new ArrayList<>();
-    private final Map<Integer, Integer> mAudioPlaybackStates = new HashMap<>();
-    private final Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
-
-    // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
-    // The UID whose audio playback becomes active at the last comes first.
-    // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
-    private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
-
-    static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) {
-        if (sInstance == null) {
-            sInstance = new AudioPlaybackMonitor(context, audioService);
-        }
-        return sInstance;
-    }
-
-    private AudioPlaybackMonitor(Context context, IAudioService audioService) {
-        mContext = context;
-        try {
-            audioService.registerPlaybackCallback(this);
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Failed to register playback callback", e);
-        }
-    }
-
-    /**
-     * Called when the {@link AudioPlaybackConfiguration} is updated.
-     * <p>If an app starts audio playback, the app's local media session will be the media button
-     * session. If the app has multiple media sessions, the playback active local session will be
-     * picked.
-     *
-     * @param configs List of the current audio playback configuration
-     */
-    @Override
-    public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
-            boolean flush) {
-        if (flush) {
-            Binder.flushPendingCommands();
-        }
-        final long token = Binder.clearCallingIdentity();
-        try {
-            List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>();
-            List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners;
-            List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners;
-            synchronized (mLock) {
-                // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids,
-                // and find newly activated audio playbacks.
-                mActiveAudioPlaybackClientUids.clear();
-                for (AudioPlaybackConfiguration config : configs) {
-                    // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL
-                    // (i.e. playback from the SoundPool class which is only for sound effects)
-                    // playback.
-                    // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
-                    // specific audio/video players.
-                    if (!config.isActive() || config.getPlayerType()
-                            == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
-                        continue;
-                    }
-
-                    mActiveAudioPlaybackClientUids.add(config.getClientUid());
-                    Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId());
-                    if (!isActiveState(oldState)) {
-                        if (DEBUG) {
-                            Log.d(TAG, "Found a new active media playback. " +
-                                    AudioPlaybackConfiguration.toLogFriendlyString(config));
-                        }
-                        // New active audio playback.
-                        newActiveAudioPlaybackClientUids.add(config.getClientUid());
-                        int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid());
-                        if (index == 0) {
-                            // It's the lastly played music app already. Skip updating.
-                            continue;
-                        } else if (index > 0) {
-                            mSortedAudioPlaybackClientUids.remove(index);
-                        }
-                        mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
-                    }
-                }
-                audioPlayerActiveStateChangedListeners = new ArrayList<>(
-                        mAudioPlayerActiveStateChangedListeners);
-                audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners);
-            }
-            // Notify the change of audio playback states.
-            for (AudioPlaybackConfiguration config : configs) {
-                boolean wasActive = isActiveState(
-                        mAudioPlaybackStates.get(config.getPlayerInterfaceId()));
-                boolean isActive = config.isActive();
-                if (wasActive != isActive) {
-                    for (OnAudioPlayerActiveStateChangedListener listener
-                            : audioPlayerActiveStateChangedListeners) {
-                        listener.onAudioPlayerActiveStateChanged(config.getClientUid(),
-                                isActive);
-                    }
-                }
-            }
-            // Notify the start of audio playback
-            for (int uid : newActiveAudioPlaybackClientUids) {
-                for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) {
-                    listener.onAudioPlaybackStarted(uid);
-                }
-            }
-            mAudioPlaybackStates.clear();
-            for (AudioPlaybackConfiguration config : configs) {
-                mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState());
-            }
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * Registers OnAudioPlaybackStartedListener.
-     */
-    public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
-        synchronized (mLock) {
-            mAudioPlaybackStartedListeners.add(listener);
-        }
-    }
-
-    /**
-     * Unregisters OnAudioPlaybackStartedListener.
-     */
-    public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
-        synchronized (mLock) {
-            mAudioPlaybackStartedListeners.remove(listener);
-        }
-    }
-
-    /**
-     * Registers OnAudioPlayerActiveStateChangedListener.
-     */
-    public void registerOnAudioPlayerActiveStateChangedListener(
-            OnAudioPlayerActiveStateChangedListener listener) {
-        synchronized (mLock) {
-            mAudioPlayerActiveStateChangedListeners.add(listener);
-        }
-    }
-
-    /**
-     * Unregisters OnAudioPlayerActiveStateChangedListener.
-     */
-    public void unregisterOnAudioPlayerActiveStateChangedListener(
-            OnAudioPlayerActiveStateChangedListener listener) {
-        synchronized (mLock) {
-            mAudioPlayerActiveStateChangedListeners.remove(listener);
-        }
-    }
-
-    /**
-     * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
-     * audio/video) The UID whose audio playback becomes active at the last comes first.
-     */
-    public IntArray getSortedAudioPlaybackClientUids() {
-        IntArray sortedAudioPlaybackClientUids = new IntArray();
-        synchronized (mLock) {
-            sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
-        }
-        return sortedAudioPlaybackClientUids;
-    }
-
-    /**
-     * Returns if the audio playback is active for the uid.
-     */
-    public boolean isPlaybackActive(int uid) {
-        synchronized (mLock) {
-            return mActiveAudioPlaybackClientUids.contains(uid);
-        }
-    }
-
-    /**
-     * Cleans up the sorted list of audio playback client UIDs with given {@param
-     * mediaButtonSessionUid}.
-     * <p>UIDs whose audio playback started after the media button session's audio playback
-     * cannot be the lastly played media app. So they won't needed anymore.
-     *
-     * @param mediaButtonSessionUid UID of the media button session.
-     */
-    public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
-        synchronized (mLock) {
-            int userId = UserHandle.getUserId(mediaButtonSessionUid);
-            for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
-                if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
-                    break;
-                }
-                int uid = mSortedAudioPlaybackClientUids.get(i);
-                if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
-                    // Clean up unnecessary UIDs.
-                    // It doesn't need to be managed profile aware because it's just to prevent
-                    // the list from increasing indefinitely. The media button session updating
-                    // shouldn't be affected by cleaning up.
-                    mSortedAudioPlaybackClientUids.remove(i);
-                }
-            }
-        }
-    }
-
-    /**
-     * Dumps {@link AudioPlaybackMonitor}.
-     */
-    public void dump(PrintWriter pw, String prefix) {
-        synchronized (mLock) {
-            pw.println(prefix + "Audio playback (lastly played comes first)");
-            String indent = prefix + "  ";
-            for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
-                int uid = mSortedAudioPlaybackClientUids.get(i);
-                pw.print(indent + "uid=" + uid + " packages=");
-                String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
-                if (packages != null && packages.length > 0) {
-                    for (int j = 0; j < packages.length; j++) {
-                        pw.print(packages[j] + " ");
-                    }
-                }
-                pw.println();
-            }
-        }
-    }
-
-    private boolean isActiveState(Integer state) {
-        return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
-    }
-}
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
new file mode 100644
index 0000000..be223f1
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -0,0 +1,327 @@
+/*
+ * 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.server.media;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioPlaybackConfiguration;
+import android.media.IAudioService;
+import android.media.IPlaybackConfigDispatcher;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IntArray;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Monitors the state changes of audio players.
+ */
+class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
+    private static boolean DEBUG = MediaSessionService.DEBUG;
+    private static String TAG = "AudioPlayerStateMonitor";
+
+    private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
+
+    /**
+     * Called when the state of audio player is changed.
+     */
+    interface OnAudioPlayerStateChangedListener {
+        void onAudioPlayerStateChanged(
+                int uid, int prevState, @Nullable AudioPlaybackConfiguration config);
+    }
+
+    private final static class MessageHandler extends Handler {
+        private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1;
+
+        private final OnAudioPlayerStateChangedListener mListsner;
+
+        public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) {
+            super(looper);
+            mListsner = listener;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_AUDIO_PLAYER_STATE_CHANGED:
+                    mListsner.onAudioPlayerStateChanged(
+                            msg.arg1, msg.arg2, (AudioPlaybackConfiguration) msg.obj);
+                    break;
+            }
+        }
+
+        public void sendAudioPlayerStateChangedMessage(int uid, int prevState,
+                AudioPlaybackConfiguration config) {
+            obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget();
+        }
+    }
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap =
+            new HashMap<>();
+    @GuardedBy("mLock")
+    private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>();
+    @GuardedBy("mLock")
+    private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>();
+    // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
+    // The UID whose audio playback becomes active at the last comes first.
+    // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
+    @GuardedBy("mLock")
+    private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
+
+    @GuardedBy("mLock")
+    private boolean mRegisteredToAudioService;
+
+    static AudioPlayerStateMonitor getInstance() {
+        return sInstance;
+    }
+
+    private AudioPlayerStateMonitor() {
+    }
+
+    /**
+     * Called when the {@link AudioPlaybackConfiguration} is updated.
+     * <p>If an app starts audio playback, the app's local media session will be the media button
+     * session. If the app has multiple media sessions, the playback active local session will be
+     * picked.
+     *
+     * @param configs List of the current audio playback configuration
+     */
+    @Override
+    public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
+            boolean flush) {
+        if (flush) {
+            Binder.flushPendingCommands();
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates);
+            final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid =
+                    new HashMap<>(mAudioPlayersForUid);
+            synchronized (mLock) {
+                mAudioPlayerStates.clear();
+                mAudioPlayersForUid.clear();
+                for (AudioPlaybackConfiguration config : configs) {
+                    int pii = config.getPlayerInterfaceId();
+                    int uid = config.getClientUid();
+                    mAudioPlayerStates.put(pii, config.getPlayerState());
+                    HashSet<Integer> players = mAudioPlayersForUid.get(uid);
+                    if (players == null) {
+                        players = new HashSet<Integer>();
+                        players.add(pii);
+                        mAudioPlayersForUid.put(uid, players);
+                    } else {
+                        players.add(pii);
+                    }
+                }
+                for (AudioPlaybackConfiguration config : configs) {
+                    if (!config.isActive()) {
+                        continue;
+                    }
+
+                    int uid = config.getClientUid();
+                    if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Found a new active media playback. " +
+                                    AudioPlaybackConfiguration.toLogFriendlyString(config));
+                        }
+                        // New active audio playback.
+                        int index = mSortedAudioPlaybackClientUids.indexOf(uid);
+                        if (index == 0) {
+                            // It's the lastly played music app already. Skip updating.
+                            continue;
+                        } else if (index > 0) {
+                            mSortedAudioPlaybackClientUids.remove(index);
+                        }
+                        mSortedAudioPlaybackClientUids.add(0, uid);
+                    }
+                }
+                // Notify the change of audio player states.
+                for (AudioPlaybackConfiguration config : configs) {
+                    final Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId());
+                    final int prevStateInt =
+                            (prevState == null) ? AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN :
+                                prevState.intValue();
+                    if (prevStateInt != config.getPlayerState()) {
+                        sendAudioPlayerStateChangedMessageLocked(
+                                config.getClientUid(), prevStateInt, config);
+                    }
+                }
+                for (Integer prevUid : prevAudioPlayersForUid.keySet()) {
+                    // If all players for prevUid is removed, notify the prev state was
+                    // PLAYER_STATE_STARTED only when there were a player whose state was
+                    // PLAYER_STATE_STARTED, otherwise any inactive state is okay to notify.
+                    if (!mAudioPlayersForUid.containsKey(prevUid)) {
+                        Set<Integer> prevPlayers = prevAudioPlayersForUid.get(prevUid);
+                        int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN;
+                        for (int pii : prevPlayers) {
+                            Integer state = prevAudioPlayerStates.get(pii);
+                            if (state == null) {
+                                continue;
+                            }
+                            if (state == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                                prevState = state;
+                                break;
+                            } else if (prevState
+                                    == AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN) {
+                                prevState = state;
+                            }
+                        }
+                        sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Registers OnAudioPlayerStateChangedListener.
+     */
+    public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) {
+        synchronized (mLock) {
+            mListenerMap.put(listener, new MessageHandler((handler == null) ?
+                    Looper.myLooper() : handler.getLooper(), listener));
+        }
+    }
+
+    /**
+     * Unregisters OnAudioPlayerStateChangedListener.
+     */
+    public void unregisterListener(OnAudioPlayerStateChangedListener listener) {
+        synchronized (mLock) {
+            mListenerMap.remove(listener);
+        }
+    }
+
+    /**
+     * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
+     * audio/video) The UID whose audio playback becomes active at the last comes first.
+     */
+    public IntArray getSortedAudioPlaybackClientUids() {
+        IntArray sortedAudioPlaybackClientUids = new IntArray();
+        synchronized (mLock) {
+            sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
+        }
+        return sortedAudioPlaybackClientUids;
+    }
+
+    /**
+     * Returns if the audio playback is active for the uid.
+     */
+    public boolean isPlaybackActive(int uid) {
+        synchronized (mLock) {
+            Set<Integer> players = mAudioPlayersForUid.get(uid);
+            if (players == null) {
+                return false;
+            }
+            for (Integer pii : players) {
+                if (isActiveState(mAudioPlayerStates.get(pii))) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Cleans up the sorted list of audio playback client UIDs with given {@param
+     * mediaButtonSessionUid}.
+     * <p>UIDs whose audio playback are inactive and have started before the media button session's
+     * audio playback cannot be the lastly played media app. So they won't needed anymore.
+     *
+     * @param mediaButtonSessionUid UID of the media button session.
+     */
+    public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
+        synchronized (mLock) {
+            int userId = UserHandle.getUserId(mediaButtonSessionUid);
+            for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
+                if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
+                    break;
+                }
+                int uid = mSortedAudioPlaybackClientUids.get(i);
+                if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
+                    // Clean up unnecessary UIDs.
+                    // It doesn't need to be managed profile aware because it's just to prevent
+                    // the list from increasing indefinitely. The media button session updating
+                    // shouldn't be affected by cleaning up.
+                    mSortedAudioPlaybackClientUids.remove(i);
+                }
+            }
+        }
+    }
+
+    /**
+     * Dumps {@link AudioPlayerStateMonitor}.
+     */
+    public void dump(Context context, PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            pw.println(prefix + "Audio playback (lastly played comes first)");
+            String indent = prefix + "  ";
+            for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
+                int uid = mSortedAudioPlaybackClientUids.get(i);
+                pw.print(indent + "uid=" + uid + " packages=");
+                String[] packages = context.getPackageManager().getPackagesForUid(uid);
+                if (packages != null && packages.length > 0) {
+                    for (int j = 0; j < packages.length; j++) {
+                        pw.print(packages[j] + " ");
+                    }
+                }
+                pw.println();
+            }
+        }
+    }
+
+    public void registerSelfIntoAudioServiceIfNeeded(IAudioService audioService) {
+        synchronized (mLock) {
+            try {
+                if (!mRegisteredToAudioService) {
+                    audioService.registerPlaybackCallback(this);
+                    mRegisteredToAudioService = true;
+                }
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Failed to register playback callback", e);
+                mRegisteredToAudioService = false;
+            }
+        }
+    }
+
+    private void sendAudioPlayerStateChangedMessageLocked(
+            final int uid, final int prevState, final AudioPlaybackConfiguration config) {
+        for (MessageHandler messageHandler : mListenerMap.values()) {
+            messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config);
+        }
+    }
+
+    private static boolean isActiveState(Integer state) {
+        return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 1cfd5f0..3c9e1d4 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -19,12 +19,14 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.Watchdog;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
 import android.media.IAudioRoutesObserver;
@@ -96,7 +98,8 @@
     private int mCurrentUserId = -1;
     private boolean mGlobalBluetoothA2dpOn = false;
     private final IAudioService mAudioService;
-    private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
+    private final Handler mHandler = new Handler();
     private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
 
     public MediaRouterService(Context context) {
@@ -106,31 +109,57 @@
         mAudioService = IAudioService.Stub.asInterface(
                 ServiceManager.getService(Context.AUDIO_SERVICE));
 
-        mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(context, mAudioService);
-        mAudioPlaybackMonitor.registerOnAudioPlayerActiveStateChangedListener(
-                new AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener() {
+        mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+        mAudioPlayerStateMonitor.registerListener(
+                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
+            static final long WAIT_MS = 500;
+            final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    restoreBluetoothA2dp();
+                }
+            };
+
             @Override
-            public void onAudioPlayerActiveStateChanged(int uid, boolean active) {
+            public void onAudioPlayerStateChanged(
+                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+                int restoreUid = -1;
+                boolean active = config == null ? false : config.isActive();
                 if (active) {
-                    restoreRoute(uid);
+                    restoreUid = uid;
+                } else if (prevState != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    // Noting to do if the prev state is not an active state.
+                    return;
                 } else {
                     IntArray sortedAudioPlaybackClientUids =
-                            mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
-                    boolean restored = false;
-                    for (int i = 0; i < sortedAudioPlaybackClientUids.size(); i++) {
-                        if (mAudioPlaybackMonitor.isPlaybackActive(
+                            mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
+                    for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) {
+                        if (mAudioPlayerStateMonitor.isPlaybackActive(
                                 sortedAudioPlaybackClientUids.get(i))) {
-                            restoreRoute(sortedAudioPlaybackClientUids.get(i));
-                            restored = true;
+                            restoreUid = sortedAudioPlaybackClientUids.get(i);
                             break;
                         }
                     }
-                    if (!restored) {
-                        restoreBluetoothA2dp();
+                }
+
+                mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
+                if (restoreUid >= 0) {
+                    restoreRoute(restoreUid);
+                    if (DEBUG) {
+                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+                                + " active " + active + " restoring " + restoreUid);
+                    }
+                } else {
+                    mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
+                    if (DEBUG) {
+                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+                                + " active " + active + " delaying");
                     }
                 }
             }
-        });
+        }, mHandler);
+        mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
+
         AudioRoutesInfo audioRoutes = null;
         try {
             audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
@@ -261,9 +290,14 @@
 
         final long token = Binder.clearCallingIdentity();
         try {
+            ClientRecord clientRecord;
             synchronized (mLock) {
-                return isPlaybackActiveLocked(client);
+                clientRecord = mAllClientRecords.get(client.asBinder());
             }
+            if (clientRecord != null) {
+                return mAudioPlayerStateMonitor.isPlaybackActive(clientRecord.mUid);
+            }
+            return false;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -480,14 +514,6 @@
         return null;
     }
 
-    private boolean isPlaybackActiveLocked(IMediaRouterClient client) {
-        ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
-        if (clientRecord != null) {
-            return mAudioPlaybackMonitor.isPlaybackActive(clientRecord.mUid);
-        }
-        return false;
-    }
-
     private void setDiscoveryRequestLocked(IMediaRouterClient client,
             int routeTypes, boolean activeScan) {
         final IBinder binder = client.asBinder();
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index aa65244..f6a81d0 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.media;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
@@ -31,7 +32,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.media.AudioManager;
-import android.media.AudioManagerInternal;
+import android.media.AudioPlaybackConfiguration;
 import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
@@ -68,7 +69,6 @@
 import android.view.ViewConfiguration;
 
 import com.android.internal.util.DumpUtils;
-import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.Watchdog;
 import com.android.server.Watchdog.Monitor;
@@ -104,7 +104,6 @@
 
     private KeyguardManager mKeyguardManager;
     private IAudioService mAudioService;
-    private AudioManagerInternal mAudioManagerInternal;
     private ContentResolver mContentResolver;
     private SettingsObserver mSettingsObserver;
     private INotificationManager mNotificationManager;
@@ -114,7 +113,7 @@
     // It's always not null after the MediaSessionService is started.
     private FullUserRecord mCurrentFullUserRecord;
     private MediaSessionRecord mGlobalPrioritySession;
-    private AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
 
     // Used to notify system UI when remote volume was changed. TODO find a
     // better way to handle this.
@@ -137,11 +136,16 @@
         mKeyguardManager =
                 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
         mAudioService = getAudioService();
-        mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(getContext(), mAudioService);
-        mAudioPlaybackMonitor.registerOnAudioPlaybackStartedListener(
-                new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
+        mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+        mAudioPlayerStateMonitor.registerListener(
+                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
             @Override
-            public void onAudioPlaybackStarted(int uid) {
+            public void onAudioPlayerStateChanged(
+                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+                if (config == null || !config.isActive() || config.getPlayerType()
+                        == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+                    return;
+                }
                 synchronized (mLock) {
                     FullUserRecord user =
                             getFullUserRecordLocked(UserHandle.getUserId(uid));
@@ -150,8 +154,8 @@
                     }
                 }
             }
-        });
-        mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
+        }, null /* handler */);
+        mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
         mContentResolver = getContext().getContentResolver();
         mSettingsObserver = new SettingsObserver();
         mSettingsObserver.observe();
@@ -650,7 +654,7 @@
 
         public FullUserRecord(int fullUserId) {
             mFullUserId = fullUserId;
-            mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);
+            mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
             // Restore the remembered media button receiver before the boot.
             String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
                     Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
@@ -1309,7 +1313,7 @@
                 for (int i = 0; i < count; i++) {
                     mUserRecords.valueAt(i).dumpLocked(pw, "");
                 }
-                mAudioPlaybackMonitor.dump(pw, "");
+                mAudioPlayerStateMonitor.dump(getContext(), pw, "");
             }
         }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index d9fe72e..719ec36 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -75,7 +75,7 @@
      */
     private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
 
-    private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
 
     /**
@@ -84,7 +84,6 @@
      */
     private MediaSessionRecord mMediaButtonSession;
 
-    private MediaSessionRecord mCachedDefault;
     private MediaSessionRecord mCachedVolumeDefault;
 
     /**
@@ -93,8 +92,8 @@
     private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists =
             new SparseArray<>();
 
-    MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) {
-        mAudioPlaybackMonitor = monitor;
+    MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) {
+        mAudioPlayerStateMonitor = monitor;
         mOnMediaButtonSessionChangedListener = listener;
     }
 
@@ -187,13 +186,13 @@
         if (DEBUG) {
             Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
         }
-        IntArray audioPlaybackUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
+        IntArray audioPlaybackUids = mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
         for (int i = 0; i < audioPlaybackUids.size(); i++) {
             MediaSessionRecord mediaButtonSession =
                     findMediaButtonSession(audioPlaybackUids.get(i));
             if (mediaButtonSession != null) {
                 // Found the media button session.
-                mAudioPlaybackMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
+                mAudioPlayerStateMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
                 if (mMediaButtonSession != mediaButtonSession) {
                     updateMediaButtonSession(mediaButtonSession);
                 }
@@ -216,7 +215,7 @@
         for (MediaSessionRecord session : mSessions) {
             if (uid == session.getUid()) {
                 if (session.getPlaybackState() != null && session.isPlaybackActive() ==
-                        mAudioPlaybackMonitor.isPlaybackActive(session.getUid())) {
+                        mAudioPlayerStateMonitor.isPlaybackActive(session.getUid())) {
                     // If there's a media session whose PlaybackState matches
                     // the audio playback state, return it immediately.
                     return session;
@@ -376,7 +375,6 @@
     }
 
     private void clearCache(int userId) {
-        mCachedDefault = null;
         mCachedVolumeDefault = null;
         mCachedActiveLists.remove(userId);
         // mCachedActiveLists may also include the list of sessions for UserHandle.USER_ALL,
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 3574466..fca9585 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -18,6 +18,8 @@
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 
+import static com.android.server.pm.PackageManagerService.SCAN_INITIAL;
+
 import com.android.internal.util.Preconditions;
 import android.content.pm.PackageParser;
 import android.util.ArrayMap;
@@ -341,6 +343,41 @@
         return mKeySets.get(id) != null;
     }
 
+    public boolean shouldCheckUpgradeKeySetLocked(PackageSettingBase oldPs, int scanFlags) {
+        // Can't rotate keys during boot or if sharedUser.
+        if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
+                || !oldPs.keySetData.isUsingUpgradeKeySets()) {
+            return false;
+        }
+        // app is using upgradeKeySets; make sure all are valid
+        long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();
+        for (int i = 0; i < upgradeKeySets.length; i++) {
+            if (!isIdValidKeySetId(upgradeKeySets[i])) {
+                Slog.wtf(TAG, "Package "
+                         + (oldPs.name != null ? oldPs.name : "<null>")
+                         + " contains upgrade-key-set reference to unknown key-set: "
+                         + upgradeKeySets[i]
+                         + " reverting to signatures check.");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean checkUpgradeKeySetLocked(PackageSettingBase oldPS,
+            PackageParser.Package newPkg) {
+        // Upgrade keysets are being used.  Determine if new package has a superset of the
+        // required keys.
+        long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
+        for (int i = 0; i < upgradeKeySets.length; i++) {
+            Set<PublicKey> upgradeSet = getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
+            if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Fetches the {@link PublicKey public keys} which belong to the specified
      * KeySet id.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6c42f4f..6a0ea4ee 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -102,6 +102,15 @@
 import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
+import static com.android.server.pm.PackageManagerServiceUtils.decompressFile;
+import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
+import static com.android.server.pm.PackageManagerServiceUtils.dumpCriticalInfo;
+import static com.android.server.pm.PackageManagerServiceUtils.getCompressedFiles;
+import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
 import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
 import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
 import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
@@ -408,7 +417,7 @@
     private static final boolean DEBUG_FILTERS = false;
     public static final boolean DEBUG_PERMISSIONS = false;
     private static final boolean DEBUG_SHARED_LIBRARIES = false;
-    private static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
+    public static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
 
     // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
     // and PackageDexOptimizer. All these classes have their own flag to allow switching a single
@@ -462,9 +471,9 @@
 
     private static final String STATIC_SHARED_LIB_DELIMITER = "_";
     /** Extension of the compressed packages */
-    private final static String COMPRESSED_EXTENSION = ".gz";
+    public final static String COMPRESSED_EXTENSION = ".gz";
     /** Suffix of stub packages on the system partition */
-    private final static String STUB_SUFFIX = "-Stub";
+    public final static String STUB_SUFFIX = "-Stub";
 
     private static final int[] EMPTY_INT_ARRAY = new int[0];
 
@@ -3076,75 +3085,6 @@
         }
     }
 
-    private int decompressFile(File srcFile, File dstFile) throws ErrnoException {
-        if (DEBUG_COMPRESSION) {
-            Slog.i(TAG, "Decompress file"
-                    + "; src: " + srcFile.getAbsolutePath()
-                    + ", dst: " + dstFile.getAbsolutePath());
-        }
-        try (
-                InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
-                OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
-        ) {
-            Streams.copy(fileIn, fileOut);
-            Os.chmod(dstFile.getAbsolutePath(), 0644);
-            return PackageManager.INSTALL_SUCCEEDED;
-        } catch (IOException e) {
-            logCriticalInfo(Log.ERROR, "Failed to decompress file"
-                    + "; src: " + srcFile.getAbsolutePath()
-                    + ", dst: " + dstFile.getAbsolutePath());
-        }
-        return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-    }
-
-    private File[] getCompressedFiles(String codePath) {
-        final File stubCodePath = new File(codePath);
-        final String stubName = stubCodePath.getName();
-
-        // The layout of a compressed package on a given partition is as follows :
-        //
-        // Compressed artifacts:
-        //
-        // /partition/ModuleName/foo.gz
-        // /partation/ModuleName/bar.gz
-        //
-        // Stub artifact:
-        //
-        // /partition/ModuleName-Stub/ModuleName-Stub.apk
-        //
-        // In other words, stub is on the same partition as the compressed artifacts
-        // and in a directory that's suffixed with "-Stub".
-        int idx = stubName.lastIndexOf(STUB_SUFFIX);
-        if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
-            return null;
-        }
-
-        final File stubParentDir = stubCodePath.getParentFile();
-        if (stubParentDir == null) {
-            Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
-            return null;
-        }
-
-        final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
-        final File[] files = compressedPath.listFiles(new FilenameFilter() {
-            @Override
-            public boolean accept(File dir, String name) {
-                return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
-            }
-        });
-
-        if (DEBUG_COMPRESSION && files != null && files.length > 0) {
-            Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
-        }
-
-        return files;
-    }
-
-    private boolean compressedFileExists(String codePath) {
-        final File[] compressedFiles = getCompressedFiles(codePath);
-        return compressedFiles != null && compressedFiles.length > 0;
-    }
-
     /**
      * Decompresses the given package on the system image onto
      * the /data partition.
@@ -5385,56 +5325,6 @@
     }
 
     /**
-     * Compares two sets of signatures. Returns:
-     * <br />
-     * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
-     * <br />
-     * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
-     * <br />
-     * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
-     * <br />
-     * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
-     * <br />
-     * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
-     */
-    public static int compareSignatures(Signature[] s1, Signature[] s2) {
-        if (s1 == null) {
-            return s2 == null
-                    ? PackageManager.SIGNATURE_NEITHER_SIGNED
-                    : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
-        }
-
-        if (s2 == null) {
-            return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
-        }
-
-        if (s1.length != s2.length) {
-            return PackageManager.SIGNATURE_NO_MATCH;
-        }
-
-        // Since both signature sets are of size 1, we can compare without HashSets.
-        if (s1.length == 1) {
-            return s1[0].equals(s2[0]) ?
-                    PackageManager.SIGNATURE_MATCH :
-                    PackageManager.SIGNATURE_NO_MATCH;
-        }
-
-        ArraySet<Signature> set1 = new ArraySet<Signature>();
-        for (Signature sig : s1) {
-            set1.add(sig);
-        }
-        ArraySet<Signature> set2 = new ArraySet<Signature>();
-        for (Signature sig : s2) {
-            set2.add(sig);
-        }
-        // Make sure s2 contains all signatures in s1.
-        if (set1.equals(set2)) {
-            return PackageManager.SIGNATURE_MATCH;
-        }
-        return PackageManager.SIGNATURE_NO_MATCH;
-    }
-
-    /**
      * If the database version for this type of package (internal storage or
      * external storage) is less than the version where package signatures
      * were updated, return true.
@@ -5444,76 +5334,11 @@
         return ver.databaseVersion < DatabaseVersion.SIGNATURE_END_ENTITY;
     }
 
-    /**
-     * Used for backward compatibility to make sure any packages with
-     * certificate chains get upgraded to the new style. {@code existingSigs}
-     * will be in the old format (since they were stored on disk from before the
-     * system upgrade) and {@code scannedSigs} will be in the newer format.
-     */
-    private int compareSignaturesCompat(PackageSignatures existingSigs,
-            PackageParser.Package scannedPkg) {
-        if (!isCompatSignatureUpdateNeeded(scannedPkg)) {
-            return PackageManager.SIGNATURE_NO_MATCH;
-        }
-
-        ArraySet<Signature> existingSet = new ArraySet<Signature>();
-        for (Signature sig : existingSigs.mSignatures) {
-            existingSet.add(sig);
-        }
-        ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
-        for (Signature sig : scannedPkg.mSignatures) {
-            try {
-                Signature[] chainSignatures = sig.getChainSignatures();
-                for (Signature chainSig : chainSignatures) {
-                    scannedCompatSet.add(chainSig);
-                }
-            } catch (CertificateEncodingException e) {
-                scannedCompatSet.add(sig);
-            }
-        }
-        /*
-         * Make sure the expanded scanned set contains all signatures in the
-         * existing one.
-         */
-        if (scannedCompatSet.equals(existingSet)) {
-            // Migrate the old signatures to the new scheme.
-            existingSigs.assignSignatures(scannedPkg.mSignatures);
-            // The new KeySets will be re-added later in the scanning process.
-            synchronized (mPackages) {
-                mSettings.mKeySetManagerService.removeAppKeySetDataLPw(scannedPkg.packageName);
-            }
-            return PackageManager.SIGNATURE_MATCH;
-        }
-        return PackageManager.SIGNATURE_NO_MATCH;
-    }
-
     private boolean isRecoverSignatureUpdateNeeded(PackageParser.Package scannedPkg) {
         final VersionInfo ver = getSettingsVersionForPackage(scannedPkg);
         return ver.databaseVersion < DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
     }
 
-    private int compareSignaturesRecover(PackageSignatures existingSigs,
-            PackageParser.Package scannedPkg) {
-        if (!isRecoverSignatureUpdateNeeded(scannedPkg)) {
-            return PackageManager.SIGNATURE_NO_MATCH;
-        }
-
-        String msg = null;
-        try {
-            if (Signature.areEffectiveMatch(existingSigs.mSignatures, scannedPkg.mSignatures)) {
-                logCriticalInfo(Log.INFO, "Recovered effectively matching certificates for "
-                        + scannedPkg.packageName);
-                return PackageManager.SIGNATURE_MATCH;
-            }
-        } catch (CertificateException e) {
-            msg = e.getMessage();
-        }
-
-        logCriticalInfo(Log.INFO,
-                "Failed to recover certificates for " + scannedPkg.packageName + ": " + msg);
-        return PackageManager.SIGNATURE_NO_MATCH;
-    }
-
     @Override
     public List<String> getAllPackages() {
         final int callingUid = Binder.getCallingUid();
@@ -8237,51 +8062,10 @@
         parallelPackageParser.close();
     }
 
-    private static File getSettingsProblemFile() {
-        File dataDir = Environment.getDataDirectory();
-        File systemDir = new File(dataDir, "system");
-        File fname = new File(systemDir, "uiderrors.txt");
-        return fname;
-    }
-
     public static void reportSettingsProblem(int priority, String msg) {
         logCriticalInfo(priority, msg);
     }
 
-    public static void logCriticalInfo(int priority, String msg) {
-        Slog.println(priority, TAG, msg);
-        EventLogTags.writePmCriticalInfo(msg);
-        try {
-            File fname = getSettingsProblemFile();
-            FileOutputStream out = new FileOutputStream(fname, true);
-            PrintWriter pw = new FastPrintWriter(out);
-            SimpleDateFormat formatter = new SimpleDateFormat();
-            String dateString = formatter.format(new Date(System.currentTimeMillis()));
-            pw.println(dateString + ": " + msg);
-            pw.close();
-            FileUtils.setPermissions(
-                    fname.toString(),
-                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
-                    -1, -1);
-        } catch (java.io.IOException e) {
-        }
-    }
-
-    private long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
-        if (srcFile.isDirectory()) {
-            final File baseFile = new File(pkg.baseCodePath);
-            long maxModifiedTime = baseFile.lastModified();
-            if (pkg.splitCodePaths != null) {
-                for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
-                    final File splitFile = new File(pkg.splitCodePaths[i]);
-                    maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
-                }
-            }
-            return maxModifiedTime;
-        }
-        return srcFile.lastModified();
-    }
-
     private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile,
             final int policyFlags) throws PackageManagerException {
         // When upgrading from pre-N MR1, verify the package time stamp using the package
@@ -8294,7 +8078,7 @@
                 && !isCompatSignatureUpdateNeeded(pkg)
                 && !isRecoverSignatureUpdateNeeded(pkg)) {
             long mSigningKeySetId = ps.keySetData.getProperSigningKeySet();
-            KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            final KeySetManagerService ksms = mSettings.mKeySetManagerService;
             ArraySet<PublicKey> signingKs;
             synchronized (mPackages) {
                 signingKs = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId);
@@ -8710,7 +8494,7 @@
         return scannedPkg;
     }
 
-    private void renameStaticSharedLibraryPackage(PackageParser.Package pkg) {
+    private static void renameStaticSharedLibraryPackage(PackageParser.Package pkg) {
         // Derive the new package synthetic package name
         pkg.setPackageName(pkg.packageName + STATIC_SHARED_LIB_DELIMITER
                 + pkg.staticSharedLibVersion);
@@ -8724,49 +8508,6 @@
         return processName;
     }
 
-    private void verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg)
-            throws PackageManagerException {
-        if (pkgSetting.signatures.mSignatures != null) {
-            // Already existing package. Make sure signatures match
-            boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
-                    == PackageManager.SIGNATURE_MATCH;
-            if (!match) {
-                match = compareSignaturesCompat(pkgSetting.signatures, pkg)
-                        == PackageManager.SIGNATURE_MATCH;
-            }
-            if (!match) {
-                match = compareSignaturesRecover(pkgSetting.signatures, pkg)
-                        == PackageManager.SIGNATURE_MATCH;
-            }
-            if (!match) {
-                throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
-                        + pkg.packageName + " signatures do not match the "
-                        + "previously installed version; ignoring!");
-            }
-        }
-
-        // Check for shared user signatures
-        if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
-            // Already existing package. Make sure signatures match
-            boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
-                    pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
-            if (!match) {
-                match = compareSignaturesCompat(pkgSetting.sharedUser.signatures, pkg)
-                        == PackageManager.SIGNATURE_MATCH;
-            }
-            if (!match) {
-                match = compareSignaturesRecover(pkgSetting.sharedUser.signatures, pkg)
-                        == PackageManager.SIGNATURE_MATCH;
-            }
-            if (!match) {
-                throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
-                        "Package " + pkg.packageName
-                        + " has no signatures that match those in shared user "
-                        + pkgSetting.sharedUser.name + "; ignoring!");
-            }
-        }
-    }
-
     /**
      * Enforces that only the system UID or root's UID can call a method exposed
      * via Binder.
@@ -9737,24 +9478,6 @@
         return res;
     }
 
-    /**
-     * Derive the value of the {@code cpuAbiOverride} based on the provided
-     * value and an optional stored value from the package settings.
-     */
-    private static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
-        String cpuAbiOverride = null;
-
-        if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
-            cpuAbiOverride = null;
-        } else if (abiOverride != null) {
-            cpuAbiOverride = abiOverride;
-        } else if (settings != null) {
-            cpuAbiOverride = settings.cpuAbiOverrideString;
-        }
-
-        return cpuAbiOverride;
-    }
-
     private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,
             final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
                     throws PackageManagerException {
@@ -10102,8 +9825,9 @@
                 }
             }
 
-            if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
-                if (checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
+            final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+                if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) {
                     // We just determined the app is signed correctly, so bring
                     // over the latest parsed certs.
                     pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -10121,8 +9845,16 @@
                 }
             } else {
                 try {
-                    // SIDE EFFECTS; compareSignaturesCompat() changes KeysetManagerService
-                    verifySignaturesLP(signatureCheckPs, pkg);
+                    final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
+                    final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
+                    final boolean compatMatch = verifySignatures(signatureCheckPs, pkg.mSignatures,
+                            compareCompat, compareRecover);
+                    // The new KeySets will be re-added later in the scanning process.
+                    if (compatMatch) {
+                        synchronized (mPackages) {
+                            ksms.removeAppKeySetDataLPw(pkg.packageName);
+                        }
+                    }
                     // We just determined the app is signed correctly, so bring
                     // over the latest parsed certs.
                     pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -10410,7 +10142,7 @@
         }
 
         // Make sure we're not adding any bogus keyset info
-        KeySetManagerService ksms = mSettings.mKeySetManagerService;
+        final KeySetManagerService ksms = mSettings.mKeySetManagerService;
         ksms.assertScannedPackageValid(pkg);
 
         synchronized (mPackages) {
@@ -15566,42 +15298,6 @@
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
-    private boolean shouldCheckUpgradeKeySetLP(PackageSettingBase oldPs, int scanFlags) {
-        // Can't rotate keys during boot or if sharedUser.
-        if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
-                || !oldPs.keySetData.isUsingUpgradeKeySets()) {
-            return false;
-        }
-        // app is using upgradeKeySets; make sure all are valid
-        KeySetManagerService ksms = mSettings.mKeySetManagerService;
-        long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();
-        for (int i = 0; i < upgradeKeySets.length; i++) {
-            if (!ksms.isIdValidKeySetId(upgradeKeySets[i])) {
-                Slog.wtf(TAG, "Package "
-                         + (oldPs.name != null ? oldPs.name : "<null>")
-                         + " contains upgrade-key-set reference to unknown key-set: "
-                         + upgradeKeySets[i]
-                         + " reverting to signatures check.");
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private boolean checkUpgradeKeySetLP(PackageSettingBase oldPS, PackageParser.Package newPkg) {
-        // Upgrade keysets are being used.  Determine if new package has a superset of the
-        // required keys.
-        long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
-        KeySetManagerService ksms = mSettings.mKeySetManagerService;
-        for (int i = 0; i < upgradeKeySets.length; i++) {
-            Set<PublicKey> upgradeSet = ksms.getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
-            if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private static void updateDigest(MessageDigest digest, File file) throws IOException {
         try (DigestInputStream digestStream =
                 new DigestInputStream(new FileInputStream(file), digest)) {
@@ -15640,8 +15336,9 @@
             ps = mSettings.mPackages.get(pkgName);
 
             // verify signatures are valid
-            if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
-                if (!checkUpgradeKeySetLP(ps, pkg)) {
+            final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            if (ksms.shouldCheckUpgradeKeySetLocked(ps, scanFlags)) {
+                if (!ksms.checkUpgradeKeySetLocked(ps, pkg)) {
                     res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                             "New package not signed by keys specified by upgrade-keysets: "
                                     + pkgName);
@@ -16542,8 +16239,9 @@
                 // Quick sanity check that we're signed correctly if updating;
                 // we'll check this again later when scanning, but we want to
                 // bail early here before tripping over redefined permissions.
-                if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
-                    if (!checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
+                final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+                if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+                    if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) {
                         res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
                                 + pkg.packageName + " upgrade keys do not match the "
                                 + "previously installed version");
@@ -16551,7 +16249,16 @@
                     }
                 } else {
                     try {
-                        verifySignaturesLP(signatureCheckPs, pkg);
+                        final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
+                        final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
+                        final boolean compatMatch = verifySignatures(
+                                signatureCheckPs, pkg.mSignatures, compareCompat, compareRecover);
+                        // The new KeySets will be re-added later in the scanning process.
+                        if (compatMatch) {
+                            synchronized (mPackages) {
+                                ksms.removeAppKeySetDataLPw(pkg.packageName);
+                            }
+                        }
                     } catch (PackageManagerException e) {
                         res.setError(e.error, e.getMessage());
                         return;
@@ -16589,10 +16296,11 @@
                     final boolean sigsOk;
                     final String sourcePackageName = bp.getSourcePackageName();
                     final PackageSettingBase sourcePackageSetting = bp.getSourcePackageSetting();
+                    final KeySetManagerService ksms = mSettings.mKeySetManagerService;
                     if (sourcePackageName.equals(pkg.packageName)
-                            && (shouldCheckUpgradeKeySetLP(sourcePackageSetting,
-                                    scanFlags))) {
-                        sigsOk = checkUpgradeKeySetLP(sourcePackageSetting, pkg);
+                            && (ksms.shouldCheckUpgradeKeySetLocked(
+                                    sourcePackageSetting, scanFlags))) {
+                        sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg);
                     } else {
                         sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures,
                                 pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
@@ -20938,34 +20646,11 @@
 
                 pw.println();
                 pw.println("Package warning messages:");
-                BufferedReader in = null;
-                String line = null;
-                try {
-                    in = new BufferedReader(new FileReader(getSettingsProblemFile()));
-                    while ((line = in.readLine()) != null) {
-                        if (line.contains("ignored: updated version")) continue;
-                        pw.println(line);
-                    }
-                } catch (IOException ignored) {
-                } finally {
-                    IoUtils.closeQuietly(in);
-                }
+                dumpCriticalInfo(pw, null);
             }
 
             if (checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES)) {
-                BufferedReader in = null;
-                String line = null;
-                try {
-                    in = new BufferedReader(new FileReader(getSettingsProblemFile()));
-                    while ((line = in.readLine()) != null) {
-                        if (line.contains("ignored: updated version")) continue;
-                        pw.print("msg,");
-                        pw.println(line);
-                    }
-                } catch (IOException ignored) {
-                } finally {
-                    IoUtils.closeQuietly(in);
-                }
+                dumpCriticalInfo(pw, "msg,");
             }
         }
 
@@ -21011,26 +20696,11 @@
             dumpFeaturesProto(proto);
             mSettings.dumpPackagesProto(proto);
             mSettings.dumpSharedUsersProto(proto);
-            dumpMessagesProto(proto);
+            dumpCriticalInfo(proto);
         }
         proto.flush();
     }
 
-    private void dumpMessagesProto(ProtoOutputStream proto) {
-        BufferedReader in = null;
-        String line = null;
-        try {
-            in = new BufferedReader(new FileReader(getSettingsProblemFile()));
-            while ((line = in.readLine()) != null) {
-                if (line.contains("ignored: updated version")) continue;
-                proto.write(PackageServiceDumpProto.MESSAGES, line);
-            }
-        } catch (IOException ignored) {
-        } finally {
-            IoUtils.closeQuietly(in);
-        }
-    }
-
     private void dumpFeaturesProto(ProtoOutputStream proto) {
         synchronized (mAvailableFeatures) {
             final int count = mAvailableFeatures.size();
@@ -22439,7 +22109,7 @@
                 Slog.w(TAG, "KeySet requested for filtered package: " + packageName);
                 throw new IllegalArgumentException("Unknown package: " + packageName);
             }
-            KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            final KeySetManagerService ksms = mSettings.mKeySetManagerService;
             return new KeySet(ksms.getKeySetByAliasAndPackageNameLPr(packageName, alias));
         }
     }
@@ -22468,7 +22138,7 @@
                     && Process.SYSTEM_UID != callingUid) {
                 throw new SecurityException("May not access signing KeySet of other apps.");
             }
-            KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            final KeySetManagerService ksms = mSettings.mKeySetManagerService;
             return new KeySet(ksms.getSigningKeySetByPackageNameLPr(packageName));
         }
     }
@@ -22492,7 +22162,7 @@
             }
             IBinder ksh = ks.getToken();
             if (ksh instanceof KeySetHandle) {
-                KeySetManagerService ksms = mSettings.mKeySetManagerService;
+                final KeySetManagerService ksms = mSettings.mKeySetManagerService;
                 return ksms.packageIsSignedByLPr(packageName, (KeySetHandle) ksh);
             }
             return false;
@@ -22518,7 +22188,7 @@
             }
             IBinder ksh = ks.getToken();
             if (ksh instanceof KeySetHandle) {
-                KeySetManagerService ksms = mSettings.mKeySetManagerService;
+                final KeySetManagerService ksms = mSettings.mKeySetManagerService;
                 return ksms.packageIsSignedByExactlyLPr(packageName, (KeySetHandle) ksh);
             }
             return false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 67e06dd..758abd7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -16,42 +16,74 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
+import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
+import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
+import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
+import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+
+import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import com.android.server.EventLogTags;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.PackageDexUsage;
 
-import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
-import static com.android.server.pm.PackageManagerService.TAG;
-
-import com.android.internal.util.ArrayUtils;
-
 import android.annotation.NonNull;
 import android.app.AppGlobals;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
 import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
 import android.os.Build;
 import android.os.Debug;
+import android.os.Environment;
+import android.os.FileUtils;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.service.pm.PackageServiceDumpProto;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 import android.util.jar.StrictJarFile;
-import dalvik.system.VMRuntime;
-import libcore.io.Libcore;
+import android.util.proto.ProtoOutputStream;
 
+import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import libcore.io.Streams;
+
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FilenameFilter;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.function.Predicate;
+import java.util.zip.GZIPInputStream;
 import java.util.zip.ZipEntry;
 
 /**
@@ -200,7 +232,7 @@
      *
      * If it doesn't have sufficient information about the package, it return <code>false</code>.
      */
-    static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
+    public static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
             long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo,
             long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) {
 
@@ -263,6 +295,21 @@
         return false;
     }
 
+    public static long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
+        if (srcFile.isDirectory()) {
+            final File baseFile = new File(pkg.baseCodePath);
+            long maxModifiedTime = baseFile.lastModified();
+            if (pkg.splitCodePaths != null) {
+                for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
+                    final File splitFile = new File(pkg.splitCodePaths[i]);
+                    maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
+                }
+            }
+            return maxModifiedTime;
+        }
+        return srcFile.lastModified();
+    }
+
     /**
      * Checks that the archive located at {@code fileName} has uncompressed dex file and so
      * files that can be direclty mapped.
@@ -318,6 +365,57 @@
         }
     }
 
+    private static File getSettingsProblemFile() {
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        File fname = new File(systemDir, "uiderrors.txt");
+        return fname;
+    }
+
+    public static void dumpCriticalInfo(ProtoOutputStream proto) {
+        try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
+            String line = null;
+            while ((line = in.readLine()) != null) {
+                if (line.contains("ignored: updated version")) continue;
+                proto.write(PackageServiceDumpProto.MESSAGES, line);
+            }
+        } catch (IOException ignored) {
+        }
+    }
+
+    public static void dumpCriticalInfo(PrintWriter pw, String msg) {
+        try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
+            String line = null;
+            while ((line = in.readLine()) != null) {
+                if (line.contains("ignored: updated version")) continue;
+                if (msg != null) {
+                    pw.print(msg);
+                }
+                pw.println(line);
+            }
+        } catch (IOException ignored) {
+        }
+    }
+
+    public static void logCriticalInfo(int priority, String msg) {
+        Slog.println(priority, TAG, msg);
+        EventLogTags.writePmCriticalInfo(msg);
+        try {
+            File fname = getSettingsProblemFile();
+            FileOutputStream out = new FileOutputStream(fname, true);
+            PrintWriter pw = new FastPrintWriter(out);
+            SimpleDateFormat formatter = new SimpleDateFormat();
+            String dateString = formatter.format(new Date(System.currentTimeMillis()));
+            pw.println(dateString + ": " + msg);
+            pw.close();
+            FileUtils.setPermissions(
+                    fname.toString(),
+                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
+                    -1, -1);
+        } catch (java.io.IOException e) {
+        }
+    }
+
     public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
         if (callingUid == Process.SHELL_UID) {
             if (userHandle >= 0
@@ -331,4 +429,240 @@
             }
         }
     }
+
+    /**
+     * Derive the value of the {@code cpuAbiOverride} based on the provided
+     * value and an optional stored value from the package settings.
+     */
+    public static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
+        String cpuAbiOverride = null;
+        if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
+            cpuAbiOverride = null;
+        } else if (abiOverride != null) {
+            cpuAbiOverride = abiOverride;
+        } else if (settings != null) {
+            cpuAbiOverride = settings.cpuAbiOverrideString;
+        }
+        return cpuAbiOverride;
+    }
+
+    /**
+     * Compares two sets of signatures. Returns:
+     * <br />
+     * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
+     * <br />
+     * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
+     * <br />
+     * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
+     * <br />
+     * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
+     * <br />
+     * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
+     */
+    public static int compareSignatures(Signature[] s1, Signature[] s2) {
+        if (s1 == null) {
+            return s2 == null
+                    ? PackageManager.SIGNATURE_NEITHER_SIGNED
+                    : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
+        }
+
+        if (s2 == null) {
+            return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
+        }
+
+        if (s1.length != s2.length) {
+            return PackageManager.SIGNATURE_NO_MATCH;
+        }
+
+        // Since both signature sets are of size 1, we can compare without HashSets.
+        if (s1.length == 1) {
+            return s1[0].equals(s2[0]) ?
+                    PackageManager.SIGNATURE_MATCH :
+                    PackageManager.SIGNATURE_NO_MATCH;
+        }
+
+        ArraySet<Signature> set1 = new ArraySet<Signature>();
+        for (Signature sig : s1) {
+            set1.add(sig);
+        }
+        ArraySet<Signature> set2 = new ArraySet<Signature>();
+        for (Signature sig : s2) {
+            set2.add(sig);
+        }
+        // Make sure s2 contains all signatures in s1.
+        if (set1.equals(set2)) {
+            return PackageManager.SIGNATURE_MATCH;
+        }
+        return PackageManager.SIGNATURE_NO_MATCH;
+    }
+
+    /**
+     * Used for backward compatibility to make sure any packages with
+     * certificate chains get upgraded to the new style. {@code existingSigs}
+     * will be in the old format (since they were stored on disk from before the
+     * system upgrade) and {@code scannedSigs} will be in the newer format.
+     */
+    private static boolean matchSignaturesCompat(String packageName,
+            PackageSignatures packageSignatures, Signature[] parsedSignatures) {
+        ArraySet<Signature> existingSet = new ArraySet<Signature>();
+        for (Signature sig : packageSignatures.mSignatures) {
+            existingSet.add(sig);
+        }
+        ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
+        for (Signature sig : parsedSignatures) {
+            try {
+                Signature[] chainSignatures = sig.getChainSignatures();
+                for (Signature chainSig : chainSignatures) {
+                    scannedCompatSet.add(chainSig);
+                }
+            } catch (CertificateEncodingException e) {
+                scannedCompatSet.add(sig);
+            }
+        }
+        // make sure the expanded scanned set contains all signatures in the existing one
+        if (scannedCompatSet.equals(existingSet)) {
+            // migrate the old signatures to the new scheme
+            packageSignatures.assignSignatures(parsedSignatures);
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean matchSignaturesRecover(String packageName,
+            Signature[] existingSignatures, Signature[] parsedSignatures) {
+        String msg = null;
+        try {
+            if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) {
+                logCriticalInfo(Log.INFO,
+                        "Recovered effectively matching certificates for " + packageName);
+                return true;
+            }
+        } catch (CertificateException e) {
+            msg = e.getMessage();
+        }
+        logCriticalInfo(Log.INFO,
+                "Failed to recover certificates for " + packageName + ": " + msg);
+        return false;
+    }
+
+    /**
+     * Verifies that signatures match.
+     * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
+     * @throws PackageManagerException if the signatures did not match.
+     */
+    public static boolean verifySignatures(PackageSetting pkgSetting,
+            Signature[] parsedSignatures, boolean compareCompat, boolean compareRecover)
+            throws PackageManagerException {
+        final String packageName = pkgSetting.name;
+        boolean compatMatch = false;
+        if (pkgSetting.signatures.mSignatures != null) {
+            // Already existing package. Make sure signatures match
+            boolean match = compareSignatures(pkgSetting.signatures.mSignatures, parsedSignatures)
+                    == PackageManager.SIGNATURE_MATCH;
+            if (!match && compareCompat) {
+                match = matchSignaturesCompat(packageName, pkgSetting.signatures, parsedSignatures);
+                compatMatch = match;
+            }
+            if (!match && compareRecover) {
+                match = matchSignaturesRecover(
+                        packageName, pkgSetting.signatures.mSignatures, parsedSignatures);
+            }
+            if (!match) {
+                throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                        "Package " + packageName +
+                        " signatures don't match previously installed version; ignoring!");
+            }
+        }
+        // Check for shared user signatures
+        if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
+            // Already existing package. Make sure signatures match
+            boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
+                    parsedSignatures) == PackageManager.SIGNATURE_MATCH;
+            if (!match) {
+                match = matchSignaturesCompat(
+                        packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
+            }
+            if (!match && compareCompat) {
+                match = matchSignaturesRecover(
+                        packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures);
+                compatMatch |= match;
+            }
+            if (!match && compareRecover) {
+                throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+                        "Package " + packageName
+                        + " has no signatures that match those in shared user "
+                        + pkgSetting.sharedUser.name + "; ignoring!");
+            }
+        }
+        return compatMatch;
+    }
+
+    public static int decompressFile(File srcFile, File dstFile) throws ErrnoException {
+        if (DEBUG_COMPRESSION) {
+            Slog.i(TAG, "Decompress file"
+                    + "; src: " + srcFile.getAbsolutePath()
+                    + ", dst: " + dstFile.getAbsolutePath());
+        }
+        try (
+                InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
+                OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
+        ) {
+            Streams.copy(fileIn, fileOut);
+            Os.chmod(dstFile.getAbsolutePath(), 0644);
+            return PackageManager.INSTALL_SUCCEEDED;
+        } catch (IOException e) {
+            logCriticalInfo(Log.ERROR, "Failed to decompress file"
+                    + "; src: " + srcFile.getAbsolutePath()
+                    + ", dst: " + dstFile.getAbsolutePath());
+        }
+        return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+    }
+
+    public static File[] getCompressedFiles(String codePath) {
+        final File stubCodePath = new File(codePath);
+        final String stubName = stubCodePath.getName();
+
+        // The layout of a compressed package on a given partition is as follows :
+        //
+        // Compressed artifacts:
+        //
+        // /partition/ModuleName/foo.gz
+        // /partation/ModuleName/bar.gz
+        //
+        // Stub artifact:
+        //
+        // /partition/ModuleName-Stub/ModuleName-Stub.apk
+        //
+        // In other words, stub is on the same partition as the compressed artifacts
+        // and in a directory that's suffixed with "-Stub".
+        int idx = stubName.lastIndexOf(STUB_SUFFIX);
+        if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
+            return null;
+        }
+
+        final File stubParentDir = stubCodePath.getParentFile();
+        if (stubParentDir == null) {
+            Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
+            return null;
+        }
+
+        final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
+        final File[] files = compressedPath.listFiles(new FilenameFilter() {
+            @Override
+            public boolean accept(File dir, String name) {
+                return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
+            }
+        });
+
+        if (DEBUG_COMPRESSION && files != null && files.length > 0) {
+            Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
+        }
+
+        return files;
+    }
+
+    public static boolean compressedFileExists(String codePath) {
+        final File[] compressedFiles = getCompressedFiles(codePath);
+        return compressedFiles != null && compressedFiles.length > 0;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index bfe09b8..96c102b 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Environment;
@@ -40,8 +42,6 @@
 import java.util.Objects;
 import java.util.Set;
 
-import static com.android.server.pm.PackageManagerService.logCriticalInfo;
-
 /**
  * Helper class for preparing and destroying user storage
  */
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 8014acf..c40d1fa 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm.permission;
 
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1110,8 +1112,8 @@
         final String systemPackageName = mServiceInternal.getKnownPackageName(
                 PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
         final PackageParser.Package systemPackage = getPackage(systemPackageName);
-        return PackageManagerService.compareSignatures(systemPackage.mSignatures,
-                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+        return compareSignatures(systemPackage.mSignatures, pkg.mSignatures)
+                == PackageManager.SIGNATURE_MATCH;
     }
 
     private void grantDefaultPermissionExceptions(int userId) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 6d2051f..7d8e206 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1009,10 +1009,10 @@
                 PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
         final PackageParser.Package systemPackage =
                 mPackageManagerInt.getPackage(systemPackageName);
-        boolean allowed = (PackageManagerService.compareSignatures(
+        boolean allowed = (PackageManagerServiceUtils.compareSignatures(
                                 bp.getSourceSignatures(), pkg.mSignatures)
                         == PackageManager.SIGNATURE_MATCH)
-                || (PackageManagerService.compareSignatures(
+                || (PackageManagerServiceUtils.compareSignatures(
                                 systemPackage.mSignatures, pkg.mSignatures)
                         == PackageManager.SIGNATURE_MATCH);
         if (!allowed && (privilegedPermission || oemPermission)) {
diff --git a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
index 92729dc..6886985 100644
--- a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
+++ b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
@@ -253,7 +253,8 @@
     public void onDisplayChanged(int displayId) {
         if (displayId == mDisplay.getDisplayId()) {
             if (mDisplay.getState() == Display.STATE_DOZE
-                    || mDisplay.getState() == Display.STATE_DOZE_SUSPEND) {
+                    || mDisplay.getState() == Display.STATE_DOZE_SUSPEND
+                    || mDisplay.getState() == Display.STATE_ON_SUSPEND) {
                 startBurnInProtection();
             } else {
                 cancelBurnInProtection();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index fd186ce..45f645b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -66,6 +66,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
@@ -198,6 +199,7 @@
 import android.service.vr.IPersistentVrStateCallbacks;
 import android.speech.RecognizerIntent;
 import android.telecom.TelecomManager;
+import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
@@ -456,6 +458,7 @@
     private AccessibilityShortcutController mAccessibilityShortcutController;
 
     boolean mSafeMode;
+    private final ArraySet<WindowState> mScreenDecorWindows = new ArraySet<>();
     WindowState mStatusBar = null;
     int mStatusBarHeight;
     WindowState mNavigationBar = null;
@@ -2613,7 +2616,19 @@
     }
 
     @Override
-    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
+    public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+            boolean hasStatusBarServicePermission) {
+
+        final boolean isScreenDecor = (attrs.privateFlags & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;
+        if (mScreenDecorWindows.contains(win)) {
+            if (!isScreenDecor) {
+                // No longer has the flag set, so remove from the set.
+                mScreenDecorWindows.remove(win);
+            }
+        } else if (isScreenDecor && hasStatusBarServicePermission) {
+            mScreenDecorWindows.add(win);
+        }
+
         switch (attrs.type) {
             case TYPE_SYSTEM_OVERLAY:
             case TYPE_SECURE_SYSTEM_OVERLAY:
@@ -3074,6 +3089,14 @@
      */
     @Override
     public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) {
+
+        if ((attrs.privateFlags & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.STATUS_BAR_SERVICE,
+                    "PhoneWindowManager");
+            mScreenDecorWindows.add(win);
+        }
+
         switch (attrs.type) {
             case TYPE_STATUS_BAR:
                 mContext.enforceCallingOrSelfPermission(
@@ -3125,6 +3148,7 @@
             mNavigationBar = null;
             mNavigationBarController.setWindow(null);
         }
+        mScreenDecorWindows.remove(win);
     }
 
     static final boolean PRINT_ANIM = false;
@@ -4395,8 +4419,9 @@
 
     /** {@inheritDoc} */
     @Override
-    public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
-                              int displayRotation, int uiMode) {
+    public void beginLayoutLw(int displayId, int displayWidth, int displayHeight,
+            int displayRotation, int uiMode) {
+        final boolean isDefaultDisplay = displayId == DEFAULT_DISPLAY;
         mDisplayRotation = displayRotation;
         final int overscanLeft, overscanTop, overscanRight, overscanBottom;
         if (isDefaultDisplay) {
@@ -4524,6 +4549,71 @@
                 updateSystemUiVisibilityLw();
             }
         }
+        layoutScreenDecorWindows(displayId, displayWidth, displayHeight, pf, df, dcf);
+    }
+
+    private void layoutScreenDecorWindows(int displayId, int displayWidth, int displayHeight,
+            Rect pf, Rect df, Rect dcf) {
+        if (mScreenDecorWindows.isEmpty()) {
+            return;
+        }
+
+        for (int i = mScreenDecorWindows.size() - 1; i >= 0; --i) {
+            final WindowState w = mScreenDecorWindows.valueAt(i);
+            if (w.getDisplayId() != displayId || !w.isVisibleLw()) {
+                // Skip if not on the same display or not visible.
+                continue;
+            }
+
+            w.computeFrameLw(pf /* parentFrame */, df /* displayFrame */, df /* overlayFrame */,
+                    df /* contentFrame */, df /* visibleFrame */, dcf /* decorFrame */,
+                    df /* stableFrame */, df /* outsetFrame */);
+            final Rect frame = w.getFrameLw();
+
+            if (frame.left <= 0 && frame.top <= 0) {
+                // Docked at left or top.
+                if (frame.bottom >= displayHeight) {
+                    // Docked left.
+                    mDockLeft = Math.max(frame.right, mDockLeft);
+                } else if (frame.right >= displayWidth ) {
+                    // Docked top.
+                    mDockTop = Math.max(frame.bottom, mDockTop);
+                } else {
+                    Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+                            + " not docked on left or top of display. frame=" + frame
+                            + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
+                }
+            } else if (frame.right >= displayWidth && frame.bottom >= displayHeight) {
+                // Docked at right or bottom.
+                if (frame.top <= 0) {
+                    // Docked right.
+                    mDockRight = Math.min(frame.left, mDockRight);
+                } else if (frame.left <= 0) {
+                    // Docked bottom.
+                    mDockBottom = Math.min(frame.top, mDockBottom);
+                } else {
+                    Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+                            + " not docked on right or bottom" + " of display. frame=" + frame
+                            + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
+                }
+            } else {
+                // Screen decor windows are required to be docked on one of the sides of the screen.
+                Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+                        + " not docked on one of the sides of the display. frame=" + frame
+                        + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
+            }
+        }
+
+        mContentTop = mSystemTop = mVoiceContentTop = mCurTop = mRestrictedScreenTop = mDockTop;
+        mContentLeft = mSystemLeft = mVoiceContentLeft = mCurLeft = mRestrictedScreenLeft
+                = mRestrictedOverscanScreenLeft = mDockLeft;
+        mContentBottom = mSystemBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
+        mContentRight = mSystemRight = mVoiceContentRight = mCurRight = mDockRight;
+
+        mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
+        mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop;
+        mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
+        mRestrictedOverscanScreenHeight = mDockBottom - mRestrictedOverscanScreenTop;
     }
 
     private boolean layoutStatusBar(Rect pf, Rect df, Rect of, Rect vf, Rect dcf, int sysui,
@@ -4823,9 +4913,11 @@
     /** {@inheritDoc} */
     @Override
     public void layoutWindowLw(WindowState win, WindowState attached) {
-        // We've already done the navigation bar and status bar. If the status bar can receive
-        // input, we need to layout it again to accomodate for the IME window.
-        if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) {
+        // We've already done the navigation bar, status bar, and all screen decor windows. If the
+        // status bar can receive input, we need to layout it again to accommodate for the IME
+        // window.
+        if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar
+                || mScreenDecorWindows.contains(win)) {
             return;
         }
         final WindowManager.LayoutParams attrs = win.getAttrs();
@@ -4864,6 +4956,7 @@
         }
 
         if (!isDefaultDisplay) {
+            // TODO: Need to fix this and above to take into account decor windows.
             if (attached != null) {
                 // If this window is attached to another, our display
                 // frame is the same as the one we are attached to.
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8f23cf8..416a606 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2364,9 +2364,13 @@
 
             if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
                 mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
-                if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
-                        && (mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
-                    mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+                if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+                    if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
+                        mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+                    }
+                    if (mDisplayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
+                        mDisplayPowerRequest.dozeScreenState = Display.STATE_ON;
+                    }
                 }
                 mDisplayPowerRequest.dozeScreenBrightness =
                         mDozeScreenBrightnessOverrideFromDreamManager;
@@ -4676,6 +4680,7 @@
                 case Display.STATE_OFF:
                 case Display.STATE_DOZE:
                 case Display.STATE_DOZE_SUSPEND:
+                case Display.STATE_ON_SUSPEND:
                 case Display.STATE_ON:
                 case Display.STATE_VR:
                     break;
diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
deleted file mode 100644
index cabce18..0000000
--- a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 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.updates;
-
-import com.android.timezone.distro.TimeZoneDistro;
-import com.android.timezone.distro.installer.TimeZoneDistroInstaller;
-
-import android.util.Slog;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * An install receiver responsible for installing timezone data updates.
- */
-public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver {
-
-    private static final String TAG = "TZDataInstallReceiver";
-
-    private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata");
-    private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
-    private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/";
-    private static final String UPDATE_METADATA_DIR_NAME = "metadata/";
-    private static final String UPDATE_VERSION_FILE_NAME = "version";
-    private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_distro.zip";
-
-    private final TimeZoneDistroInstaller installer;
-
-    public TzDataInstallReceiver() {
-        super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME,
-                UPDATE_VERSION_FILE_NAME);
-        installer = new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR);
-    }
-
-    @Override
-    protected void install(byte[] content, int version) throws IOException {
-        TimeZoneDistro distro = new TimeZoneDistro(content);
-        boolean valid = installer.install(distro);
-        Slog.i(TAG, "Timezone data install valid for this device: " + valid);
-        // Even if !valid, we call super.install(). Only in the event of an exception should we
-        // not. If we didn't do this we could attempt to install repeatedly.
-        super.install(content, version);
-    }
-}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2f54e0e..67d62e1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2877,8 +2877,7 @@
             Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + " dh=" + dh);
         }
 
-        mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation,
-                getConfiguration().uiMode);
+        mService.mPolicy.beginLayoutLw(mDisplayId, dw, dh, mRotation, getConfiguration().uiMode);
         if (isDefaultDisplay) {
             // Not needed on non-default displays.
             mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 79d46ce..cc4e0f8 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -35,6 +35,7 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.View;
+import com.android.server.input.InputWindowHandle;
 
 /**
  * Managing drag and drop operations initiated by View#startDragAndDrop.
@@ -49,7 +50,12 @@
     static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 2;
     static final int MSG_ANIMATION_END = 3;
 
-    DragState mDragState;
+    /**
+     * Drag state per operation.
+     * The variable is cleared by {@code #onDragStateClosedLocked} which is invoked by DragState
+     * itself, thus the variable can be null after calling DragState's methods.
+     */
+    private DragState mDragState;
 
     private WindowManagerService mService;
     private final Handler mHandler;
@@ -58,11 +64,19 @@
         return mDragState != null;
     }
 
+    InputWindowHandle getInputWindowHandleLocked() {
+        return mDragState.getInputWindowHandle();
+    }
+
     DragDropController(WindowManagerService service, Looper looper) {
         mService = service;
         mHandler = new DragHandler(service, looper);
     }
 
+    void sendDragStartedIfNeededLocked(WindowState window) {
+        mDragState.sendDragStartedIfNeededLocked(window);
+    }
+
     IBinder prepareDrag(SurfaceSession session, int callerPid,
             int callerUid, IWindow window, int flags, int width, int height, Surface outSurface) {
         if (DEBUG_DRAG) {
@@ -158,9 +172,7 @@
             if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
                     mDragState.getInputChannel())) {
                 Slog.e(TAG_WM, "Unable to transfer touch focus");
-                mDragState.unregister();
-                mDragState.reset();
-                mDragState = null;
+                mDragState.closeLocked();
                 return false;
             }
 
@@ -254,6 +266,30 @@
         }
     }
 
+    /**
+     * Handles motion events.
+     * @param keepHandling Whether if the drag operation is continuing or this is the last motion
+     *          event.
+     * @param newX X coordinate value in dp in the screen coordinate
+     * @param newY Y coordinate value in dp in the screen coordinate
+     */
+    void handleMotionEvent(boolean keepHandling, float newX, float newY) {
+        synchronized (mService.mWindowMap) {
+            if (!dragDropActiveLocked()) {
+                // The drag has ended but the clean-up message has not been processed by
+                // window manager. Drop events that occur after this until window manager
+                // has a chance to clean-up the input handle.
+                return;
+            }
+
+            if (keepHandling) {
+                mDragState.notifyMoveLocked(newX, newY);
+            } else {
+                mDragState.notifyDropLocked(newX, newY);
+            }
+        }
+    }
+
     void dragRecipientEntered(IWindow window) {
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
@@ -282,6 +318,17 @@
         mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
     }
 
+    /**
+     * Notifies the current drag state is closed.
+     */
+    void onDragStateClosedLocked(DragState dragState) {
+        if (mDragState != dragState) {
+            Slog.wtf(TAG_WM, "Unknown drag state is closed");
+            return;
+        }
+        mDragState = null;
+    }
+
     private class DragHandler extends Handler {
         /**
          * Lock for window manager.
@@ -304,9 +351,7 @@
                     synchronized (mService.mWindowMap) {
                         // !!! TODO: ANR the app that has failed to start the drag in time
                         if (mDragState != null) {
-                            mDragState.unregister();
-                            mDragState.reset();
-                            mDragState = null;
+                            mDragState.closeLocked();
                         }
                     }
                     break;
@@ -346,7 +391,7 @@
                                     "plyaing animation");
                             return;
                         }
-                        mDragState.onAnimationEndLocked();
+                        mDragState.closeLocked();
                     }
                     break;
                 }
diff --git a/services/core/java/com/android/server/wm/DragInputEventReceiver.java b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
index b4bbc90..bee2bac 100644
--- a/services/core/java/com/android/server/wm/DragInputEventReceiver.java
+++ b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
@@ -37,7 +37,6 @@
  * Input receiver for drag and drop
  */
 class DragInputEventReceiver extends InputEventReceiver {
-    private final WindowManagerService mService;
     private final DragDropController mDragDropController;
 
     // Set, if stylus button was down at the start of the drag.
@@ -48,100 +47,63 @@
     // are still being dispatched.
     private boolean mMuteInput = false;
 
-    public DragInputEventReceiver(InputChannel inputChannel, Looper looper,
-            DragDropController controller, WindowManagerService service) {
+    DragInputEventReceiver(InputChannel inputChannel, Looper looper,
+            DragDropController controller) {
         super(inputChannel, looper);
         mDragDropController = controller;
-        mService = service;
     }
 
     @Override
     public void onInputEvent(InputEvent event, int displayId) {
         boolean handled = false;
         try {
-            synchronized (mService.mWindowMap) {
-                if (!mDragDropController.dragDropActiveLocked()) {
-                    // The drag has ended but the clean-up message has not been processed by
-                    // window manager. Drop events that occur after this until window manager
-                    // has a chance to clean-up the input handle.
-                    handled = true;
-                    return;
-                }
-                if (!(event instanceof MotionEvent)
-                        || (event.getSource() & SOURCE_CLASS_POINTER) == 0
-                        || mMuteInput) {
-                    return;
-                }
-                final MotionEvent motionEvent = (MotionEvent) event;
-                boolean endDrag = false;
-                final float newX = motionEvent.getRawX();
-                final float newY = motionEvent.getRawY();
-                final boolean isStylusButtonDown =
-                        (motionEvent.getButtonState() & BUTTON_STYLUS_PRIMARY) != 0;
-
-                if (mIsStartEvent) {
-                    if (isStylusButtonDown) {
-                        // First event and the button was down, check for the button being
-                        // lifted in the future, if that happens we'll drop the item.
-                        mStylusButtonDownAtStart = true;
-                    }
-                    mIsStartEvent = false;
-                }
-
-                switch (motionEvent.getAction()) {
-                    case ACTION_DOWN: {
-                        if (DEBUG_DRAG) Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
-                    }
-                    break;
-
-                    case ACTION_MOVE: {
-                        if (mStylusButtonDownAtStart && !isStylusButtonDown) {
-                            if (DEBUG_DRAG) {
-                                Slog.d(TAG_WM, "Button no longer pressed; dropping at "
-                                        + newX + "," + newY);
-                            }
-                            mMuteInput = true;
-                            endDrag = mDragDropController.mDragState
-                                    .notifyDropLocked(newX, newY);
-                        } else {
-                            // move the surface and tell the involved window(s) where we are
-                            mDragDropController.mDragState.notifyMoveLocked(newX, newY);
-                        }
-                    }
-                    break;
-
-                    case ACTION_UP: {
-                        if (DEBUG_DRAG) {
-                            Slog.d(TAG_WM, "Got UP on move channel; dropping at "
-                                    + newX + "," + newY);
-                        }
-                        mMuteInput = true;
-                        endDrag = mDragDropController.mDragState
-                                .notifyDropLocked(newX, newY);
-                    }
-                    break;
-
-                    case ACTION_CANCEL: {
-                        if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
-                        mMuteInput = true;
-                        endDrag = true;
-                    }
-                    break;
-                }
-
-                if (endDrag) {
-                    if (DEBUG_DRAG)
-                        Slog.d(TAG_WM, "Drag ended; tearing down state");
-                    // tell all the windows that the drag has ended
-                    // endDragLocked will post back to looper to dispose the receiver
-                    // since we still need the receiver for the last finishInputEvent.
-                    mDragDropController.mDragState.endDragLocked();
-                    mStylusButtonDownAtStart = false;
-                    mIsStartEvent = true;
-                }
-
-                handled = true;
+            if (!(event instanceof MotionEvent)
+                    || (event.getSource() & SOURCE_CLASS_POINTER) == 0
+                    || mMuteInput) {
+                return;
             }
+            final MotionEvent motionEvent = (MotionEvent) event;
+            final float newX = motionEvent.getRawX();
+            final float newY = motionEvent.getRawY();
+            final boolean isStylusButtonDown =
+                    (motionEvent.getButtonState() & BUTTON_STYLUS_PRIMARY) != 0;
+
+            if (mIsStartEvent) {
+                // First event and the button was down, check for the button being
+                // lifted in the future, if that happens we'll drop the item.
+                mStylusButtonDownAtStart = isStylusButtonDown;
+                mIsStartEvent = false;
+            }
+
+            switch (motionEvent.getAction()) {
+                case ACTION_DOWN:
+                    if (DEBUG_DRAG) Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
+                    return;
+                case ACTION_MOVE:
+                    if (mStylusButtonDownAtStart && !isStylusButtonDown) {
+                        if (DEBUG_DRAG) {
+                            Slog.d(TAG_WM, "Button no longer pressed; dropping at " + newX + ","
+                                    + newY);
+                        }
+                        mMuteInput = true;
+                    }
+                    break;
+                case ACTION_UP:
+                    if (DEBUG_DRAG) {
+                        Slog.d(TAG_WM, "Got UP on move channel; dropping at " + newX + "," + newY);
+                    }
+                    mMuteInput = true;
+                    break;
+                case ACTION_CANCEL:
+                    if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
+                    mMuteInput = true;
+                    break;
+                default:
+                    return;
+            }
+
+            mDragDropController.handleMotionEvent(!mMuteInput /* keepHandling */, newX, newY);
+            handled = true;
         } catch (Exception e) {
             Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
         } finally {
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 861fb44..e81d366 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -105,6 +105,11 @@
     WindowState mTargetWindow;
     ArrayList<WindowState> mNotifiedWindows;
     boolean mDragInProgress;
+    /**
+     * Whether if animation is completed. Needs to be volatile to update from the animation thread
+     * without having a WM lock.
+     */
+    volatile boolean mAnimationCompleted = false;
     DisplayContent mDisplayContent;
 
     @Nullable private ValueAnimator mAnimator;
@@ -122,21 +127,79 @@
         mNotifiedWindows = new ArrayList<WindowState>();
     }
 
-    void reset() {
-        if (mAnimator != null) {
+    /**
+     * After calling this, DragDropController#onDragStateClosedLocked is invoked, which causes
+     * DragDropController#mDragState becomes null.
+     */
+    void closeLocked() {
+        // Unregister the input interceptor.
+        if (mInputInterceptor != null) {
+            if (DEBUG_DRAG)
+                Slog.d(TAG_WM, "unregistering drag input channel");
+
+            // Input channel should be disposed on the thread where the input is being handled.
+            mDragDropController.sendHandlerMessage(
+                    MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor);
+            mInputInterceptor = null;
+            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+        }
+
+        // Send drag end broadcast if drag start has been sent.
+        if (mDragInProgress) {
+            final int myPid = Process.myPid();
+
+            if (DEBUG_DRAG) {
+                Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
+            }
+            for (WindowState ws : mNotifiedWindows) {
+                float x = 0;
+                float y = 0;
+                if (!mDragResult && (ws.mSession.mPid == mPid)) {
+                    // Report unconsumed drop location back to the app that started the drag.
+                    x = mCurrentX;
+                    y = mCurrentY;
+                }
+                DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+                        x, y, null, null, null, null, mDragResult);
+                try {
+                    ws.mClient.dispatchDragEvent(evt);
+                } catch (RemoteException e) {
+                    Slog.w(TAG_WM, "Unable to drag-end window " + ws);
+                }
+                // if the current window is in the same process,
+                // the dispatch has already recycled the event
+                if (myPid != ws.mSession.mPid) {
+                    evt.recycle();
+                }
+            }
+            mNotifiedWindows.clear();
+            mDragInProgress = false;
+        }
+
+        // Take the cursor back if it has been changed.
+        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+            mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
+            mTouchSource = 0;
+        }
+
+        // Clear the internal variables.
+        if (mSurfaceControl != null) {
+            mSurfaceControl.destroy();
+            mSurfaceControl = null;
+        }
+        if (mAnimator != null && !mAnimationCompleted) {
             Slog.wtf(TAG_WM,
                     "Unexpectedly destroying mSurfaceControl while animation is running");
         }
-        if (mSurfaceControl != null) {
-            mSurfaceControl.destroy();
-        }
-        mSurfaceControl = null;
         mFlags = 0;
         mLocalWin = null;
         mToken = null;
         mData = null;
         mThumbOffsetX = mThumbOffsetY = 0;
         mNotifiedWindows = null;
+
+        // Notifies the controller that the drag state is closed.
+        mDragDropController.onDragStateClosedLocked(this);
     }
 
     class InputInterceptor {
@@ -151,7 +214,7 @@
             mClientChannel = channels[1];
             mService.mInputManager.registerInputChannel(mServerChannel, null);
             mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
-                    mService.mH.getLooper(), mDragDropController, mService);
+                    mService.mH.getLooper(), mDragDropController);
 
             mDragApplicationHandle = new InputApplicationHandle(null);
             mDragApplicationHandle.name = "drag";
@@ -235,19 +298,6 @@
         }
     }
 
-    void unregister() {
-        if (DEBUG_DRAG) Slog.d(TAG_WM, "unregistering drag input channel");
-        if (mInputInterceptor == null) {
-            Slog.e(TAG_WM, "Unregister of nonexistent drag input channel");
-        } else {
-            // Input channel should be disposed on the thread where the input is being handled.
-            mDragDropController.sendHandlerMessage(
-                    MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor);
-            mInputInterceptor = null;
-            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
-        }
-    }
-
     int getDragLayerLocked() {
         return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_DRAG)
                 * WindowManagerService.TYPE_LAYER_MULTIPLIER
@@ -366,46 +416,15 @@
         return false;
     }
 
-    private void broadcastDragEndedLocked() {
-        final int myPid = Process.myPid();
-
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
-        }
-        for (WindowState ws : mNotifiedWindows) {
-            float x = 0;
-            float y = 0;
-            if (!mDragResult && (ws.mSession.mPid == mPid)) {
-                // Report unconsumed drop location back to the app that started the drag.
-                x = mCurrentX;
-                y = mCurrentY;
-            }
-            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
-                    x, y, null, null, null, null, mDragResult);
-            try {
-                ws.mClient.dispatchDragEvent(evt);
-            } catch (RemoteException e) {
-                Slog.w(TAG_WM, "Unable to drag-end window " + ws);
-            }
-            // if the current window is in the same process,
-            // the dispatch has already recycled the event
-            if (myPid != ws.mSession.mPid) {
-                evt.recycle();
-            }
-        }
-        mNotifiedWindows.clear();
-        mDragInProgress = false;
-    }
-
     void endDragLocked() {
         if (mAnimator != null) {
             return;
         }
         if (!mDragResult) {
             mAnimator = createReturnAnimationLocked();
-            return;  // Will call cleanUpDragLw when the animation is done.
+            return;  // Will call closeLocked() when the animation is done.
         }
-        cleanUpDragLocked();
+        closeLocked();
     }
 
     void cancelDragLocked() {
@@ -414,33 +433,15 @@
         }
         if (!mDragInProgress) {
             // This can happen if an app invokes Session#cancelDragAndDrop before
-            // Session#performDrag. Reset the drag state:
-            // 1. without sending the end broadcast because the start broadcast has not been sent,
-            // and
-            // 2. without playing the cancel animation because H.DRAG_START_TIMEOUT may be sent to
-            //    WindowManagerService, which will cause DragState#reset() while playing the
-            //    cancel animation.
-            reset();
-            mDragDropController.mDragState = null;
+            // Session#performDrag. Reset the drag state without playing the cancel animation
+            // because H.DRAG_START_TIMEOUT may be sent to WindowManagerService, which will cause
+            // DragState#reset() while playing the cancel animation.
+            closeLocked();
             return;
         }
         mAnimator = createCancelAnimationLocked();
     }
 
-    private void cleanUpDragLocked() {
-        broadcastDragEndedLocked();
-        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
-            mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
-        }
-
-        // stop intercepting input
-        unregister();
-
-        // free our resources and drop all the object references
-        reset();
-        mDragDropController.mDragState = null;
-    }
-
     void notifyMoveLocked(float x, float y) {
         if (mAnimator != null) {
             return;
@@ -507,34 +508,33 @@
         mTargetWindow = touchedWin;
     }
 
-    // Find the drop target and tell it about the data.  Returns 'true' if we can immediately
-    // dispatch the global drag-ended message, 'false' if we need to wait for a
-    // result from the recipient.
-    boolean notifyDropLocked(float x, float y) {
+    /**
+     * Finds the drop target and tells it about the data. If the drop event is not sent to the
+     * target, invokes {@code endDragLocked} immediately.
+     */
+    void notifyDropLocked(float x, float y) {
         if (mAnimator != null) {
-            return false;
+            return;
         }
         mCurrentX = x;
         mCurrentY = y;
 
-        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
+        final WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
 
         if (!isWindowNotified(touchedWin)) {
             // "drop" outside a valid window -- no recipient to apply a
             // timeout to, and we can send the drag-ended message immediately.
             mDragResult = false;
-            return true;
+            endDragLocked();
+            return;
         }
 
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "sending DROP to " + touchedWin);
-        }
+        if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
 
         final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
 
-        DragAndDropPermissionsHandler dragAndDropPermissions = null;
-        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
-                (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
+        final DragAndDropPermissionsHandler dragAndDropPermissions;
+        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
             dragAndDropPermissions = new DragAndDropPermissionsHandler(
                     mData,
                     mUid,
@@ -542,13 +542,15 @@
                     mFlags & DRAG_FLAGS_URI_PERMISSIONS,
                     mSourceUserId,
                     targetUserId);
+        } else {
+            dragAndDropPermissions = null;
         }
         if (mSourceUserId != targetUserId){
             mData.fixUris(mSourceUserId);
         }
         final int myPid = Process.myPid();
         final IBinder token = touchedWin.mClient.asBinder();
-        DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
+        final DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
                 null, null, mData, dragAndDropPermissions, false);
         try {
             touchedWin.mClient.dispatchDragEvent(evt);
@@ -557,23 +559,13 @@
             mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, token);
         } catch (RemoteException e) {
             Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
-            return true;
+            endDragLocked();
         } finally {
             if (myPid != touchedWin.mSession.mPid) {
                 evt.recycle();
             }
         }
         mToken = token;
-        return false;
-    }
-
-    void onAnimationEndLocked() {
-        if (mAnimator == null) {
-            Slog.wtf(TAG_WM, "Unexpected null mAnimator");
-            return;
-        }
-        mAnimator = null;
-        cleanUpDragLocked();
     }
 
     private static DragEvent obtainDragEvent(WindowState win, int action,
@@ -677,6 +669,7 @@
 
         @Override
         public void onAnimationEnd(Animator animator) {
+            mAnimationCompleted = true;
             // Updating mDragState requires the WM lock so continues it on the out of
             // AnimationThread.
             mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 40eab45..a766097 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -381,7 +381,7 @@
                 Log.d(TAG_WM, "Inserting drag window");
             }
             final InputWindowHandle dragWindowHandle =
-                    mService.mDragDropController.mDragState.getInputWindowHandle();
+                    mService.mDragDropController.getInputWindowHandleLocked();
             if (dragWindowHandle != null) {
                 addInputWindowHandle(dragWindowHandle);
             } else {
@@ -698,7 +698,7 @@
             // If there's a drag in progress and 'child' is a potential drop target,
             // make sure it's been told about the drag
             if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
-                mService.mDragDropController.mDragState.sendDragStartedIfNeededLocked(w);
+                mService.mDragDropController.sendDragStartedIfNeededLocked(w);
             }
 
             addInputWindowHandle(
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b28bb71..f313f31 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1320,7 +1320,10 @@
                 return WindowManagerGlobal.ADD_INVALID_DISPLAY;
             }
 
-            mPolicy.adjustWindowParamsLw(win.mAttrs);
+            final boolean hasStatusBarServicePermission =
+                    mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
+                            == PackageManager.PERMISSION_GRANTED;
+            mPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission);
             win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
 
             res = mPolicy.prepareAddWindowLw(win, attrs);
@@ -1846,8 +1849,11 @@
             MergedConfiguration mergedConfiguration, Surface outSurface) {
         int result = 0;
         boolean configChanged;
-        boolean hasStatusBarPermission =
-                mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+        final boolean hasStatusBarPermission =
+                mContext.checkCallingOrSelfPermission(permission.STATUS_BAR)
+                        == PackageManager.PERMISSION_GRANTED;
+        final boolean hasStatusBarServicePermission =
+                mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
                         == PackageManager.PERMISSION_GRANTED;
 
         long origId = Binder.clearCallingIdentity();
@@ -1867,7 +1873,7 @@
             int attrChanges = 0;
             int flagChanges = 0;
             if (attrs != null) {
-                mPolicy.adjustWindowParamsLw(attrs);
+                mPolicy.adjustWindowParamsLw(win, attrs, hasStatusBarServicePermission);
                 // if they don't have the permission, mask out the status bar bits
                 if (seq == win.mSeq) {
                     int systemUiVisibility = attrs.systemUiVisibility
@@ -7081,7 +7087,7 @@
         }
 
         synchronized (mWindowMap) {
-            if (mDragDropController.mDragState != null) {
+            if (mDragDropController.dragDropActiveLocked()) {
                 // Drag cursor overrides the app cursor.
                 return;
             }
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 4729d06..f1e76ab 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -55,6 +55,8 @@
     <uses-permission android:name="android.permission.DEVICE_POWER" />
     <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
     <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
 
     <!-- Uses API introduced in O (26) -->
     <uses-sdk android:minSdkVersion="1"
@@ -132,6 +134,8 @@
         <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity2" />
         <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity3" />
 
+        <activity android:name="com.android.server.wm.ScreenDecorWindowTests$TestActivity" />
+
         <activity android:name="com.android.server.pm.ShortcutTestActivity"
                  android:enabled="true" android:exported="true" />
 
diff --git a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
index daaad7a8..106f9e8 100644
--- a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
@@ -36,12 +36,14 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
+import org.mockito.invocation.InvocationOnMock;
 
 
 public class BatteryServiceTest extends AndroidTestCase {
 
     @Mock IServiceManager mMockedManager;
     @Mock IHealth mMockedHal;
+    @Mock IHealth mMockedHal2;
 
     @Mock BatteryService.HealthServiceWrapper.Callback mCallback;
     @Mock BatteryService.HealthServiceWrapper.IServiceManagerSupplier mManagerSupplier;
@@ -56,6 +58,12 @@
         MockitoAnnotations.initMocks(this);
     }
 
+    @Override
+    public void tearDown() {
+        if (mWrapper != null)
+            mWrapper.getHandlerThread().quitSafely();
+    }
+
     public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) {
         return new ArgumentMatcher<T>() {
             @Override public boolean matches(T e) {
@@ -70,42 +78,64 @@
     private void initForInstances(String... instanceNamesArr) throws Exception {
         final Collection<String> instanceNames = Arrays.asList(instanceNamesArr);
         doAnswer((invocation) -> {
-                Slog.e("BatteryServiceTest", "health: onRegistration " + invocation.getArguments()[2]);
-                ((IServiceNotification)invocation.getArguments()[2]).onRegistration(
-                        IHealth.kInterfaceName,
-                        (String)invocation.getArguments()[1],
-                        true /* preexisting */);
+                // technically, preexisting is ignored by
+                // BatteryService.HealthServiceWrapper.Notification, but still call it correctly.
+                sendNotification(invocation, true);
+                sendNotification(invocation, true);
+                sendNotification(invocation, false);
                 return null;
             }).when(mMockedManager).registerForNotifications(
                 eq(IHealth.kInterfaceName),
                 argThat(isOneOf(instanceNames)),
                 any(IServiceNotification.class));
 
-        doReturn(mMockedHal).when(mMockedManager)
-            .get(eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames)));
-
-        doReturn(IServiceManager.Transport.HWBINDER).when(mMockedManager)
-            .getTransport(eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames)));
-
         doReturn(mMockedManager).when(mManagerSupplier).get();
-        doReturn(mMockedHal).when(mHealthServiceSupplier)
-            .get(argThat(isOneOf(instanceNames)));
+        doReturn(mMockedHal)        // init calls this
+            .doReturn(mMockedHal)   // notification 1
+            .doReturn(mMockedHal)   // notification 2
+            .doReturn(mMockedHal2)  // notification 3
+            .doThrow(new RuntimeException("Should not call getService for more than 4 times"))
+            .when(mHealthServiceSupplier).get(argThat(isOneOf(instanceNames)));
 
         mWrapper = new BatteryService.HealthServiceWrapper();
     }
 
+    private void waitHandlerThreadFinish() throws Exception {
+        for (int i = 0; i < 5; i++) {
+            if (!mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks()) {
+                return;
+            }
+            Thread.sleep(300);
+        }
+        assertFalse(mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks());
+    }
+
+    private static void sendNotification(InvocationOnMock invocation, boolean preexisting)
+            throws Exception {
+        ((IServiceNotification)invocation.getArguments()[2]).onRegistration(
+                IHealth.kInterfaceName,
+                (String)invocation.getArguments()[1],
+                preexisting);
+    }
+
     @SmallTest
     public void testWrapPreferVendor() throws Exception {
         initForInstances(VENDOR, HEALTHD);
         mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
-        verify(mCallback).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
+        waitHandlerThreadFinish();
+        verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
+        verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
+        verify(mCallback, times(1)).onRegistration(same(mMockedHal), same(mMockedHal2), eq(VENDOR));
     }
 
     @SmallTest
     public void testUseHealthd() throws Exception {
         initForInstances(HEALTHD);
         mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
-        verify(mCallback).onRegistration(same(null), same(mMockedHal), eq(HEALTHD));
+        waitHandlerThreadFinish();
+        verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(HEALTHD));
+        verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
+        verify(mCallback, times(1)).onRegistration(same(mMockedHal), same(mMockedHal2), eq(HEALTHD));
     }
 
     @SmallTest
diff --git a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
new file mode 100644
index 0000000..9c80544
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
@@ -0,0 +1,281 @@
+/*
+ * 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.server.wm;
+
+import static android.graphics.Color.BLUE;
+import static android.graphics.Color.RED;
+import static android.view.Gravity.BOTTOM;
+import static android.view.Gravity.LEFT;
+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;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static org.junit.Assert.assertEquals;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for the {@link android.view.WindowManager.LayoutParams#PRIVATE_FLAG_IS_SCREEN_DECOR} flag.
+ *
+ * Build/Install/Run:
+ *  bit 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.
+// TODO: Test secondary display.
+@SmallTest
+// TODO(b/68957554)
+//@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ScreenDecorWindowTests {
+
+    private final Context mContext = InstrumentationRegistry.getTargetContext();
+    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+    private WindowManager mWm;
+    private ArrayList<View> mWindows = new ArrayList<>();
+
+    @Rule
+    public ActivityTestRule<TestActivity> mTestActivityRule = new ActivityTestRule<>(
+            TestActivity.class, false /* initialTouchMode */, false /* launchActivity */);
+    private Activity mTestActivity;
+
+    private int mDecorThickness;
+    private int mHalfDecorThickness;
+
+    @Before
+    public void setUp() {
+        mWm = mContext.getSystemService(WindowManager.class);
+        final Point size = new Point();
+        mWm.getDefaultDisplay().getSize(size);
+        mDecorThickness = Math.min(size.x, size.y) / 3;
+        mHalfDecorThickness = mDecorThickness / 2;
+        mTestActivity = launchActivity(mTestActivityRule);
+    }
+
+    @After
+    public void tearDown() {
+        while (!mWindows.isEmpty()) {
+            removeWindow(mWindows.get(0));
+        }
+        finishActivity(mTestActivityRule);
+    }
+
+    @Test
+    public void testScreenSides() throws Exception {
+        // Decor on top
+        final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
+        WindowInsets insets = getInsets(mTestActivity);
+        assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness);
+
+        // Decor at the bottom
+        updateWindow(decorWindow, BOTTOM, MATCH_PARENT, mDecorThickness, 0, 0);
+        insets = getInsets(mTestActivity);
+        assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), mDecorThickness);
+
+        // Decor to the left
+        updateWindow(decorWindow, LEFT, mDecorThickness, MATCH_PARENT, 0, 0);
+        insets = getInsets(mTestActivity);
+        assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), mDecorThickness);
+
+        // Decor to the right
+        updateWindow(decorWindow, RIGHT, mDecorThickness, MATCH_PARENT, 0, 0);
+        insets = getInsets(mTestActivity);
+        assertGreaterOrEqual(insets.getSystemWindowInsetRight(), 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);
+        createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
+        insets = getInsets(mTestActivity);
+        assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness);
+
+        // And one at the bottom.
+        createDecorWindow(BOTTOM, MATCH_PARENT, mHalfDecorThickness);
+        insets = getInsets(mTestActivity);
+        assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness);
+        assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), mHalfDecorThickness);
+    }
+
+    @Test
+    public void testFlagChange() throws Exception {
+        WindowInsets initialInsets = getInsets(mTestActivity);
+
+        final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
+        WindowInsets insets = getInsets(mTestActivity);
+        assertEquals(mDecorThickness, insets.getSystemWindowInsetTop());
+
+        updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness,
+                0, PRIVATE_FLAG_IS_SCREEN_DECOR);
+        insets = getInsets(mTestActivity);
+        assertEquals(initialInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop());
+
+        updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness,
+                PRIVATE_FLAG_IS_SCREEN_DECOR, PRIVATE_FLAG_IS_SCREEN_DECOR);
+        insets = getInsets(mTestActivity);
+        assertEquals(mDecorThickness, insets.getSystemWindowInsetTop());
+    }
+
+    @Test
+    public void testRemoval() throws Exception {
+        WindowInsets initialInsets = getInsets(mTestActivity);
+
+        final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
+        WindowInsets insets = getInsets(mTestActivity);
+        assertGreaterOrEqual(insets.getSystemWindowInsetTop(), 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);
+    }
+
+    private View createDecorWindow(int gravity, int width, int height) {
+        return createWindow("decorWindow", gravity, width, height, RED,
+                FLAG_LAYOUT_IN_SCREEN, PRIVATE_FLAG_IS_SCREEN_DECOR);
+    }
+
+    private View createWindow(String name, int gravity, int width, int height, int color, int flags,
+            int privateFlags) {
+
+        final View[] viewHolder = new View[1];
+        final int finalFlag = flags
+                | FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE;
+
+        // Needs to run on the UI thread.
+        Handler.getMain().runWithScissors(() -> {
+            final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                    width, height, TYPE_APPLICATION_OVERLAY, finalFlag, PixelFormat.OPAQUE);
+            lp.gravity = gravity;
+            lp.privateFlags |= privateFlags;
+
+            final TextView view = new TextView(mContext);
+            view.setText("ScreenDecorWindowTests - " + name);
+            view.setBackgroundColor(color);
+            mWm.addView(view, lp);
+            mWindows.add(view);
+            viewHolder[0] = view;
+        }, 0);
+
+        waitForIdle();
+        return viewHolder[0];
+    }
+
+    private void updateWindow(View v, int gravity, int width, int height,
+            int privateFlags, int privateFlagsMask) {
+        // Needs to run on the UI thread.
+        Handler.getMain().runWithScissors(() -> {
+            final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) v.getLayoutParams();
+            lp.gravity = gravity;
+            lp.width = width;
+            lp.height = height;
+            setPrivateFlags(lp, privateFlags, privateFlagsMask);
+
+            mWm.updateViewLayout(v, lp);
+        }, 0);
+
+        waitForIdle();
+    }
+
+    private void removeWindow(View v) {
+        Handler.getMain().runWithScissors(() -> mWm.removeView(v), 0);
+        mWindows.remove(v);
+        waitForIdle();
+    }
+
+    private WindowInsets getInsets(View v) {
+        return new WindowInsets(v.getRootWindowInsets());
+    }
+
+    private WindowInsets getInsets(Activity a) {
+        return new WindowInsets(a.getWindow().getDecorView().getRootWindowInsets());
+    }
+
+    /**
+     * Set the flags of the window, as per the
+     * {@link WindowManager.LayoutParams WindowManager.LayoutParams}
+     * flags.
+     *
+     * @param flags The new window flags (see WindowManager.LayoutParams).
+     * @param mask Which of the window flag bits to modify.
+     */
+    public void setPrivateFlags(WindowManager.LayoutParams lp, int flags, int mask) {
+        lp.flags = (lp.flags & ~mask) | (flags & mask);
+    }
+
+    /** 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 Activity launchActivity(ActivityTestRule activityRule) {
+        final Activity activity = activityRule.launchActivity(null);
+        waitForIdle();
+        return activity;
+    }
+
+    private void finishActivity(ActivityTestRule activityRule) {
+        final Activity activity = activityRule.getActivity();
+        if (activity != null) {
+            activity.finish();
+        }
+    }
+
+    private void waitForIdle() {
+        mInstrumentation.waitForIdleSync();
+    }
+
+    public static class TestActivity extends Activity {
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 9f57f49..33d4721 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -148,8 +148,8 @@
     }
 
     @Override
-    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
-
+    public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+            boolean hasStatusBarServicePermission) {
     }
 
     @Override
@@ -290,7 +290,7 @@
     }
 
     @Override
-    public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
+    public void beginLayoutLw(int displayId, int displayWidth, int displayHeight,
             int displayRotation, int uiMode) {
 
     }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 3fc2208..061e55a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1643,6 +1643,13 @@
     public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY =
             "feature_access_codes_string_array";
 
+    /**
+     * Determines if the carrier wants to identify high definition calls in the call log.
+     * @hide
+     */
+    public static final String KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL =
+            "identify_high_definition_calls_in_call_log_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1920,6 +1927,7 @@
         sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
         sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
+        sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
     }
 
     /**
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxy.java b/telephony/java/android/telephony/ims/ImsServiceProxy.java
index 038e295..31d3db4 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxy.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxy.java
@@ -305,19 +305,6 @@
         mStatusCallback = c;
     }
 
-    /**
-     * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
-     * method returns false, it doesn't mean that the Binder connection is not available (use
-     * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
-     * at this time.
-     *
-     * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
-     * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_NOT_AVAILABLE}.
-     */
-    public boolean isBinderReady() {
-        return isBinderAlive() && getFeatureStatus() == ImsFeature.STATE_READY;
-    }
-
     @Override
     public boolean isBinderAlive() {
         return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java b/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
index bbd5f02..7ec9229 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
@@ -171,6 +171,19 @@
         return mBinder != null && mBinder.isBinderAlive();
     }
 
+    /**
+     * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
+     * method returns false, it doesn't mean that the Binder connection is not available (use
+     * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
+     * at this time.
+     *
+     * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
+     * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_NOT_AVAILABLE}.
+     */
+    public boolean isBinderReady() {
+        return isBinderAlive() && getFeatureStatus() == ImsFeature.STATE_READY;
+    }
+
     private IImsService getServiceInterface(IBinder b) {
         return IImsService.Stub.asInterface(b);
     }
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 29a95e6..3367aba 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -50,6 +50,9 @@
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
+# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
+ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
+
 # Generate the stub source files for android.test.runner.stubs
 # ============================================================
 include $(CLEAR_VARS)
@@ -149,6 +152,8 @@
 	@echo Copying removed.txt
 	$(hide) $(ACP) $(ANDROID_TEST_RUNNER_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_RUNNER_REMOVED_API_FILE)
 
+endif  # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
+
 # Build the android.test.mock library
 # ===================================
 include $(CLEAR_VARS)
@@ -161,6 +166,9 @@
 
 include $(BUILD_JAVA_LIBRARY)
 
+# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
+ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
+
 # Generate the stub source files for android.test.mock.stubs
 # ==========================================================
 include $(CLEAR_VARS)
@@ -257,3 +265,5 @@
 
 # additionally, build unit tests in a separate .apk
 include $(call all-makefiles-under,$(LOCAL_PATH))
+
+endif  # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index f2de7db..c6b4a54 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -92,6 +92,15 @@
             </intent-filter>
         </activity>
         <activity
+            android:name=".TrivialAnimationActivityWideGamut"
+            android:label="General/Trivial Animation (Wide Gamut)"
+            android:colorMode="wideColorGamut">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.uibench.TEST" />
+            </intent-filter>
+        </activity>
+        <activity
             android:name=".TrivialListActivity"
             android:label="General/Trivial ListView" >
             <intent-filter>
diff --git a/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java b/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java
new file mode 100644
index 0000000..c492753
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java
@@ -0,0 +1,18 @@
+/*
+ * 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.test.uibench;
+
+public class TrivialAnimationActivityWideGamut extends TrivialAnimationActivity { }
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index ad6ebf9..a65bb24 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -198,37 +198,33 @@
 
     @Test
     public void testDefaultNetworkEventSerialization() {
-        DefaultNetworkEvent ev = new DefaultNetworkEvent();
+        DefaultNetworkEvent ev = new DefaultNetworkEvent(1001);
         ev.netId = 102;
-        ev.prevNetId = 101;
-        ev.transportTypes = new int[]{1, 2, 3};
-        ev.prevIPv4 = true;
-        ev.prevIPv6 = true;
+        ev.transports = 2;
+        ev.previousTransports = 4;
+        ev.ipv4 = true;
+        ev.initialScore = 20;
+        ev.finalScore = 60;
+        ev.durationMs = 54;
+        ev.validatedMs = 27;
 
         String want = String.join("\n",
                 "dropped_events: 0",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 4",
                 "  network_id: 102",
                 "  time_ms: 0",
-                "  transports: 0",
+                "  transports: 2",
                 "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 102",
-                "    >",
+                "    default_network_duration_ms: 54",
+                "    final_score: 60",
+                "    initial_score: 20",
+                "    ip_support: 1",
                 "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 101",
-                "    >",
-                "    previous_network_ip_support: 3",
-                "    transport_types: 1",
-                "    transport_types: 2",
-                "    transport_types: 3",
+                "    previous_default_network_link_layer: 1",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 27",
                 "  >",
                 ">",
                 "version: 2\n");
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 6c1decc..b48ff8d 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -188,119 +188,99 @@
             {makeNai(102, 50, true, true, cell), makeNai(103, 20, true, false, wifi)},
         };
 
+        long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs;
+        long durationMs = 1001;
         for (NetworkAgentInfo[] pair : defaultNetworks) {
-            mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(pair[1], pair[0]);
+            timeMs += durationMs;
+            durationMs += durationMs;
+            mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs, pair[1], pair[0]);
         }
 
         String want = String.join("\n",
                 "dropped_events: 0",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
-                "  network_id: 100",
-                "  time_ms: 0",
-                "  transports: 0",
-                "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 100",
-                "    >",
-                "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 0",
-                "    >",
-                "    previous_network_ip_support: 0",
-                "    transport_types: 0",
-                "  >",
-                ">",
-                "events <",
-                "  if_name: \"\"",
-                "  link_layer: 0",
-                "  network_id: 101",
-                "  time_ms: 0",
-                "  transports: 0",
-                "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 101",
-                "    >",
-                "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 100",
-                "    >",
-                "    previous_network_ip_support: 3",
-                "    transport_types: 1",
-                "  >",
-                ">",
-                "events <",
-                "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 5",
                 "  network_id: 0",
                 "  time_ms: 0",
                 "  transports: 0",
                 "  default_network_event <",
-                "    default_network_duration_ms: 0",
+                "    default_network_duration_ms: 1001",
                 "    final_score: 0",
                 "    initial_score: 0",
                 "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 0",
-                "    >",
                 "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 101",
-                "    >",
-                "    previous_network_ip_support: 1",
+                "    previous_default_network_link_layer: 0",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 0",
                 "  >",
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 2",
+                "  network_id: 100",
+                "  time_ms: 0",
+                "  transports: 1",
+                "  default_network_event <",
+                "    default_network_duration_ms: 2002",
+                "    final_score: 50",
+                "    initial_score: 10",
+                "    ip_support: 3",
+                "    no_default_network_duration_ms: 0",
+                "    previous_default_network_link_layer: 0",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 2002",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 101",
+                "  time_ms: 0",
+                "  transports: 2",
+                "  default_network_event <",
+                "    default_network_duration_ms: 4004",
+                "    final_score: 60",
+                "    initial_score: 20",
+                "    ip_support: 1",
+                "    no_default_network_duration_ms: 0",
+                "    previous_default_network_link_layer: 2",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 4004",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 5",
+                "  network_id: 0",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  default_network_event <",
+                "    default_network_duration_ms: 8008",
+                "    final_score: 0",
+                "    initial_score: 0",
+                "    ip_support: 0",
+                "    no_default_network_duration_ms: 0",
+                "    previous_default_network_link_layer: 4",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 0",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 2",
                 "  network_id: 102",
                 "  time_ms: 0",
-                "  transports: 0",
+                "  transports: 1",
                 "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 102",
-                "    >",
+                "    default_network_duration_ms: 16016",
+                "    final_score: 50",
+                "    initial_score: 10",
+                "    ip_support: 3",
                 "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 0",
-                "    >",
+                "    previous_default_network_link_layer: 4",
                 "    previous_network_ip_support: 0",
-                "    transport_types: 0",
-                "  >",
-                ">",
-                "events <",
-                "  if_name: \"\"",
-                "  link_layer: 0",
-                "  network_id: 103",
-                "  time_ms: 0",
-                "  transports: 0",
-                "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 103",
-                "    >",
-                "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 102",
-                "    >",
-                "    previous_network_ip_support: 3",
-                "    transport_types: 1",
+                "    validation_duration_ms: 16016",
                 "  >",
                 ">",
                 "version: 2\n");
@@ -379,12 +359,13 @@
         wakeupEvent("wlan0", 10008);
         wakeupEvent("rmnet0", 1000);
 
+        long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs;
         final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
         final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
         NetworkAgentInfo cellNai = makeNai(100, 50, false, true, cell);
         NetworkAgentInfo wifiNai = makeNai(101, 60, true, false, wifi);
-        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(cellNai, null);
-        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(wifiNai, cellNai);
+        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 200, cellNai, null);
+        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 300, wifiNai, cellNai);
 
         String want = String.join("\n",
                 "dropped_events: 0",
@@ -473,46 +454,36 @@
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
-                "  network_id: 100",
+                "  link_layer: 5",
+                "  network_id: 0",
                 "  time_ms: 0",
                 "  transports: 0",
                 "  default_network_event <",
-                "    default_network_duration_ms: 0",
+                "    default_network_duration_ms: 200",
                 "    final_score: 0",
                 "    initial_score: 0",
                 "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 100",
-                "    >",
                 "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 0",
-                "    >",
+                "    previous_default_network_link_layer: 0",
                 "    previous_network_ip_support: 0",
-                "    transport_types: 0",
+                "    validation_duration_ms: 0",
                 "  >",
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
-                "  network_id: 101",
+                "  link_layer: 2",
+                "  network_id: 100",
                 "  time_ms: 0",
-                "  transports: 0",
+                "  transports: 1",
                 "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 101",
-                "    >",
+                "    default_network_duration_ms: 100",
+                "    final_score: 50",
+                "    initial_score: 50",
+                "    ip_support: 2",
                 "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 100",
-                "    >",
-                "    previous_network_ip_support: 2",
-                "    transport_types: 1",
+                "    previous_default_network_link_layer: 0",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 100",
                 "  >",
                 ">",
                 "events <",
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 6723601..83194d9 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -82,9 +82,8 @@
     public void testWakeupEventLogging() throws Exception {
         final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH;
 
-        // Assert no events
-        String[] events1 = listNetdEvent();
-        assertEquals(new String[]{""}, events1);
+        // Baseline without any event
+        String[] baseline = listNetdEvent();
 
         long now = System.currentTimeMillis();
         String prefix = "iface:wlan0";
@@ -93,7 +92,7 @@
             mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
         }
 
-        String[] events2 = listNetdEvent();
+        String[] events2 = remove(listNetdEvent(), baseline);
         int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line
         assertEquals(expectedLength2, events2.length);
         assertContains(events2[0], "WakeupStats");
@@ -111,7 +110,7 @@
             mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, ts);
         }
 
-        String[] events3 = listNetdEvent();
+        String[] events3 = remove(listNetdEvent(), baseline);
         int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line
         assertEquals(expectedLength3, events3.length);
         assertContains(events2[0], "WakeupStats");
@@ -126,7 +125,7 @@
         uid = 45678;
         mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
 
-        String[] events4 = listNetdEvent();
+        String[] events4 = remove(listNetdEvent(), baseline);
         String lastEvent = events4[events4.length - 1];
         assertContains(lastEvent, "WakeupEvent");
         assertContains(lastEvent, "wlan0");
@@ -423,7 +422,7 @@
         final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
         new Thread(() -> {
             while (System.currentTimeMillis() < stop) {
-                mNetdEventListenerService.dump(pw);
+                mNetdEventListenerService.list(pw);
             }
         }).start();
     }
@@ -461,4 +460,16 @@
     static void assertContains(String got, String want) {
         assertTrue(got + " did not contain \"" + want + "\"", got.contains(want));
     }
+
+    static <T> T[] remove(T[] array, T[] filtered) {
+        List<T> c = Arrays.asList(filtered);
+        int next = 0;
+        for (int i = 0; i < array.length; i++) {
+            if (c.contains(array[i])) {
+                continue;
+            }
+            array[next++] = array[i];
+        }
+        return Arrays.copyOf(array, next);
+    }
 }
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 058504d..ae67f61 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -16,6 +16,7 @@
 
 toolSources = [
     "cmd/Compile.cpp",
+    "cmd/Convert.cpp",
     "cmd/Diff.cpp",
     "cmd/Dump.cpp",
     "cmd/Link.cpp",
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index ae32ee9..921d853 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -21,43 +21,139 @@
 #include "format/Archive.h"
 #include "format/binary/TableFlattener.h"
 #include "format/binary/XmlFlattener.h"
+#include "format/proto/ProtoDeserialize.h"
 #include "io/BigBufferStream.h"
 #include "io/Util.h"
 #include "xml/XmlDom.h"
 
+using ::aapt::io::IFile;
+using ::aapt::io::IFileCollection;
+using ::aapt::xml::XmlResource;
+using ::android::StringPiece;
+using ::std::unique_ptr;
+
 namespace aapt {
 
-using xml::XmlResource;
-
-std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
-                                                      const android::StringPiece& path) {
+std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, IDiagnostics* diag) {
   Source source(path);
   std::string error;
   std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
-  if (!apk) {
-    context->GetDiagnostics()->Error(DiagMessage(source) << error);
+  if (apk == nullptr) {
+    diag->Error(DiagMessage(path) << "failed opening zip: " << error);
     return {};
   }
 
-  io::IFile* file = apk->FindFile("resources.arsc");
-  if (!file) {
-    context->GetDiagnostics()->Error(DiagMessage(source) << "no resources.arsc found");
+  if (apk->FindFile("resources.arsc") != nullptr) {
+    return LoadBinaryApkFromFileCollection(source, std::move(apk), diag);
+  } else if (apk->FindFile("resources.pb") != nullptr) {
+    return LoadProtoApkFromFileCollection(source, std::move(apk), diag);
+  }
+  diag->Error(DiagMessage(path) << "no resource table found");
+  return {};
+}
+
+std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
+    const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
+  io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
+  if (table_file == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to find " << kProtoResourceTablePath);
     return {};
   }
 
-  std::unique_ptr<io::IData> data = file->OpenAsData();
-  if (!data) {
-    context->GetDiagnostics()->Error(DiagMessage(source) << "could not open resources.arsc");
+  std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
+  if (in == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
+    return {};
+  }
+
+  pb::ResourceTable pb_table;
+  io::ZeroCopyInputAdaptor adaptor(in.get());
+  if (!pb_table.ParseFromZeroCopyStream(&adaptor)) {
+    diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath);
+    return {};
+  }
+
+  std::string error;
+  std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+  if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) {
+    diag->Error(DiagMessage(source)
+                << "failed to deserialize " << kProtoResourceTablePath << ": " << error);
+    return {};
+  }
+
+  io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
+  if (manifest_file == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
+    return {};
+  }
+
+  std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
+  if (manifest_in == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
+    return {};
+  }
+
+  pb::XmlNode pb_node;
+  io::ZeroCopyInputAdaptor manifest_adaptor(manifest_in.get());
+  if (!pb_node.ParseFromZeroCopyStream(&manifest_adaptor)) {
+    diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath);
+    return {};
+  }
+
+  std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error);
+  if (manifest == nullptr) {
+    diag->Error(DiagMessage(source)
+                << "failed to deserialize proto " << kAndroidManifestPath << ": " << error);
+    return {};
+  }
+  return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
+                                      std::move(manifest));
+}
+
+std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
+    const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
+  io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
+  if (table_file == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to find " << kApkResourceTablePath);
+
+    return {};
+  }
+
+  std::unique_ptr<io::IData> data = table_file->OpenAsData();
+  if (data == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath);
     return {};
   }
 
   std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
-  BinaryResourceParser parser(context, table.get(), source, data->data(), data->size(), apk.get());
+  BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(),
+                              collection.get());
   if (!parser.Parse()) {
     return {};
   }
 
-  return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
+  io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
+  if (manifest_file == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
+    return {};
+  }
+
+  std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
+  if (manifest_data == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
+    return {};
+  }
+
+  std::string error;
+  std::unique_ptr<xml::XmlResource> manifest =
+      xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
+  if (manifest == nullptr) {
+    diag->Error(DiagMessage(source)
+                << "failed to parse binary " << kAndroidManifestPath << ": " << error);
+    return {};
+  }
+  return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
+                                      std::move(manifest));
 }
 
 bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
@@ -148,26 +244,4 @@
   return true;
 }
 
-std::unique_ptr<xml::XmlResource> LoadedApk::InflateManifest(IAaptContext* context) {
-  IDiagnostics* diag = context->GetDiagnostics();
-
-  io::IFile* manifest_file = GetFileCollection()->FindFile("AndroidManifest.xml");
-  if (manifest_file == nullptr) {
-    diag->Error(DiagMessage(source_) << "no AndroidManifest.xml found");
-    return {};
-  }
-
-  std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
-  if (manifest_data == nullptr) {
-    diag->Error(DiagMessage(manifest_file->GetSource()) << "could not open AndroidManifest.xml");
-    return {};
-  }
-
-  std::unique_ptr<xml::XmlResource> manifest =
-      xml::Inflate(manifest_data->data(), manifest_data->size(), diag, manifest_file->GetSource());
-  if (manifest == nullptr) {
-    diag->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
-  }
-  return manifest;
-}
 }  // namespace aapt
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index d2dd5cf..ef97de3 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -29,20 +29,41 @@
 
 namespace aapt {
 
+constexpr static const char kApkResourceTablePath[] = "resources.arsc";
+constexpr static const char kProtoResourceTablePath[] = "resources.pb";
+constexpr static const char kAndroidManifestPath[] = "AndroidManifest.xml";
+
 // Info about an APK loaded in memory.
 class LoadedApk {
  public:
-  LoadedApk(
-      const Source& source,
-      std::unique_ptr<io::IFileCollection> apk,
-      std::unique_ptr<ResourceTable> table)
-      : source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
-  virtual ~LoadedApk() = default;
+  // Loads both binary and proto APKs from disk.
+  static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path,
+                                                    IDiagnostics* diag);
+
+  // Loads a proto APK from the given file collection.
+  static std::unique_ptr<LoadedApk> LoadProtoApkFromFileCollection(
+      const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag);
+
+  // Loads a binary APK from the given file collection.
+  static std::unique_ptr<LoadedApk> LoadBinaryApkFromFileCollection(
+      const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag);
+
+  LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
+            std::unique_ptr<ResourceTable> table, std::unique_ptr<xml::XmlResource> manifest)
+      : source_(source),
+        apk_(std::move(apk)),
+        table_(std::move(table)),
+        manifest_(std::move(manifest)) {
+  }
 
   io::IFileCollection* GetFileCollection() {
     return apk_.get();
   }
 
+  const ResourceTable* GetResourceTable() const {
+    return table_.get();
+  }
+
   ResourceTable* GetResourceTable() {
     return table_.get();
   }
@@ -51,6 +72,10 @@
     return source_;
   }
 
+  const xml::XmlResource* GetManifest() const {
+    return manifest_.get();
+  }
+
   /**
    * Writes the APK on disk at the given path, while also removing the resource
    * files that are not referenced in the resource table.
@@ -71,11 +96,6 @@
                               const TableFlattenerOptions& options, FilterChain* filters,
                               IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
 
-  /** Inflates the AndroidManifest.xml file from the APK. */
-  std::unique_ptr<xml::XmlResource> InflateManifest(IAaptContext* context);
-
-  static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
-                                                    const android::StringPiece& path);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedApk);
@@ -83,6 +103,7 @@
   Source source_;
   std::unique_ptr<io::IFileCollection> apk_;
   std::unique_ptr<ResourceTable> table_;
+  std::unique_ptr<xml::XmlResource> manifest_;
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 36ab30c..808b29c 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -50,7 +50,7 @@
 }
 
 static void PrintUsage() {
-  std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|version] ..." << std::endl;
+  std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|convert|version] ..." << std::endl;
 }
 
 extern int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics);
@@ -58,6 +58,7 @@
 extern int Dump(const std::vector<StringPiece>& args);
 extern int Diff(const std::vector<StringPiece>& args);
 extern int Optimize(const std::vector<StringPiece>& args);
+extern int Convert(const std::vector<StringPiece>& args);
 
 static int ExecuteCommand(const StringPiece& command, const std::vector<StringPiece>& args,
                           IDiagnostics* diagnostics) {
@@ -71,6 +72,8 @@
     return Diff(args);
   } else if (command == "optimize") {
     return Optimize(args);
+  } else if (command == "convert") {
+    return Convert(args);
   } else if (command == "version") {
     PrintVersion();
     return 0;
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
new file mode 100644
index 0000000..89ae9e8
--- /dev/null
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -0,0 +1,228 @@
+/*
+ * 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.
+ */
+
+#include <vector>
+
+#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
+
+#include "Flags.h"
+#include "LoadedApk.h"
+#include "ValueVisitor.h"
+#include "cmd/Util.h"
+#include "format/binary/TableFlattener.h"
+#include "format/binary/XmlFlattener.h"
+#include "format/proto/ProtoDeserialize.h"
+#include "io/BigBufferStream.h"
+#include "io/Util.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+
+using ::android::StringPiece;
+using ::std::unique_ptr;
+using ::std::vector;
+
+namespace aapt {
+
+static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml,
+                       const std::string& entry_path, bool utf16, IArchiveWriter* writer) {
+  BigBuffer buffer(4096);
+  XmlFlattenerOptions options = {};
+  options.use_utf16 = utf16;
+  XmlFlattener flattener(&buffer, options);
+  if (!flattener.Consume(context, &xml)) {
+    return false;
+  }
+  io::BigBufferInputStream input_stream(&buffer);
+  return io::CopyInputStreamToArchive(context, &input_stream, entry_path, ArchiveEntry::kCompress,
+                                      writer);
+}
+
+bool ConvertProtoApkToBinaryApk(IAaptContext* context, unique_ptr<LoadedApk> apk,
+                                const TableFlattenerOptions& options, IArchiveWriter* writer) {
+  if (!FlattenXml(context, *apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
+    return false;
+  }
+
+  BigBuffer buffer(4096);
+  TableFlattener table_flattener(options, &buffer);
+  if (!table_flattener.Consume(context, apk->GetResourceTable())) {
+    return false;
+  }
+
+  io::BigBufferInputStream input_stream(&buffer);
+  if (!io::CopyInputStreamToArchive(context, &input_stream, kApkResourceTablePath,
+                                    ArchiveEntry::kAlign, writer)) {
+    return false;
+  }
+
+  for (const auto& package : apk->GetResourceTable()->packages) {
+    for (const auto& type : package->types) {
+      for (const auto& entry : type->entries) {
+        for (const auto& config_value : entry->values) {
+          const FileReference* file = ValueCast<FileReference>(config_value->value.get());
+          if (file != nullptr) {
+            if (file->file == nullptr) {
+              context->GetDiagnostics()->Warn(DiagMessage(apk->GetSource())
+                                              << "no file associated with " << *file);
+              return false;
+            }
+
+            if (file->type == ResourceFile::Type::kProtoXml) {
+              unique_ptr<io::InputStream> in = file->file->OpenInputStream();
+              if (in == nullptr) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "failed to open file " << *file->path);
+                return false;
+              }
+
+              pb::XmlNode pb_node;
+              io::ZeroCopyInputAdaptor adaptor(in.get());
+              if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "failed to parse proto XML " << *file->path);
+                return false;
+              }
+
+              std::string error;
+              unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
+              if (xml == nullptr) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "failed to deserialize proto XML "
+                                                 << *file->path << ": " << error);
+                return false;
+              }
+
+              if (!FlattenXml(context, *xml, *file->path, false /*utf16*/, writer)) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "failed to serialize XML " << *file->path);
+                return false;
+              }
+            } else {
+              if (!io::CopyFileToArchive(context, file->file, *file->path,
+                                         file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u,
+                                         writer)) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "failed to copy file " << *file->path);
+                return false;
+              }
+            }
+
+          } // file
+        } // config_value
+      } // entry
+    } // type
+  } // package
+  return true;
+}
+
+class Context : public IAaptContext {
+ public:
+  Context() : mangler_({}), symbols_(&mangler_) {
+  }
+
+  PackageType GetPackageType() override {
+    return PackageType::kApp;
+  }
+
+  SymbolTable* GetExternalSymbols() override {
+    return &symbols_;
+  }
+
+  IDiagnostics* GetDiagnostics() override {
+    return &diag_;
+  }
+
+  const std::string& GetCompilationPackage() override {
+    return package_;
+  }
+
+  uint8_t GetPackageId() override {
+    // Nothing should call this.
+    UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
+    return 0;
+  }
+
+  NameMangler* GetNameMangler() override {
+    UNIMPLEMENTED(FATAL);
+    return nullptr;
+  }
+
+  bool IsVerbose() override {
+    return verbose_;
+  }
+
+  int GetMinSdkVersion() override {
+    return 0u;
+  }
+
+  bool verbose_ = false;
+  std::string package_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Context);
+
+  NameMangler mangler_;
+  SymbolTable symbols_;
+  StdErrDiagnostics diag_;
+};
+
+int Convert(const vector<StringPiece>& args) {
+  Context context;
+  std::string output_path;
+  TableFlattenerOptions options;
+  Flags flags =
+      Flags()
+          .RequiredFlag("-o", "Output path", &output_path)
+          .OptionalSwitch("--enable-sparse-encoding",
+                          "Enables encoding sparse entries using a binary search tree.\n"
+                          "This decreases APK size at the cost of resource retrieval performance.",
+                          &options.use_sparse_entries)
+          .OptionalSwitch("-v", "Enables verbose logging", &context.verbose_);
+  if (!flags.Parse("aapt2 convert", args, &std::cerr)) {
+    return 1;
+  }
+
+  if (flags.GetArgs().size() != 1) {
+    std::cerr << "must supply a single proto APK\n";
+    flags.Usage("aapt2 convert", &std::cerr);
+    return 1;
+  }
+
+  const StringPiece& path = flags.GetArgs()[0];
+  unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
+  if (apk == nullptr) {
+    context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK");
+    return 1;
+  }
+
+  Maybe<AppInfo> app_info =
+      ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
+  if (!app_info) {
+    return 1;
+  }
+
+  context.package_ = app_info.value().package;
+
+  unique_ptr<IArchiveWriter> writer =
+      CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path);
+  if (writer == nullptr) {
+    return 1;
+  }
+  return ConvertProtoApkToBinaryApk(&context, std::move(apk), options, writer.get()) ? 0 : 1;
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 625c47c..fc1f1d6 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -357,8 +357,9 @@
     return 1;
   }
 
-  std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
-  std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[1]);
+  IDiagnostics* diag = context.GetDiagnostics();
+  std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(flags.GetArgs()[0], diag);
+  std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(flags.GetArgs()[1], diag);
   if (!apk_a || !apk_b) {
     return 1;
   }
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 090c3fb..bc8f1dc 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -78,7 +78,7 @@
         return false;
       }
 
-      if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+      if (!DeserializeTableFromPb(pb_table, zip.get(), &table, &err)) {
         context->GetDiagnostics()->Error(DiagMessage(file_path)
                                          << "failed to parse table: " << err);
         return false;
@@ -90,7 +90,8 @@
         return false;
       }
 
-      BinaryResourceParser parser(context, &table, Source(file_path), data->data(), data->size());
+      BinaryResourceParser parser(context->GetDiagnostics(), &table, Source(file_path),
+                                  data->data(), data->size());
       if (!parser.Parse()) {
         return false;
       }
@@ -129,7 +130,7 @@
 
       ResourceTable table;
       err.clear();
-      if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+      if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &err)) {
         context->GetDiagnostics()->Error(DiagMessage(file_path)
                                          << "failed to parse table: " << err);
         continue;
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index e0dae1b..b372923 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -29,6 +29,7 @@
 #include "AppInfo.h"
 #include "Debug.h"
 #include "Flags.h"
+#include "LoadedApk.h"
 #include "Locale.h"
 #include "NameMangler.h"
 #include "ResourceUtils.h"
@@ -39,7 +40,6 @@
 #include "filter/ConfigFilter.h"
 #include "format/Archive.h"
 #include "format/Container.h"
-#include "format/binary/BinaryResourceParser.h"
 #include "format/binary/TableFlattener.h"
 #include "format/binary/XmlFlattener.h"
 #include "format/proto/ProtoDeserialize.h"
@@ -71,9 +71,6 @@
 
 namespace aapt {
 
-constexpr static const char kApkResourceTablePath[] = "resources.arsc";
-constexpr static const char kProtoResourceTablePath[] = "resources.pb";
-
 enum class OutputFormat {
   kApk,
   kProto,
@@ -298,23 +295,6 @@
   return false;
 }
 
-static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, const void* data,
-                                                      size_t len, IDiagnostics* diag) {
-  pb::ResourceTable pb_table;
-  if (!pb_table.ParseFromArray(data, len)) {
-    diag->Error(DiagMessage(source) << "invalid compiled table");
-    return {};
-  }
-
-  std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
-  std::string error;
-  if (!DeserializeTableFromPb(pb_table, table.get(), &error)) {
-    diag->Error(DiagMessage(source) << "invalid compiled table: " << error);
-    return {};
-  }
-  return table;
-}
-
 // Inflates an XML file from the source path.
 static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path, IDiagnostics* diag) {
   FileInputStream fin(path);
@@ -587,7 +567,7 @@
               pb::XmlNode pb_xml_node;
               if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) {
                 context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
-                                                  << "failed to parse proto xml");
+                                                  << "failed to parse proto XML");
                 return false;
               }
 
@@ -595,13 +575,15 @@
               file_op.xml_to_flatten = DeserializeXmlResourceFromPb(pb_xml_node, &error);
               if (file_op.xml_to_flatten == nullptr) {
                 context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
-                                                  << "failed to deserialize proto xml: " << error);
+                                                  << "failed to deserialize proto XML: " << error);
                 return false;
               }
             } else {
-              file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(),
-                                                    context_->GetDiagnostics(), file->GetSource());
+              std::string error_str;
+              file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(), &error_str);
               if (file_op.xml_to_flatten == nullptr) {
+                context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+                                                  << "failed to parse binary XML: " << error_str);
                 return false;
               }
             }
@@ -748,22 +730,29 @@
         file_collection_(util::make_unique<io::FileCollection>()) {
   }
 
-  /**
-   * Creates a SymbolTable that loads symbols from the various APKs and caches
-   * the results for faster lookup.
-   */
+  // Creates a SymbolTable that loads symbols from the various APKs.
   bool LoadSymbolsFromIncludePaths() {
-    std::unique_ptr<AssetManagerSymbolSource> asset_source =
-        util::make_unique<AssetManagerSymbolSource>();
+    auto asset_source = util::make_unique<AssetManagerSymbolSource>();
     for (const std::string& path : options_.include_paths) {
       if (context_->IsVerbose()) {
         context_->GetDiagnostics()->Note(DiagMessage() << "including " << path);
       }
 
-      // First try to load the file as a static lib.
-      std::string error_str;
-      std::unique_ptr<ResourceTable> include_static = LoadStaticLibrary(path, &error_str);
-      if (include_static) {
+      std::string error;
+      auto zip_collection = io::ZipFileCollection::Create(path, &error);
+      if (zip_collection == nullptr) {
+        context_->GetDiagnostics()->Error(DiagMessage() << "failed to open APK: " << error);
+        return false;
+      }
+
+      if (zip_collection->FindFile(kProtoResourceTablePath) != nullptr) {
+        // Load this as a static library include.
+        std::unique_ptr<LoadedApk> static_apk = LoadedApk::LoadProtoApkFromFileCollection(
+            Source(path), std::move(zip_collection), context_->GetDiagnostics());
+        if (static_apk == nullptr) {
+          return false;
+        }
+
         if (context_->GetPackageType() != PackageType::kStaticLib) {
           // Can't include static libraries when not building a static library (they have no IDs
           // assigned).
@@ -772,13 +761,15 @@
           return false;
         }
 
-        // If we are using --no-static-lib-packages, we need to rename the
-        // package of this table to our compilation package.
+        ResourceTable* table = static_apk->GetResourceTable();
+
+        // If we are using --no-static-lib-packages, we need to rename the package of this table to
+        // our compilation package.
         if (options_.no_static_lib_packages) {
           // Since package names can differ, and multiple packages can exist in a ResourceTable,
           // we place the requirement that all static libraries are built with the package
           // ID 0x7f. So if one is not found, this is an error.
-          if (ResourceTablePackage* pkg = include_static->FindPackageById(kAppPackageId)) {
+          if (ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId)) {
             pkg->name = context_->GetCompilationPackage();
           } else {
             context_->GetDiagnostics()->Error(DiagMessage(path)
@@ -788,19 +779,14 @@
         }
 
         context_->GetExternalSymbols()->AppendSource(
-            util::make_unique<ResourceTableSymbolSource>(include_static.get()));
-
-        static_table_includes_.push_back(std::move(include_static));
-
-      } else if (!error_str.empty()) {
-        // We had an error with reading, so fail.
-        context_->GetDiagnostics()->Error(DiagMessage(path) << error_str);
-        return false;
-      }
-
-      if (!asset_source->AddAssetPath(path)) {
-        context_->GetDiagnostics()->Error(DiagMessage(path) << "failed to load include path");
-        return false;
+            util::make_unique<ResourceTableSymbolSource>(table));
+        static_library_includes_.push_back(std::move(static_apk));
+      } else {
+        if (!asset_source->AddAssetPath(path)) {
+          context_->GetDiagnostics()->Error(DiagMessage()
+                                            << "failed to load include path " << path);
+          return false;
+        }
       }
     }
 
@@ -1194,46 +1180,18 @@
     return true;
   }
 
-  std::unique_ptr<ResourceTable> LoadStaticLibrary(const std::string& input,
-                                                   std::string* out_error) {
-    std::unique_ptr<io::ZipFileCollection> collection =
-        io::ZipFileCollection::Create(input, out_error);
-    if (!collection) {
-      return {};
-    }
-    return LoadTablePbFromCollection(collection.get());
-  }
-
-  std::unique_ptr<ResourceTable> LoadTablePbFromCollection(io::IFileCollection* collection) {
-    io::IFile* file = collection->FindFile(kProtoResourceTablePath);
-    if (!file) {
-      return {};
-    }
-
-    std::unique_ptr<io::IData> data = file->OpenAsData();
-    return LoadTableFromPb(file->GetSource(), data->data(), data->size(),
-                           context_->GetDiagnostics());
-  }
-
   bool MergeStaticLibrary(const std::string& input, bool override) {
     if (context_->IsVerbose()) {
       context_->GetDiagnostics()->Note(DiagMessage() << "merging static library " << input);
     }
 
-    std::string error_str;
-    std::unique_ptr<io::ZipFileCollection> collection =
-        io::ZipFileCollection::Create(input, &error_str);
-    if (!collection) {
-      context_->GetDiagnostics()->Error(DiagMessage(input) << error_str);
-      return false;
-    }
-
-    std::unique_ptr<ResourceTable> table = LoadTablePbFromCollection(collection.get());
-    if (!table) {
+    std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(input, context_->GetDiagnostics());
+    if (apk == nullptr) {
       context_->GetDiagnostics()->Error(DiagMessage(input) << "invalid static library");
       return false;
     }
 
+    ResourceTable* table = apk->GetResourceTable();
     ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId);
     if (!pkg) {
       context_->GetDiagnostics()->Error(DiagMessage(input) << "static library has no package");
@@ -1254,13 +1212,12 @@
       // Clear the package name, so as to make the resources look like they are coming from the
       // local package.
       pkg->name = "";
-      result = table_merger_->Merge(Source(input), table.get(), override, collection.get());
+      result = table_merger_->Merge(Source(input), table, override);
 
     } else {
       // This is the proper way to merge libraries, where the package name is
       // preserved and resource names are mangled.
-      result =
-          table_merger_->MergeAndMangle(Source(input), pkg->name, table.get(), collection.get());
+      result = table_merger_->MergeAndMangle(Source(input), pkg->name, table);
     }
 
     if (!result) {
@@ -1268,31 +1225,10 @@
     }
 
     // Make sure to move the collection into the set of IFileCollections.
-    collections_.push_back(std::move(collection));
+    merged_apks_.push_back(std::move(apk));
     return true;
   }
 
-  bool MergeResourceTable(io::IFile* file, bool override) {
-    if (context_->IsVerbose()) {
-      context_->GetDiagnostics()->Note(DiagMessage() << "merging resource table "
-                                                     << file->GetSource());
-    }
-
-    std::unique_ptr<io::IData> data = file->OpenAsData();
-    if (!data) {
-      context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "failed to open file");
-      return false;
-    }
-
-    std::unique_ptr<ResourceTable> table =
-        LoadTableFromPb(file->GetSource(), data->data(), data->size(), context_->GetDiagnostics());
-    if (!table) {
-      return false;
-    }
-
-    return table_merger_->Merge(file->GetSource(), table.get(), override);
-  }
-
   bool MergeCompiledFile(const ResourceFile& compiled_file, io::IFile* file, bool override) {
     if (context_->IsVerbose()) {
       context_->GetDiagnostics()->Note(DiagMessage()
@@ -1423,7 +1359,7 @@
 
         ResourceTable table;
         std::string error;
-        if (!DeserializeTableFromPb(pb_table, &table, &error)) {
+        if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &error)) {
           context_->GetDiagnostics()->Error(DiagMessage(src)
                                             << "failed to deserialize resource table: " << error);
           return false;
@@ -1868,13 +1804,15 @@
   // A pointer to the FileCollection representing the filesystem (not archives).
   std::unique_ptr<io::FileCollection> file_collection_;
 
-  // A vector of IFileCollections. This is mainly here to keep ownership of the
+  // A vector of IFileCollections. This is mainly here to retain ownership of the
   // collections.
   std::vector<std::unique_ptr<io::IFileCollection>> collections_;
 
-  // A vector of ResourceTables. This is here to retain ownership, so that the
-  // SymbolTable can use these.
-  std::vector<std::unique_ptr<ResourceTable>> static_table_includes_;
+  // The set of merged APKs. This is mainly here to retain ownership of the APKs.
+  std::vector<std::unique_ptr<LoadedApk>> merged_apks_;
+
+  // The set of included APKs (not merged). This is mainly here to retain ownership of the APKs.
+  std::vector<std::unique_ptr<LoadedApk>> static_library_includes_;
 
   // The set of shared libraries being used, mapping their assigned package ID to package name.
   std::map<size_t, std::string> shared_libs_;
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 44e148e..688b6a7 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -281,15 +281,14 @@
   OptimizeContext* context_;
 };
 
-bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
+bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
                                 OptimizeOptions* out_options) {
-  std::unique_ptr<xml::XmlResource> manifest = apk->InflateManifest(context);
+  const xml::XmlResource* manifest = apk->GetManifest();
   if (manifest == nullptr) {
     return false;
   }
 
-  Maybe<AppInfo> app_info =
-      ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics());
+  Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
   if (!app_info) {
     context->GetDiagnostics()->Error(DiagMessage()
                                      << "failed to extract data from AndroidManifest.xml");
@@ -355,7 +354,7 @@
   }
 
   const std::string& apk_path = flags.GetArgs()[0];
-  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, apk_path);
+  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
   if (!apk) {
     return 1;
   }
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 708bed8..d39f43e8 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -214,9 +214,10 @@
   return doc;
 }
 
-static Maybe<std::string> ExtractCompiledString(xml::Attribute* attr, std::string* out_error) {
-  if (attr->compiled_value != nullptr) {
-    String* compiled_str = ValueCast<String>(attr->compiled_value.get());
+static Maybe<std::string> ExtractCompiledString(const xml::Attribute& attr,
+                                                std::string* out_error) {
+  if (attr.compiled_value != nullptr) {
+    const String* compiled_str = ValueCast<String>(attr.compiled_value.get());
     if (compiled_str != nullptr) {
       if (!compiled_str->value->empty()) {
         return *compiled_str->value;
@@ -230,16 +231,16 @@
   }
 
   // Fallback to the plain text value if there is one.
-  if (!attr->value.empty()) {
-    return attr->value;
+  if (!attr.value.empty()) {
+    return attr.value;
   }
   *out_error = "value is an empty string";
   return {};
 }
 
-static Maybe<uint32_t> ExtractCompiledInt(xml::Attribute* attr, std::string* out_error) {
-  if (attr->compiled_value != nullptr) {
-    BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get());
+static Maybe<uint32_t> ExtractCompiledInt(const xml::Attribute& attr, std::string* out_error) {
+  if (attr.compiled_value != nullptr) {
+    const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get());
     if (compiled_prim != nullptr) {
       if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT &&
           compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) {
@@ -251,19 +252,19 @@
   }
 
   // Fallback to the plain text value if there is one.
-  Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr->value);
+  Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr.value);
   if (integer) {
     return integer;
   }
   std::stringstream error_msg;
-  error_msg << "'" << attr->value << "' is not a valid integer";
+  error_msg << "'" << attr.value << "' is not a valid integer";
   *out_error = error_msg.str();
   return {};
 }
 
-static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error) {
-  if (attr->compiled_value != nullptr) {
-    BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get());
+static Maybe<int> ExtractSdkVersion(const xml::Attribute& attr, std::string* out_error) {
+  if (attr.compiled_value != nullptr) {
+    const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get());
     if (compiled_prim != nullptr) {
       if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT &&
           compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) {
@@ -273,7 +274,7 @@
       return {};
     }
 
-    String* compiled_str = ValueCast<String>(attr->compiled_value.get());
+    const String* compiled_str = ValueCast<String>(attr.compiled_value.get());
     if (compiled_str != nullptr) {
       Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(*compiled_str->value);
       if (sdk_version) {
@@ -288,19 +289,20 @@
   }
 
   // Fallback to the plain text value if there is one.
-  Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr->value);
+  Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
   if (sdk_version) {
     return sdk_version;
   }
   std::stringstream error_msg;
-  error_msg << "'" << attr->value << "' is not a valid SDK version";
+  error_msg << "'" << attr.value << "' is not a valid SDK version";
   *out_error = error_msg.str();
   return {};
 }
 
-Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag) {
+Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res,
+                                                IDiagnostics* diag) {
   // Make sure the first element is <manifest> with package attribute.
-  xml::Element* manifest_el = xml_res->root.get();
+  const xml::Element* manifest_el = xml_res.root.get();
   if (manifest_el == nullptr) {
     return {};
   }
@@ -308,63 +310,63 @@
   AppInfo app_info;
 
   if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
-    diag->Error(DiagMessage(xml_res->file.source) << "root tag must be <manifest>");
+    diag->Error(DiagMessage(xml_res.file.source) << "root tag must be <manifest>");
     return {};
   }
 
-  xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
+  const xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
   if (!package_attr) {
-    diag->Error(DiagMessage(xml_res->file.source) << "<manifest> must have a 'package' attribute");
+    diag->Error(DiagMessage(xml_res.file.source) << "<manifest> must have a 'package' attribute");
     return {};
   }
 
   std::string error_msg;
-  Maybe<std::string> maybe_package = ExtractCompiledString(package_attr, &error_msg);
+  Maybe<std::string> maybe_package = ExtractCompiledString(*package_attr, &error_msg);
   if (!maybe_package) {
-    diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+    diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
                 << "invalid package name: " << error_msg);
     return {};
   }
   app_info.package = maybe_package.value();
 
-  if (xml::Attribute* version_code_attr =
+  if (const xml::Attribute* version_code_attr =
           manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
-    Maybe<uint32_t> maybe_code = ExtractCompiledInt(version_code_attr, &error_msg);
+    Maybe<uint32_t> maybe_code = ExtractCompiledInt(*version_code_attr, &error_msg);
     if (!maybe_code) {
-      diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+      diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
                   << "invalid android:versionCode: " << error_msg);
       return {};
     }
     app_info.version_code = maybe_code.value();
   }
 
-  if (xml::Attribute* revision_code_attr =
+  if (const xml::Attribute* revision_code_attr =
           manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
-    Maybe<uint32_t> maybe_code = ExtractCompiledInt(revision_code_attr, &error_msg);
+    Maybe<uint32_t> maybe_code = ExtractCompiledInt(*revision_code_attr, &error_msg);
     if (!maybe_code) {
-      diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+      diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
                   << "invalid android:revisionCode: " << error_msg);
       return {};
     }
     app_info.revision_code = maybe_code.value();
   }
 
-  if (xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
-    Maybe<std::string> maybe_split_name = ExtractCompiledString(split_name_attr, &error_msg);
+  if (const xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
+    Maybe<std::string> maybe_split_name = ExtractCompiledString(*split_name_attr, &error_msg);
     if (!maybe_split_name) {
-      diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+      diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
                   << "invalid split name: " << error_msg);
       return {};
     }
     app_info.split_name = maybe_split_name.value();
   }
 
-  if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
-    if (xml::Attribute* min_sdk =
+  if (const xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+    if (const xml::Attribute* min_sdk =
             uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
-      Maybe<int> maybe_sdk = ExtractSdkVersion(min_sdk, &error_msg);
+      Maybe<int> maybe_sdk = ExtractSdkVersion(*min_sdk, &error_msg);
       if (!maybe_sdk) {
-        diag->Error(DiagMessage(xml_res->file.source.WithLine(uses_sdk_el->line_number))
+        diag->Error(DiagMessage(xml_res.file.source.WithLine(uses_sdk_el->line_number))
                     << "invalid android:minSdkVersion: " << error_msg);
         return {};
       }
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index fd9b39c..7611c15 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -57,7 +57,8 @@
                                                         const SplitConstraints& constraints);
 
 // Extracts relevant info from the AndroidManifest.xml.
-Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag);
+Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res,
+                                                IDiagnostics* diag);
 
 }  // namespace aapt
 
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 95eec4a..66510b0 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -73,29 +73,22 @@
 
 }  // namespace
 
-BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
+BinaryResourceParser::BinaryResourceParser(IDiagnostics* diag, ResourceTable* table,
                                            const Source& source, const void* data, size_t len,
                                            io::IFileCollection* files)
-    : context_(context),
-      table_(table),
-      source_(source),
-      data_(data),
-      data_len_(len),
-      files_(files) {
+    : diag_(diag), table_(table), source_(source), data_(data), data_len_(len), files_(files) {
 }
 
 bool BinaryResourceParser::Parse() {
   ResChunkPullParser parser(data_, data_len_);
 
   if (!ResChunkPullParser::IsGoodEvent(parser.Next())) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "corrupt resources.arsc: " << parser.error());
+    diag_->Error(DiagMessage(source_) << "corrupt resources.arsc: " << parser.error());
     return false;
   }
 
   if (parser.chunk()->type != android::RES_TABLE_TYPE) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << StringPrintf("unknown chunk of type 0x%02x",
+    diag_->Error(DiagMessage(source_) << StringPrintf("unknown chunk of type 0x%02x",
                                                       static_cast<int>(parser.chunk()->type)));
     return false;
   }
@@ -106,13 +99,12 @@
 
   if (parser.Next() != ResChunkPullParser::Event::kEndDocument) {
     if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
-      context_->GetDiagnostics()->Warn(
-          DiagMessage(source_) << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error());
+      diag_->Warn(DiagMessage(source_)
+                  << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error());
     } else {
-      context_->GetDiagnostics()->Warn(
-          DiagMessage(source_) << StringPrintf(
-              "unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE",
-              static_cast<int>(parser.chunk()->type)));
+      diag_->Warn(DiagMessage(source_)
+                  << StringPrintf("unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE",
+                                  static_cast<int>(parser.chunk()->type)));
     }
   }
   return true;
@@ -122,7 +114,7 @@
 bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) {
   const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk);
   if (!table_header) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_header chunk");
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_header chunk");
     return false;
   }
 
@@ -135,17 +127,15 @@
           status_t err =
               value_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
           if (err != NO_ERROR) {
-            context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                              << "corrupt string pool in ResTable: "
-                                              << value_pool_.getError());
+            diag_->Error(DiagMessage(source_)
+                         << "corrupt string pool in ResTable: " << value_pool_.getError());
             return false;
           }
 
           // Reserve some space for the strings we are going to add.
           table_->string_pool.HintWillAdd(value_pool_.size(), value_pool_.styleCount());
         } else {
-          context_->GetDiagnostics()->Warn(DiagMessage(source_)
-                                           << "unexpected string pool in ResTable");
+          diag_->Warn(DiagMessage(source_) << "unexpected string pool in ResTable");
         }
         break;
 
@@ -156,16 +146,15 @@
         break;
 
       default:
-        context_->GetDiagnostics()->Warn(
-            DiagMessage(source_) << "unexpected chunk type "
-                                 << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
+        diag_->Warn(DiagMessage(source_)
+                    << "unexpected chunk type "
+                    << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
         break;
     }
   }
 
   if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "corrupt resource table: " << parser.error());
+    diag_->Error(DiagMessage(source_) << "corrupt resource table: " << parser.error());
     return false;
   }
   return true;
@@ -176,14 +165,13 @@
       sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
   const ResTable_package* package_header = ConvertTo<ResTable_package, kMinPackageSize>(chunk);
   if (!package_header) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_package chunk");
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_package chunk");
     return false;
   }
 
   uint32_t package_id = util::DeviceToHost32(package_header->id);
   if (package_id > std::numeric_limits<uint8_t>::max()) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "package ID is too big (" << package_id << ")");
+    diag_->Error(DiagMessage(source_) << "package ID is too big (" << package_id << ")");
     return false;
   }
 
@@ -198,9 +186,8 @@
   ResourceTablePackage* package =
       table_->CreatePackage(util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id));
   if (!package) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "incompatible package '" << package_name << "' with ID "
-                                      << package_id);
+    diag_->Error(DiagMessage(source_)
+                 << "incompatible package '" << package_name << "' with ID " << package_id);
     return false;
   }
 
@@ -218,8 +205,7 @@
           status_t err =
               type_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
           if (err != NO_ERROR) {
-            context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                              << "corrupt type string pool in "
+            diag_->Error(DiagMessage(source_) << "corrupt type string pool in "
                                               << "ResTable_package: " << type_pool_.getError());
             return false;
           }
@@ -227,13 +213,12 @@
           status_t err =
               key_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
           if (err != NO_ERROR) {
-            context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                              << "corrupt key string pool in "
+            diag_->Error(DiagMessage(source_) << "corrupt key string pool in "
                                               << "ResTable_package: " << key_pool_.getError());
             return false;
           }
         } else {
-          context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unexpected string pool");
+          diag_->Warn(DiagMessage(source_) << "unexpected string pool");
         }
         break;
 
@@ -256,16 +241,15 @@
         break;
 
       default:
-        context_->GetDiagnostics()->Warn(
-            DiagMessage(source_) << "unexpected chunk type "
-                                 << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
+        diag_->Warn(DiagMessage(source_)
+                    << "unexpected chunk type "
+                    << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
         break;
     }
   }
 
   if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "corrupt ResTable_package: " << parser.error());
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_package: " << parser.error());
     return false;
   }
 
@@ -278,19 +262,18 @@
 
 bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
   if (type_pool_.getError() != NO_ERROR) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool");
+    diag_->Error(DiagMessage(source_) << "missing type string pool");
     return false;
   }
 
   const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk);
   if (!type_spec) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk");
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk");
     return false;
   }
 
   if (type_spec->id == 0) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "ResTable_typeSpec has invalid id: " << type_spec->id);
+    diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id);
     return false;
   }
   return true;
@@ -299,12 +282,12 @@
 bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
                                      const ResChunk_header* chunk) {
   if (type_pool_.getError() != NO_ERROR) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool");
+    diag_->Error(DiagMessage(source_) << "missing type string pool");
     return false;
   }
 
   if (key_pool_.getError() != NO_ERROR) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing key string pool");
+    diag_->Error(DiagMessage(source_) << "missing key string pool");
     return false;
   }
 
@@ -312,13 +295,12 @@
   // a lot and has its own code to handle variable size.
   const ResTable_type* type = ConvertTo<ResTable_type, kResTableTypeMinSize>(chunk);
   if (!type) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_type chunk");
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_type chunk");
     return false;
   }
 
   if (type->id == 0) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "ResTable_type has invalid id: " << (int)type->id);
+    diag_->Error(DiagMessage(source_) << "ResTable_type has invalid id: " << (int)type->id);
     return false;
   }
 
@@ -329,9 +311,8 @@
 
   const ResourceType* parsed_type = ParseResourceType(type_str);
   if (!parsed_type) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "invalid type name '" << type_str << "' for type with ID "
-                                      << (int)type->id);
+    diag_->Error(DiagMessage(source_)
+                 << "invalid type name '" << type_str << "' for type with ID " << (int)type->id);
     return false;
   }
 
@@ -360,14 +341,13 @@
     }
 
     if (!resource_value) {
-      context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                        << "failed to parse value for resource " << name << " ("
+      diag_->Error(DiagMessage(source_) << "failed to parse value for resource " << name << " ("
                                         << res_id << ") with configuration '" << config << "'");
       return false;
     }
 
     if (!table_->AddResourceAllowMangled(name, res_id, config, {}, std::move(resource_value),
-                                         context_->GetDiagnostics())) {
+                                         diag_)) {
       return false;
     }
 
@@ -375,7 +355,7 @@
       Symbol symbol;
       symbol.state = SymbolState::kPublic;
       symbol.source = source_.WithLine(0);
-      if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, context_->GetDiagnostics())) {
+      if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, diag_)) {
         return false;
       }
     }
@@ -410,15 +390,14 @@
                                                        const android::Res_value& value) {
   std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(name.type, config, value_pool_,
                                                                   value, &table_->string_pool);
-  if (files_ != nullptr && item != nullptr) {
+  if (files_ != nullptr) {
     FileReference* file_ref = ValueCast<FileReference>(item.get());
     if (file_ref != nullptr) {
       file_ref->file = files_->FindFile(*file_ref->path);
       if (file_ref->file == nullptr) {
-        context_->GetDiagnostics()->Warn(DiagMessage()
-                                         << "resource " << name << " for config '" << config
-                                         << "' is a file reference to '" << *file_ref->path
-                                         << "' but no such path exists");
+        diag_->Warn(DiagMessage() << "resource " << name << " for config '" << config
+                                  << "' is a file reference to '" << *file_ref->path
+                                  << "' but no such path exists");
       }
     }
   }
@@ -432,7 +411,7 @@
     case ResourceType::kStyle:
       return ParseStyle(name, config, map);
     case ResourceType::kAttrPrivate:
-    // fallthrough
+      // fallthrough
     case ResourceType::kAttr:
       return ParseAttr(name, config, map);
     case ResourceType::kArray:
@@ -445,8 +424,8 @@
       // We can ignore the value here.
       return util::make_unique<Id>();
     default:
-      context_->GetDiagnostics()->Error(DiagMessage() << "illegal map type '" << ToString(name.type)
-                                                      << "' (" << (int)name.type << ")");
+      diag_->Error(DiagMessage() << "illegal map type '" << ToString(name.type) << "' ("
+                                 << (int)name.type << ")");
       break;
   }
   return {};
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index dc9a384..052f806 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -39,7 +39,7 @@
  public:
   // Creates a parser, which will read `len` bytes from `data`, and add any resources parsed to
   // `table`. `source` is for logging purposes.
-  BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
+  BinaryResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
                        const void* data, size_t data_len, io::IFileCollection* files = nullptr);
 
   // Parses the binary resource table and returns true if successful.
@@ -80,7 +80,7 @@
    */
   bool CollectMetaData(const android::ResTable_map& map_entry, Value* value);
 
-  IAaptContext* context_;
+  IDiagnostics* diag_;
   ResourceTable* table_;
 
   const Source source_;
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 57565a5..259f2e9 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -568,14 +568,6 @@
   ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
   table_header->packageCount = util::HostToDevice32(table->packages.size());
 
-  // Write a self mapping entry for this package if the ID is non-standard (0x7f).
-  if (context->GetPackageType() == PackageType::kApp) {
-    const uint8_t package_id = context->GetPackageId();
-    if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
-      table->included_packages_[package_id] = context->GetCompilationPackage();
-    }
-  }
-
   // Flatten the values string pool.
   StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool);
 
@@ -583,6 +575,14 @@
 
   // Flatten each package.
   for (auto& package : table->packages) {
+    if (context->GetPackageType() == PackageType::kApp) {
+      // Write a self mapping entry for this package if the ID is non-standard (0x7f).
+      const uint8_t package_id = package->id.value();
+      if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
+        table->included_packages_[package_id] = package->name;
+      }
+    }
+
     PackageFlattener flattener(context, package.get(), &table->included_packages_,
                                options_.use_sparse_entries);
     if (!flattener.FlattenPackage(&package_buffer)) {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 6d75973..e11890b 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -71,7 +71,8 @@
       return result;
     }
 
-    BinaryResourceParser parser(context, out_table, {}, content.data(), content.size());
+    BinaryResourceParser parser(context->GetDiagnostics(), out_table, {}, content.data(),
+                                content.size());
     if (!parser.Parse()) {
       return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
     }
@@ -278,7 +279,7 @@
   // Attempt to parse the sparse contents.
 
   ResourceTable sparse_table;
-  BinaryResourceParser parser(context.get(), &sparse_table, Source("test.arsc"),
+  BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
                               sparse_contents.data(), sparse_contents.size());
   ASSERT_TRUE(parser.Parse());
 
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 86bd865..0f0bce8 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -371,7 +371,8 @@
 }
 
 static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool,
-                                     ResourceTable* out_table, std::string* out_error) {
+                                     io::IFileCollection* files, ResourceTable* out_table,
+                                     std::string* out_error) {
   Maybe<uint8_t> id;
   if (pb_package.has_package_id()) {
     id = static_cast<uint8_t>(pb_package.package_id().id());
@@ -444,7 +445,7 @@
         }
 
         config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
-                                                     &out_table->string_pool, out_error);
+                                                     &out_table->string_pool, files, out_error);
         if (config_value->value == nullptr) {
           return false;
         }
@@ -457,8 +458,8 @@
   return true;
 }
 
-bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* out_table,
-                            std::string* out_error) {
+bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollection* files,
+                            ResourceTable* out_table, std::string* out_error) {
   // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
   // causes errors when qualifying it with android::
   using namespace android;
@@ -474,7 +475,7 @@
   }
 
   for (const pb::Package& pb_package : pb_table.package()) {
-    if (!DeserializePackageFromPb(pb_package, source_pool, out_table, out_error)) {
+    if (!DeserializePackageFromPb(pb_package, source_pool, files, out_table, out_error)) {
       return false;
     }
   }
@@ -600,10 +601,11 @@
 std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
                                               const android::ResStringPool& src_pool,
                                               const ConfigDescription& config,
-                                              StringPool* value_pool, std::string* out_error) {
+                                              StringPool* value_pool, io::IFileCollection* files,
+                                              std::string* out_error) {
   std::unique_ptr<Value> value;
   if (pb_value.has_item()) {
-    value = DeserializeItemFromPb(pb_value.item(), src_pool, config, value_pool, out_error);
+    value = DeserializeItemFromPb(pb_value.item(), src_pool, config, value_pool, files, out_error);
     if (value == nullptr) {
       return {};
     }
@@ -651,8 +653,8 @@
             return {};
           }
           DeserializeItemMetaDataFromPb(pb_entry, src_pool, &entry.key);
-          entry.value =
-              DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+          entry.value = DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, files,
+                                              out_error);
           if (entry.value == nullptr) {
             return {};
           }
@@ -680,8 +682,8 @@
         const pb::Array& pb_array = pb_compound_value.array();
         std::unique_ptr<Array> array = util::make_unique<Array>();
         for (const pb::Array_Element& pb_entry : pb_array.element()) {
-          std::unique_ptr<Item> item =
-              DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+          std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), src_pool, config,
+                                                             value_pool, files, out_error);
           if (item == nullptr) {
             return {};
           }
@@ -697,8 +699,8 @@
         std::unique_ptr<Plural> plural = util::make_unique<Plural>();
         for (const pb::Plural_Entry& pb_entry : pb_plural.entry()) {
           size_t plural_idx = DeserializePluralEnumFromPb(pb_entry.arity());
-          plural->values[plural_idx] =
-              DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+          plural->values[plural_idx] = DeserializeItemFromPb(pb_entry.item(), src_pool, config,
+                                                             value_pool, files, out_error);
           if (!plural->values[plural_idx]) {
             return {};
           }
@@ -727,7 +729,7 @@
 std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
                                             const android::ResStringPool& src_pool,
                                             const ConfigDescription& config, StringPool* value_pool,
-                                            std::string* out_error) {
+                                            io::IFileCollection* files, std::string* out_error) {
   switch (pb_item.value_case()) {
     case pb::Item::kRef: {
       const pb::Reference& pb_ref = pb_item.ref();
@@ -774,6 +776,9 @@
           util::make_unique<FileReference>(value_pool->MakeRef(
               pb_file.path(), StringPool::Context(StringPool::Context::kHighPriority, config)));
       file_ref->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+      if (files != nullptr) {
+        file_ref->file = files->FindFile(*file_ref->path);
+      }
       return std::move(file_ref);
     } break;
 
@@ -825,7 +830,7 @@
     }
     if (pb_attr.has_compiled_item()) {
       attr.compiled_value =
-          DeserializeItemFromPb(pb_attr.compiled_item(), {}, {}, value_pool, out_error);
+          DeserializeItemFromPb(pb_attr.compiled_item(), {}, {}, value_pool, nullptr, out_error);
       if (attr.compiled_value == nullptr) {
         return {};
       }
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.h b/tools/aapt2/format/proto/ProtoDeserialize.h
index 7dc54f2..0c581a1 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.h
+++ b/tools/aapt2/format/proto/ProtoDeserialize.h
@@ -27,6 +27,7 @@
 #include "Resources.pb.h"
 #include "ResourcesInternal.pb.h"
 #include "StringPool.h"
+#include "io/File.h"
 #include "xml/XmlDom.h"
 
 namespace aapt {
@@ -34,12 +35,13 @@
 std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
                                               const android::ResStringPool& src_pool,
                                               const ConfigDescription& config,
-                                              StringPool* value_pool, std::string* out_error);
+                                              StringPool* value_pool, io::IFileCollection* files,
+                                              std::string* out_error);
 
 std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
                                             const android::ResStringPool& src_pool,
                                             const ConfigDescription& config, StringPool* value_pool,
-                                            std::string* out_error);
+                                            io::IFileCollection* files, std::string* out_error);
 
 std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node,
                                                                std::string* out_error);
@@ -50,8 +52,9 @@
 bool DeserializeConfigFromPb(const pb::Configuration& pb_config, ConfigDescription* out_config,
                              std::string* out_error);
 
-bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* out_table,
-                            std::string* out_error);
+// Optional io::IFileCollection used to lookup references to files in the ResourceTable.
+bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollection* files,
+                            ResourceTable* out_table, std::string* out_error);
 
 bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
                                    ResourceFile* out_file, std::string* out_error);
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 8efac8a..9649a4d 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -29,6 +29,12 @@
 
 namespace aapt {
 
+class MockFileCollection : public io::IFileCollection {
+ public:
+  MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path));
+  MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>());
+};
+
 TEST(ProtoSerializeTest, SerializeSinglePackage) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   std::unique_ptr<ResourceTable> table =
@@ -86,9 +92,14 @@
   pb::ResourceTable pb_table;
   SerializeTableToPb(*table, &pb_table);
 
+  test::TestFile file_a("res/layout/main.xml");
+  MockFileCollection files;
+  EXPECT_CALL(files, FindFile(Eq("res/layout/main.xml")))
+      .WillRepeatedly(::testing::Return(&file_a));
+
   ResourceTable new_table;
   std::string error;
-  ASSERT_TRUE(DeserializeTableFromPb(pb_table, &new_table, &error));
+  ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
   EXPECT_THAT(error, IsEmpty());
 
   Id* new_id = test::GetValue<Id>(&new_table, "com.app.a:id/foo");
@@ -124,6 +135,11 @@
   ASSERT_TRUE(actual_ref->id);
   EXPECT_THAT(*actual_ref, Eq(expected_ref));
 
+  FileReference* actual_file_ref =
+      test::GetValue<FileReference>(&new_table, "com.app.a:layout/main");
+  ASSERT_THAT(actual_file_ref, NotNull());
+  EXPECT_THAT(actual_file_ref->file, Eq(&file_a));
+
   StyledString* actual_styled_str =
       test::GetValue<StyledString>(&new_table, "com.app.a:string/styled");
   ASSERT_THAT(actual_styled_str, NotNull());
diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
index 6ed07b0..94686c0 100644
--- a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
+++ b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
@@ -25,5 +25,4 @@
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     AaptTestNamespace_LibOne \
     AaptTestNamespace_LibTwo
-LOCAL_AAPT_FLAGS := -v
 include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 21c6b11..58d0607 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -37,17 +37,14 @@
   CHECK(master_package_ != nullptr) << "package name or ID already taken";
 }
 
-bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay,
-                        io::IFileCollection* collection) {
+bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay) {
   // We allow adding new resources if this is not an overlay, or if the options allow overlays
   // to add new resources.
-  return MergeImpl(src, table, collection, overlay,
-                   options_.auto_add_overlay || !overlay /*allow_new*/);
+  return MergeImpl(src, table, overlay, options_.auto_add_overlay || !overlay /*allow_new*/);
 }
 
 // This will merge packages with the same package name (or no package name).
-bool TableMerger::MergeImpl(const Source& src, ResourceTable* table,
-                            io::IFileCollection* collection, bool overlay, bool allow_new) {
+bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, bool overlay, bool allow_new) {
   bool error = false;
   for (auto& package : table->packages) {
     // Only merge an empty package or the package we're building.
@@ -55,37 +52,20 @@
     // This is because at compile time it is unknown if the attributes are
     // simply uses of the attribute or definitions.
     if (package->name.empty() || context_->GetCompilationPackage() == package->name) {
-      FileMergeCallback callback;
-      if (collection) {
-        callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
-                       FileReference* new_file, FileReference* old_file) -> bool {
-          // The old file's path points inside the APK, so we can use it as is.
-          io::IFile* f = collection->FindFile(*old_file->path);
-          if (!f) {
-            context_->GetDiagnostics()->Error(DiagMessage(src)
-                                              << "file '" << *old_file->path << "' not found");
-            return false;
-          }
-
-          new_file->file = f;
-          return true;
-        };
-      }
-
       // Merge here. Once the entries are merged and mangled, any references to them are still
       // valid. This is because un-mangled references are mangled, then looked up at resolution
       // time. Also, when linking, we convert references with no package name to use the compilation
       // package name.
-      error |=
-          !DoMerge(src, table, package.get(), false /* mangle */, overlay, allow_new, callback);
+      error |= !DoMerge(src, table, package.get(), false /*mangle*/, overlay, allow_new);
     }
   }
   return !error;
 }
 
-// This will merge and mangle resources from a static library.
+// This will merge and mangle resources from a static library. It is assumed that all FileReferences
+// have correctly set their io::IFile*.
 bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_name,
-                                 ResourceTable* table, io::IFileCollection* collection) {
+                                 ResourceTable* table) {
   bool error = false;
   for (auto& package : table->packages) {
     // Warn of packages with an unrelated ID.
@@ -96,23 +76,7 @@
 
     bool mangle = package_name != context_->GetCompilationPackage();
     merged_packages_.insert(package->name);
-
-    auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
-                        FileReference* new_file, FileReference* old_file) -> bool {
-      // The old file's path points inside the APK, so we can use it as is.
-      io::IFile* f = collection->FindFile(*old_file->path);
-      if (!f) {
-        context_->GetDiagnostics()->Error(DiagMessage(src)
-                                          << "file '" << *old_file->path << "' not found");
-        return false;
-      }
-
-      new_file->file = f;
-      return true;
-    };
-
-    error |= !DoMerge(src, table, package.get(), mangle, false /*overlay*/, true /*allow_new*/,
-                      callback);
+    error |= !DoMerge(src, table, package.get(), mangle, false /*overlay*/, true /*allow_new*/);
   }
   return !error;
 }
@@ -187,7 +151,7 @@
 
 static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
                                                        const ResourceNameRef& res_name,
-                                                       const bool overlay,
+                                                       bool overlay,
                                                        ResourceConfigValue* dst_config_value,
                                                        ResourceConfigValue* src_config_value,
                                                        StringPool* pool) {
@@ -220,10 +184,8 @@
 }
 
 bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
-                          ResourceTablePackage* src_package,
-                          const bool mangle_package, const bool overlay,
-                          const bool allow_new_resources,
-                          const FileMergeCallback& callback) {
+                          ResourceTablePackage* src_package, bool mangle_package, bool overlay,
+                          bool allow_new_resources) {
   bool error = false;
 
   for (auto& src_type : src_package->types) {
@@ -292,13 +254,6 @@
           } else {
             new_file_ref = std::unique_ptr<FileReference>(f->Clone(&master_table_->string_pool));
           }
-
-          if (callback) {
-            if (!callback(res_name, src_config_value->config, new_file_ref.get(), f)) {
-              error = true;
-              continue;
-            }
-          }
           dst_config_value->value = std::move(new_file_ref);
 
         } else {
@@ -343,8 +298,8 @@
       ->FindOrCreateValue(file_desc.config, {})
       ->value = std::move(file_ref);
 
-  return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, overlay /* overlay */,
-                 true /* allow_new */, {});
+  return DoMerge(file->GetSource(), &table, pkg, false /*mangle*/, overlay /*overlay*/,
+                 true /*allow_new*/);
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index d024aa4..47e23dd 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -40,6 +40,9 @@
 // TableMerger takes resource tables and merges all packages within the tables that have the same
 // package ID.
 //
+// It is assumed that any FileReference values have their io::IFile pointer set to point to the
+// file they represent.
+//
 // If a package has a different name, all the entries in that table have their names mangled
 // to include the package name. This way there are no collisions. In order to do this correctly,
 // the TableMerger needs to also mangle any FileReference paths. Once these are mangled, the
@@ -60,14 +63,11 @@
 
   // Merges resources from the same or empty package. This is for local sources.
   // If overlay is true, the resources are treated as overlays.
-  // An io::IFileCollection is optional and used to find the referenced Files and process them.
-  bool Merge(const Source& src, ResourceTable* table, bool overlay,
-             io::IFileCollection* collection = nullptr);
+  bool Merge(const Source& src, ResourceTable* table, bool overlay);
 
   // Merges resources from the given package, mangling the name. This is for static libraries.
-  // An io::IFileCollection is needed in order to find the referenced Files and process them.
-  bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table,
-                      io::IFileCollection* collection);
+  // All FileReference values must have their io::IFile set.
+  bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table);
 
   // Merges a compiled file that belongs to this same or empty package.
   bool MergeFile(const ResourceFile& fileDesc, bool overlay, io::IFile* file);
@@ -75,23 +75,16 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(TableMerger);
 
-  using FileMergeCallback = std::function<bool(const ResourceNameRef&,
-                                               const ConfigDescription& config,
-                                               FileReference*, FileReference*)>;
-
   IAaptContext* context_;
   ResourceTable* master_table_;
   TableMergerOptions options_;
   ResourceTablePackage* master_package_;
   std::set<std::string> merged_packages_;
 
-  bool MergeImpl(const Source& src, ResourceTable* src_table,
-                 io::IFileCollection* collection, bool overlay, bool allow_new);
+  bool MergeImpl(const Source& src, ResourceTable* src_table, bool overlay, bool allow_new);
 
-  bool DoMerge(const Source& src, ResourceTable* src_table,
-               ResourceTablePackage* src_package, const bool mangle_package,
-               const bool overlay, const bool allow_new_resources,
-               const FileMergeCallback& callback);
+  bool DoMerge(const Source& src, ResourceTable* src_table, ResourceTablePackage* src_package,
+               bool mangle_package, bool overlay, bool allow_new_resources);
 
   std::unique_ptr<FileReference> CloneAndMangleFile(const std::string& package,
                                                     const FileReference& value);
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 3499809..6aab8de 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -22,11 +22,12 @@
 
 using ::aapt::test::ValueEq;
 using ::testing::Contains;
-using ::testing::NotNull;
-using ::testing::UnorderedElementsAreArray;
-using ::testing::Pointee;
-using ::testing::Field;
 using ::testing::Eq;
+using ::testing::Field;
+using ::testing::NotNull;
+using ::testing::Pointee;
+using ::testing::StrEq;
+using ::testing::UnorderedElementsAreArray;
 
 namespace aapt {
 
@@ -67,10 +68,9 @@
 
   ResourceTable final_table;
   TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
-  io::FileCollection collection;
 
   ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
-  ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
+  ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get()));
 
   EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0);
 
@@ -122,32 +122,35 @@
 }
 
 TEST_F(TableMergerTest, MergeFileReferences) {
+  test::TestFile file_a("res/xml/file.xml");
+  test::TestFile file_b("res/xml/file.xml");
+
   std::unique_ptr<ResourceTable> table_a =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.a", 0x7f)
-          .AddFileReference("com.app.a:xml/file", "res/xml/file.xml")
+          .AddFileReference("com.app.a:xml/file", "res/xml/file.xml", &file_a)
           .Build();
   std::unique_ptr<ResourceTable> table_b =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.b", 0x7f)
-          .AddFileReference("com.app.b:xml/file", "res/xml/file.xml")
+          .AddFileReference("com.app.b:xml/file", "res/xml/file.xml", &file_b)
           .Build();
 
   ResourceTable final_table;
   TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
-  io::FileCollection collection;
-  collection.InsertFile("res/xml/file.xml");
 
   ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
-  ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
+  ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get()));
 
   FileReference* f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/file");
   ASSERT_THAT(f, NotNull());
-  EXPECT_EQ(std::string("res/xml/file.xml"), *f->path);
+  EXPECT_THAT(*f->path, StrEq("res/xml/file.xml"));
+  EXPECT_THAT(f->file, Eq(&file_a));
 
   f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/com.app.b$file");
   ASSERT_THAT(f, NotNull());
-  EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path);
+  EXPECT_THAT(*f->path, StrEq("res/xml/com.app.b$file.xml"));
+  EXPECT_THAT(f->file, Eq(&file_b));
 }
 
 TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 6803088..473693c 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -265,12 +265,14 @@
                                        const PostProcessingConfiguration& config,
                                        std::unique_ptr<XmlResource>* updated_manifest,
                                        IDiagnostics* diag) {
-  *updated_manifest = apk_->InflateManifest(context_);
-  XmlResource* manifest = updated_manifest->get();
-  if (manifest == nullptr) {
+  const xml::XmlResource* apk_manifest = apk_->GetManifest();
+  if (apk_manifest == nullptr) {
     return false;
   }
 
+  *updated_manifest = apk_manifest->Clone();
+  XmlResource* manifest = updated_manifest->get();
+
   // Make sure the first element is <manifest> with package attribute.
   xml::Element* manifest_el = manifest->root.get();
   if (manifest_el == nullptr) {
diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
index c8f3524..30c9146 100644
--- a/tools/aapt2/optimize/MultiApkGenerator_test.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
@@ -106,7 +106,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
@@ -147,7 +147,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
@@ -186,7 +186,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterNoVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 5a62e97..ecec63f 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -78,21 +78,27 @@
 }
 
 ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
-                                                             const StringPiece& path) {
-  return AddFileReference(name, {}, path);
+                                                             const StringPiece& path,
+                                                             io::IFile* file) {
+  return AddFileReference(name, {}, path, file);
 }
 
 ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
                                                              const ResourceId& id,
-                                                             const StringPiece& path) {
-  return AddValue(name, id, util::make_unique<FileReference>(table_->string_pool.MakeRef(path)));
+                                                             const StringPiece& path,
+                                                             io::IFile* file) {
+  auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
+  file_ref->file = file;
+  return AddValue(name, id, std::move(file_ref));
 }
 
 ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
                                                              const StringPiece& path,
-                                                             const ConfigDescription& config) {
-  return AddValue(name, config, {},
-                  util::make_unique<FileReference>(table_->string_pool.MakeRef(path)));
+                                                             const ConfigDescription& config,
+                                                             io::IFile* file) {
+  auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
+  file_ref->file = file;
+  return AddValue(name, config, {}, std::move(file_ref));
 }
 
 ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 263fb55..4cdfc33 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -52,12 +52,15 @@
   ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
                                   const ConfigDescription& config, const android::StringPiece& str);
   ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
-                                         const android::StringPiece& path);
+                                         const android::StringPiece& path,
+                                         io::IFile* file = nullptr);
   ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id,
-                                         const android::StringPiece& path);
+                                         const android::StringPiece& path,
+                                         io::IFile* file = nullptr);
   ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
                                          const android::StringPiece& path,
-                                         const ConfigDescription& config);
+                                         const ConfigDescription& config,
+                                         io::IFile* file = nullptr);
   ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value);
   ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id,
                                  std::unique_ptr<Value> value);
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 3522506..b0cf44a 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -258,8 +258,7 @@
   }
 }
 
-std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,
-                                     const Source& source) {
+std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* out_error) {
   // We import the android namespace because on Windows NO_ERROR is a macro, not
   // an enum, which causes errors when qualifying it with android::
   using namespace android;
@@ -270,7 +269,10 @@
   std::unique_ptr<Element> pending_element;
 
   ResXMLTree tree;
-  if (tree.setTo(data, data_len) != NO_ERROR) {
+  if (tree.setTo(data, len) != NO_ERROR) {
+    if (out_error != nullptr) {
+      *out_error = "failed to initialize ResXMLTree";
+    }
     return {};
   }
 
@@ -361,6 +363,27 @@
   return util::make_unique<XmlResource>(ResourceFile{}, std::move(string_pool), std::move(root));
 }
 
+std::unique_ptr<XmlResource> XmlResource::Clone() const {
+  std::unique_ptr<XmlResource> cloned = util::make_unique<XmlResource>(file);
+  if (root != nullptr) {
+    cloned->root = root->CloneElement([&](const xml::Element& src, xml::Element* dst) {
+      dst->attributes.reserve(src.attributes.size());
+      for (const xml::Attribute& attr : src.attributes) {
+        xml::Attribute cloned_attr;
+        cloned_attr.name = attr.name;
+        cloned_attr.namespace_uri = attr.namespace_uri;
+        cloned_attr.value = attr.value;
+        cloned_attr.compiled_attribute = attr.compiled_attribute;
+        if (attr.compiled_value != nullptr) {
+          cloned_attr.compiled_value.reset(attr.compiled_value->Clone(&cloned->string_pool));
+        }
+        dst->attributes.push_back(std::move(cloned_attr));
+      }
+    });
+  }
+  return cloned;
+}
+
 Element* FindRootElement(Node* node) {
   if (node == nullptr) {
     return nullptr;
@@ -383,12 +406,7 @@
 }
 
 Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) {
-  for (auto& attr : attributes) {
-    if (ns == attr.namespace_uri && name == attr.name) {
-      return &attr;
-    }
-  }
-  return nullptr;
+  return const_cast<Attribute*>(static_cast<const Element*>(this)->FindAttribute(ns, name));
 }
 
 const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const {
@@ -404,17 +422,29 @@
   return FindChildWithAttribute(ns, name, {}, {}, {});
 }
 
+const Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) const {
+  return FindChildWithAttribute(ns, name, {}, {}, {});
+}
+
 Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
                                          const StringPiece& attr_ns, const StringPiece& attr_name,
                                          const StringPiece& attr_value) {
-  for (auto& child : children) {
-    if (Element* el = NodeCast<Element>(child.get())) {
+  return const_cast<Element*>(static_cast<const Element*>(this)->FindChildWithAttribute(
+      ns, name, attr_ns, attr_name, attr_value));
+}
+
+const Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
+                                               const StringPiece& attr_ns,
+                                               const StringPiece& attr_name,
+                                               const StringPiece& attr_value) const {
+  for (const auto& child : children) {
+    if (const Element* el = NodeCast<Element>(child.get())) {
       if (ns == el->namespace_uri && name == el->name) {
         if (attr_ns.empty() && attr_name.empty()) {
           return el;
         }
 
-        Attribute* attr = el->FindAttribute(attr_ns, attr_name);
+        const Attribute* attr = el->FindAttribute(attr_ns, attr_name);
         if (attr && attr_value == attr->value) {
           return el;
         }
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 063d7b9..cf06ba5 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -100,11 +100,21 @@
   Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
   const Attribute* FindAttribute(const android::StringPiece& ns,
                                  const android::StringPiece& name) const;
+
   Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
+  const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const;
+
   Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name,
                                   const android::StringPiece& attr_ns,
                                   const android::StringPiece& attr_name,
                                   const android::StringPiece& attr_value);
+
+  const Element* FindChildWithAttribute(const android::StringPiece& ns,
+                                        const android::StringPiece& name,
+                                        const android::StringPiece& attr_ns,
+                                        const android::StringPiece& attr_name,
+                                        const android::StringPiece& attr_value) const;
+
   std::vector<Element*> GetChildElements();
 
   // Due to overriding of subtypes not working with unique_ptr, define a convenience Clone method
@@ -139,16 +149,16 @@
   StringPool string_pool;
 
   std::unique_ptr<xml::Element> root;
+
+  std::unique_ptr<XmlResource> Clone() const;
 };
 
 // Inflates an XML DOM from an InputStream, logging errors to the logger.
-// Returns the root node on success, or nullptr on failure.
 std::unique_ptr<XmlResource> Inflate(io::InputStream* in, IDiagnostics* diag, const Source& source);
 
-// Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger.
-// Returns the root node on success, or nullptr on failure.
-std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,
-                                     const Source& source);
+// Inflates an XML DOM from a binary ResXMLTree.
+std::unique_ptr<XmlResource> Inflate(const void* data, size_t len,
+                                     std::string* out_error = nullptr);
 
 Element* FindRootElement(Node* node);
 
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 34e6d3f..e5012d6 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -70,8 +70,7 @@
   ASSERT_TRUE(flattener.Consume(context.get(), doc.get()));
 
   auto block = util::Copy(buffer);
-  std::unique_ptr<XmlResource> new_doc =
-      Inflate(block.get(), buffer.size(), context->GetDiagnostics(), Source("test.xml"));
+  std::unique_ptr<XmlResource> new_doc = Inflate(block.get(), buffer.size(), nullptr);
   ASSERT_THAT(new_doc, NotNull());
 
   EXPECT_THAT(new_doc->root->name, StrEq("Layout"));
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index c2959d5..4f3acd6 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1803,18 +1803,14 @@
 
     /**
      * Enable or disable Wi-Fi.
-     *
-     * Note: This method will return false if wifi cannot be enabled (e.g., an incompatible mode
-     * where the user has enabled tethering or Airplane Mode).
-     *
-     * Applications need to have the {@link android.Manifest.permission#CHANGE_WIFI_STATE}
-     * permission to toggle wifi. Callers without the permissions will trigger a
-     * {@link java.lang.SecurityException}.
+     * <p>
+     * Applications must have the {@link android.Manifest.permission#CHANGE_WIFI_STATE}
+     * permission to toggle wifi.
      *
      * @param enabled {@code true} to enable, {@code false} to disable.
-     * @return {@code true} if the operation succeeds (or if the existing state
-     *         is the same as the requested state). False if wifi cannot be toggled on/off when the
-     *         request is made.
+     * @return {@code false} if the request cannot be satisfied; {@code true} indicates that wifi is
+     *         either already in the requested state, or in progress toward the requested state.
+     * @throws  {@link java.lang.SecurityException} if the caller is missing required permissions.
      */
     public boolean setWifiEnabled(boolean enabled) {
         try {