Merge "Autofill metrics improvements:"
diff --git a/Android.bp b/Android.bp
index 70b1fa0..82589c1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -548,14 +548,6 @@
 
         ":framework-statslog-gen",
     ],
-    logtags: [
-        "core/java/android/app/admin/SecurityLogTags.logtags",
-        "core/java/android/content/EventLogTags.logtags",
-        "core/java/android/speech/tts/EventLogTags.logtags",
-        "core/java/android/net/EventLogTags.logtags",
-        "core/java/android/webkit/EventLogTags.logtags",
-        "core/java/com/android/internal/logging/EventLogTags.logtags",
-    ],
 
     aidl: {
         local_include_dirs: [
diff --git a/Android.mk b/Android.mk
index 7ca8358..10d11f3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -656,6 +656,10 @@
 # Check comment when you are updating the API
 update-api: doc-comment-check-docs
 
+# Generate API diffs as part of docs builds
+docs: offline-sdk-referenceonly-diff
+docs: offline-system-sdk-referenceonly-diff
+
 # ====  static html in the sdk ==================================
 include $(CLEAR_VARS)
 
diff --git a/apct-tests/perftests/core/Android.mk b/apct-tests/perftests/core/Android.mk
index f08b402..75cb229 100644
--- a/apct-tests/perftests/core/Android.mk
+++ b/apct-tests/perftests/core/Android.mk
@@ -11,8 +11,9 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     apct-perftests-utils \
-    guava \
-    legacy-android-test
+    guava
+
+LOCAL_JAVA_LIBRARIES := android.test.base
 
 LOCAL_PACKAGE_NAME := CorePerfTests
 
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 87cde03..337aeaa 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -161,6 +161,7 @@
     tests/LogEntryMatcher_test.cpp \
     tests/LogReader_test.cpp \
     tests/MetricsManager_test.cpp \
+    tests/StatsLogProcessor_test.cpp \
     tests/UidMap_test.cpp \
     tests/condition/CombinationConditionTracker_test.cpp \
     tests/condition/SimpleConditionTracker_test.cpp \
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index c291647..2df0c90 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -86,9 +86,7 @@
     // pass the event to metrics managers.
     for (auto& pair : mMetricsManagers) {
         pair.second->onLogEvent(msg);
-        // TODO: THIS CHECK FAILS BECAUSE ONCE UIDMAP SIZE EXCEEDS LIMIT, DROPPING METRICS DATA
-        // DOESN'T HELP. FIX THIS.
-        // flushIfNecessary(msg.GetTimestampNs(), pair.first, pair.second);
+        flushIfNecessary(msg.GetTimestampNs(), pair.first, *(pair.second));
     }
 
     // Hard-coded logic to update the isolated uid's in the uid-map.
@@ -217,23 +215,32 @@
     mLastBroadcastTimes.erase(key);
 }
 
-void StatsLogProcessor::flushIfNecessary(uint64_t timestampNs,
-                                         const ConfigKey& key,
-                                         const unique_ptr<MetricsManager>& metricsManager) {
+void StatsLogProcessor::flushIfNecessary(uint64_t timestampNs, const ConfigKey& key,
+                                         MetricsManager& metricsManager) {
     std::lock_guard<std::mutex> lock(mBroadcastTimesMutex);
 
-    size_t totalBytes = metricsManager->byteSize() + mUidMap->getBytesUsed();
-    // TODO: Find a way to test that the dropping and broadcasts are sent when memory is exceeded.
-    if (totalBytes > kMaxSerializedBytes) {  // Too late. We need to start clearing data.
+    auto lastCheckTime = mLastByteSizeTimes.find(key);
+    if (lastCheckTime != mLastByteSizeTimes.end()) {
+        if (timestampNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) {
+            return;
+        }
+    }
+
+    // We suspect that the byteSize() computation is expensive, so we set a rate limit.
+    size_t totalBytes = metricsManager.byteSize();
+    mLastByteSizeTimes[key] = timestampNs;
+    if (totalBytes >
+        StatsdStats::kMaxMetricsBytesPerConfig) {  // Too late. We need to start clearing data.
         // We ignore the return value so we force each metric producer to clear its contents.
-        metricsManager->onDumpReport();
+        metricsManager.onDumpReport();
         StatsdStats::getInstance().noteDataDropped(key);
         VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str());
-    } else if (totalBytes >
-               .9 * kMaxSerializedBytes) {  // Send broadcast so that receivers can pull data.
-        auto lastFlushNs = mLastBroadcastTimes.find(key);
-        if (lastFlushNs != mLastBroadcastTimes.end()) {
-            if (timestampNs - lastFlushNs->second < kMinBroadcastPeriod) {
+    } else if (totalBytes > .9 * StatsdStats::kMaxMetricsBytesPerConfig) {
+        // Send broadcast so that receivers can pull data.
+        auto lastBroadcastTime = mLastBroadcastTimes.find(key);
+        if (lastBroadcastTime != mLastBroadcastTimes.end()) {
+            if (timestampNs - lastBroadcastTime->second < StatsdStats::kMinBroadcastPeriodNs) {
+                VLOG("StatsD would've sent a broadcast but the rate limit stopped us.");
                 return;
             }
         }
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index e9ac015..a4df23a 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -60,29 +60,25 @@
 
     std::unordered_map<ConfigKey, long> mLastBroadcastTimes;
 
+    // Tracks when we last checked the bytes consumed for each config key.
+    std::unordered_map<ConfigKey, long> mLastByteSizeTimes;
+
     sp<UidMap> mUidMap;  // Reference to the UidMap to lookup app name and version for each uid.
 
     sp<AnomalyMonitor> mAnomalyMonitor;
 
-    /* Max *serialized* size of the logs kept in memory before flushing through binder call.
-       Proto lite does not implement the SpaceUsed() function which gives the in memory byte size.
-       So we cap memory usage by limiting the serialized size. Note that protobuf's in memory size
-       is higher than its serialized size.
-     */
-    static const size_t kMaxSerializedBytes = 16 * 1024;
-
     /* Check if we should send a broadcast if approaching memory limits and if we're over, we
      * actually delete the data. */
-    void flushIfNecessary(uint64_t timestampNs,
-                          const ConfigKey& key,
-                          const unique_ptr<MetricsManager>& metricsManager);
+    void flushIfNecessary(uint64_t timestampNs, const ConfigKey& key,
+                          MetricsManager& metricsManager);
 
     // Function used to send a broadcast so that receiver for the config key can call getData
     // to retrieve the stored data.
     std::function<void(const ConfigKey& key)> mSendBroadcast;
 
-    /* Minimum period between two broadcasts in nanoseconds. Currently set to 60 seconds. */
-    static const unsigned long long kMinBroadcastPeriod = 60 * NS_PER_SEC;
+    FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
+    FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
+    FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index fa7fe0c..dc12efb 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -72,7 +72,7 @@
 
 // ======================================================================
 StatsService::StatsService(const sp<Looper>& handlerLooper)
-    : mAnomalyMonitor(new AnomalyMonitor(2))  // TODO: Put this comment somewhere better
+    : mAnomalyMonitor(new AnomalyMonitor(MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS))
 {
     mUidMap = new UidMap();
     mConfigManager = new ConfigManager();
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index bdae1ef..e434f65 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -46,6 +46,10 @@
     StatsService(const sp<Looper>& handlerLooper);
     virtual ~StatsService();
 
+    /** The anomaly alarm registered with AlarmManager won't be updated by less than this. */
+    // TODO: Consider making this configurable. And choose a good number.
+    const uint32_t MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS = 5;
+
     virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
     virtual status_t dump(int fd, const Vector<String16>& args);
     virtual status_t command(FILE* in, FILE* out, FILE* err, Vector<String8>& args);
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
index 2b2bcfc..4912648 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
@@ -18,6 +18,7 @@
 #include "Log.h"
 
 #include "anomaly/AnomalyMonitor.h"
+#include "guardrail/StatsdStats.h"
 
 namespace android {
 namespace os {
@@ -76,10 +77,7 @@
     if (!wasPresent) return;
     if (mPq.empty()) {
         if (DEBUG) ALOGD("Queue is empty. Cancel any alarm.");
-        mRegisteredAlarmTimeSec = 0;
-        if (mStatsCompanionService != nullptr) {
-            mStatsCompanionService->cancelAnomalyAlarm();
-        }
+        cancelRegisteredAlarmTime_l();
         return;
     }
     uint32_t soonestAlarmTimeSec = mPq.top()->timestampSec;
@@ -106,10 +104,7 @@
     if (!oldAlarms.empty()) {
         if (mPq.empty()) {
             if (DEBUG) ALOGD("Queue is empty. Cancel any alarm.");
-            mRegisteredAlarmTimeSec = 0;
-            if (mStatsCompanionService != nullptr) {
-                mStatsCompanionService->cancelAnomalyAlarm();
-            }
+            cancelRegisteredAlarmTime_l();
         } else {
             // Always update the registered alarm in this case (unlike remove()).
             updateRegisteredAlarmTime_l(mPq.top()->timestampSec);
@@ -123,6 +118,16 @@
     mRegisteredAlarmTimeSec = timestampSec;
     if (mStatsCompanionService != nullptr) {
         mStatsCompanionService->setAnomalyAlarm(secToMs(mRegisteredAlarmTimeSec));
+        StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
+    }
+}
+
+void AnomalyMonitor::cancelRegisteredAlarmTime_l() {
+    if (DEBUG) ALOGD("Cancelling reg alarm.");
+    mRegisteredAlarmTimeSec = 0;
+    if (mStatsCompanionService != nullptr) {
+        mStatsCompanionService->cancelAnomalyAlarm();
+        StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
     }
 }
 
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.h b/cmds/statsd/src/anomaly/AnomalyMonitor.h
index e19c469..7acc7904 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.h
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.h
@@ -138,6 +138,12 @@
      */
     void updateRegisteredAlarmTime_l(uint32_t timestampSec);
 
+    /**
+     * Cancels the alarm registered with StatsCompanionService.
+     * Also correspondingly sets mRegisteredAlarmTimeSec to 0.
+     */
+    void cancelRegisteredAlarmTime_l();
+
     /** Converts uint32 timestamp in seconds to a Java long in msec. */
     int64_t secToMs(uint32_t timeSec);
 };
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 7bacb44..e8b4083 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -18,6 +18,7 @@
 #include "Log.h"
 
 #include "AnomalyTracker.h"
+#include "guardrail/StatsdStats.h"
 
 #include <android/os/IIncidentManager.h>
 #include <android/os/IncidentReportArgs.h>
@@ -31,8 +32,9 @@
 // TODO: Separate DurationAnomalyTracker as a separate subclass and let each MetricProducer
 //       decide and let which one it wants.
 // TODO: Get rid of bucketNumbers, and return to the original circular array method.
-AnomalyTracker::AnomalyTracker(const Alert& alert)
+AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
     : mAlert(alert),
+      mConfigKey(configKey),
       mNumOfPastBuckets(mAlert.number_of_buckets() - 1) {
     VLOG("AnomalyTracker() called");
     if (mAlert.number_of_buckets() <= 0) {
@@ -220,6 +222,8 @@
     } else {
         ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
     }
+
+    StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.name());
 }
 
 void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index 49e8323..874add2 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -18,6 +18,7 @@
 
 #include <gtest/gtest_prod.h>
 #include "AnomalyMonitor.h"
+#include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // Alert
 #include "stats_util.h"  // HashableDimensionKey and DimToValMap
 
@@ -35,7 +36,7 @@
 // Does NOT allow negative values.
 class AnomalyTracker : public virtual RefBase {
 public:
-    AnomalyTracker(const Alert& alert);
+    AnomalyTracker(const Alert& alert, const ConfigKey& configKey);
 
     virtual ~AnomalyTracker();
 
@@ -107,9 +108,13 @@
 
 protected:
     void flushPastBuckets(const int64_t& currBucketNum);
+
     // statsd_config.proto Alert message that defines this tracker.
     const Alert mAlert;
 
+    // A reference to the Alert's config key.
+    const ConfigKey& mConfigKey;
+
     // Number of past buckets. One less than the total number of buckets needed
     // for the anomaly detection (since the current bucket is not in the past).
     int mNumOfPastBuckets;
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index 2957457..b02b9da 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -41,11 +41,9 @@
 const int FIELD_ID_BEGIN_TIME = 1;
 const int FIELD_ID_END_TIME = 2;
 const int FIELD_ID_CONFIG_STATS = 3;
-const int FIELD_ID_MATCHER_STATS = 4;
-const int FIELD_ID_CONDITION_STATS = 5;
-const int FIELD_ID_METRIC_STATS = 6;
 const int FIELD_ID_ATOM_STATS = 7;
 const int FIELD_ID_UIDMAP_STATS = 8;
+const int FIELD_ID_ANOMALY_ALARM_STATS = 9;
 
 const int FIELD_ID_MATCHER_STATS_NAME = 1;
 const int FIELD_ID_MATCHER_STATS_COUNT = 2;
@@ -59,6 +57,8 @@
 const int FIELD_ID_ATOM_STATS_TAG = 1;
 const int FIELD_ID_ATOM_STATS_COUNT = 2;
 
+const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1;
+
 // TODO: add stats for pulled atoms.
 StatsdStats::StatsdStats() {
     mPushedAtomStats.resize(android::util::kMaxPushedAtomId + 1);
@@ -101,11 +101,12 @@
     if (it != mConfigStats.end()) {
         int32_t nowTimeSec = time(nullptr);
         it->second.set_deletion_time_sec(nowTimeSec);
-        // Add condition stats, metrics stats, matcher stats
-        addSubStatsToConfig(key, it->second);
+        // Add condition stats, metrics stats, matcher stats, alert stats
+        addSubStatsToConfigLocked(key, it->second);
         // Remove them after they are added to the config stats.
         mMatcherStats.erase(key);
         mMetricsStats.erase(key);
+        mAlertStats.erase(key);
         mConditionStats.erase(key);
         mIceBox.push_back(it->second);
         mConfigStats.erase(it);
@@ -219,6 +220,17 @@
     matcherStats[name]++;
 }
 
+void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const string& name) {
+    lock_guard<std::mutex> lock(mLock);
+    auto& alertStats = mAlertStats[key];
+    alertStats[name]++;
+}
+
+void StatsdStats::noteRegisteredAnomalyAlarmChanged() {
+    lock_guard<std::mutex> lock(mLock);
+    mAnomalyAlarmRegisteredStats++;
+}
+
 void StatsdStats::noteAtomLogged(int atomId, int32_t timeSec) {
     lock_guard<std::mutex> lock(mLock);
 
@@ -246,6 +258,8 @@
     mConditionStats.clear();
     mMetricsStats.clear();
     std::fill(mPushedAtomStats.begin(), mPushedAtomStats.end(), 0);
+    mAlertStats.clear();
+    mAnomalyAlarmRegisteredStats = 0;
     mMatcherStats.clear();
     for (auto& config : mConfigStats) {
         config.second.clear_broadcast_sent_time_sec();
@@ -254,10 +268,11 @@
         config.second.clear_matcher_stats();
         config.second.clear_condition_stats();
         config.second.clear_metric_stats();
+        config.second.clear_alert_stats();
     }
 }
 
-void StatsdStats::addSubStatsToConfig(const ConfigKey& key,
+void StatsdStats::addSubStatsToConfigLocked(const ConfigKey& key,
                                       StatsdStatsReport_ConfigStats& configStats) {
     // Add matcher stats
     if (mMatcherStats.find(key) != mMatcherStats.end()) {
@@ -289,6 +304,16 @@
             VLOG("metrics %s max output tuple size %d", stats.first.c_str(), stats.second);
         }
     }
+    // Add anomaly detection alert stats
+    if (mAlertStats.find(key) != mAlertStats.end()) {
+        const auto& alertStats = mAlertStats[key];
+        for (const auto& stats : alertStats) {
+            auto output = configStats.add_alert_stats();
+            output->set_name(stats.first);
+            output->set_declared_times(stats.second);
+            VLOG("alert %s declared %d times", stats.first.c_str(), stats.second);
+        }
+    }
 }
 
 void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) {
@@ -358,7 +383,7 @@
             }
         }
 
-        addSubStatsToConfig(pair.first, configStats);
+        addSubStatsToConfigLocked(pair.first, configStats);
 
         const int numBytes = configStats.ByteSize();
         vector<char> buffer(numBytes);
@@ -370,6 +395,7 @@
         configStats.clear_matcher_stats();
         configStats.clear_condition_stats();
         configStats.clear_metric_stats();
+        configStats.clear_alert_stats();
     }
 
     VLOG("********Atom stats***********");
@@ -386,6 +412,15 @@
         }
     }
 
+    if (mAnomalyAlarmRegisteredStats > 0) {
+        VLOG("********AnomalyAlarmStats stats***********");
+        long long token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ANOMALY_ALARM_STATS);
+        proto.write(FIELD_TYPE_INT32 | FIELD_ID_ANOMALY_ALARMS_REGISTERED,
+                    mAnomalyAlarmRegisteredStats);
+        proto.end(token);
+        VLOG("Anomaly alarm registrations: %d", mAnomalyAlarmRegisteredStats);
+    }
+
     const int numBytes = mUidMapStats.ByteSize();
     vector<char> buffer(numBytes);
     mUidMapStats.SerializeToArray(&buffer[0], numBytes);
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index d6f6566..4cf168e 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -19,6 +19,7 @@
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 
 #include <gtest/gtest_prod.h>
+#include <log/log_time.h>
 #include <mutex>
 #include <string>
 #include <vector>
@@ -45,10 +46,20 @@
 
     const static int kMaxTimestampCount = 20;
 
+    // Max memory allowed for storing metrics per configuration. When this limit is approached,
+    // statsd will send a broadcast so that the client can fetch the data and clear this memory.
+    static const size_t kMaxMetricsBytesPerConfig = 128 * 1024;
+
     // Cap the UID map's memory usage to this. This should be fairly high since the UID information
     // is critical for understanding the metrics.
     const static size_t kMaxBytesUsedUidMap = 50 * 1024;
 
+    /* Minimum period between two broadcasts in nanoseconds. */
+    static const unsigned long long kMinBroadcastPeriodNs = 60 * NS_PER_SEC;
+
+    /* Min period between two checks of byte size per config key in nanoseconds. */
+    static const unsigned long long kMinByteSizeCheckPeriodNs = 10 * NS_PER_SEC;
+
     /**
      * Report a new config has been received and report the static stats about the config.
      *
@@ -112,11 +123,24 @@
     void noteMatcherMatched(const ConfigKey& key, const std::string& name);
 
     /**
+     * Report that an anomaly detection alert has been declared.
+     *
+     * [key]: The config key that this alert belongs to.
+     * [name]: The name of the alert.
+     */
+    void noteAnomalyDeclared(const ConfigKey& key, const std::string& name);
+
+    /**
      * Report an atom event has been logged.
      */
     void noteAtomLogged(int atomId, int32_t timeSec);
 
     /**
+     * Report that statsd modified the anomaly alarm registered with StatsCompanionService.
+     */
+    void noteRegisteredAnomalyAlarmChanged();
+
+    /**
      * Records the number of snapshot and delta entries that are being dropped from the uid map.
      */
     void noteUidMapDropped(int snapshots, int deltas);
@@ -174,6 +198,14 @@
     // This is a vector, not a map because it will be accessed A LOT -- for each stats log.
     std::vector<int> mPushedAtomStats;
 
+    // Stores the number of times statsd modified the anomaly alarm registered with
+    // StatsCompanionService.
+    int mAnomalyAlarmRegisteredStats = 0;
+
+    // Stores the number of times an anomaly detection alert has been declared
+    // (per config, per alert name).
+    std::map<const ConfigKey, std::map<const std::string, int>> mAlertStats;
+
     // Stores how many times a matcher have been matched.
     std::map<const ConfigKey, std::map<const std::string, int>> mMatcherStats;
 
@@ -181,7 +213,8 @@
 
     void resetInternalLocked();
 
-    void addSubStatsToConfig(const ConfigKey& key, StatsdStatsReport_ConfigStats& configStats);
+    void addSubStatsToConfigLocked(const ConfigKey& key,
+                                   StatsdStatsReport_ConfigStats& configStats);
 
     void noteDataDropped(const ConfigKey& key, int32_t timeSec);
 
@@ -195,6 +228,7 @@
     FRIEND_TEST(StatsdStatsTest, TestSubStats);
     FRIEND_TEST(StatsdStatsTest, TestAtomLog);
     FRIEND_TEST(StatsdStatsTest, TestTimestampThreshold);
+    FRIEND_TEST(StatsdStatsTest, TestAnomalyMonitor);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index fd484c2..cedea30 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -111,7 +111,7 @@
         return nullptr;
     }
     // TODO: return a DurationAnomalyTracker (which should sublclass AnomalyTracker)
-    return new AnomalyTracker(alert);
+    return new AnomalyTracker(alert, mConfigKey);
 }
 
 void DurationMetricProducer::startNewProtoOutputStreamLocked(long long startTime) {
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index e7e84ab..adeb3cd 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -94,7 +94,7 @@
     }
 
     virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) {
-        return new AnomalyTracker(alert);
+        return new AnomalyTracker(alert, mConfigKey);
     }
 
     void addAnomalyTracker(sp<AnomalyTracker> tracker) {
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 4e6a0ce..86c4733 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -36,7 +36,7 @@
 public:
     MetricsManager(const ConfigKey& configKey, const StatsdConfig& config);
 
-    ~MetricsManager();
+    virtual ~MetricsManager();
 
     // Return whether the configuration is valid.
     bool isConfigValid() const;
@@ -52,11 +52,11 @@
     void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
 
     // Config source owner can call onDumpReport() to get all the metrics collected.
-    std::vector<std::unique_ptr<std::vector<uint8_t>>> onDumpReport();
+    virtual std::vector<std::unique_ptr<std::vector<uint8_t>>> onDumpReport();
 
     // Computes the total byte size of all metrics managed by a single config source.
     // Does not change the state.
-    size_t byteSize();
+    virtual size_t byteSize();
 
 private:
     const ConfigKey mConfigKey;
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 60d9a3d..f8b91fe 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -193,6 +193,11 @@
         optional int32 max_tuple_counts = 2;
     }
 
+    message AlertStats {
+        optional string name = 1;
+        optional int32 declared_times = 2;
+    }
+
     message ConfigStats {
         optional int32 uid = 1;
         optional string name = 2;
@@ -210,6 +215,7 @@
         repeated MatcherStats matcher_stats = 13;
         repeated ConditionStats condition_stats = 14;
         repeated MetricStats metric_stats = 15;
+        repeated AlertStats alert_stats = 16;
     }
 
     repeated ConfigStats config_stats = 3;
@@ -229,4 +235,9 @@
         optional int32 dropped_changes = 5;
     }
     optional UidMapStats uidmap_stats = 8;
+
+    message AnomalyAlarmStats {
+        optional int32 alarms_registered = 1;
+    }
+    optional AnomalyAlarmStats anomaly_alarm_stats = 9;
 }
\ No newline at end of file
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
new file mode 100644
index 0000000..ff04d95
--- /dev/null
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -0,0 +1,117 @@
+// 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 "StatsLogProcessor.h"
+#include "config/ConfigKey.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "guardrail/StatsdStats.h"
+#include "logd/LogEvent.h"
+#include "packages/UidMap.h"
+#include "statslog.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <stdio.h>
+
+using namespace android;
+using namespace testing;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+/**
+ * Mock MetricsManager (ByteSize() is called).
+ */
+class MockMetricsManager : public MetricsManager {
+public:
+    MockMetricsManager() : MetricsManager(ConfigKey(1, "key"), StatsdConfig()) {
+    }
+
+    MOCK_METHOD0(byteSize, size_t());
+    MOCK_METHOD0(onDumpReport, std::vector<std::unique_ptr<std::vector<uint8_t>>>());
+};
+
+TEST(StatsLogProcessorTest, TestRateLimitByteSize) {
+    sp<UidMap> m = new UidMap();
+    sp<AnomalyMonitor> anomalyMonitor;
+    // Construct the processor with a dummy sendBroadcast function that does nothing.
+    StatsLogProcessor p(m, anomalyMonitor, [](const ConfigKey& key) {});
+
+    MockMetricsManager mockMetricsManager;
+
+    ConfigKey key(100, "key");
+    // Expect only the first flush to trigger a check for byte size since the last two are
+    // rate-limited.
+    EXPECT_CALL(mockMetricsManager, byteSize()).Times(1);
+    p.flushIfNecessary(99, key, mockMetricsManager);
+    p.flushIfNecessary(100, key, mockMetricsManager);
+    p.flushIfNecessary(101, key, mockMetricsManager);
+}
+
+TEST(StatsLogProcessorTest, TestRateLimitBroadcast) {
+    sp<UidMap> m = new UidMap();
+    sp<AnomalyMonitor> anomalyMonitor;
+    int broadcastCount = 0;
+    StatsLogProcessor p(m, anomalyMonitor,
+                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+
+    MockMetricsManager mockMetricsManager;
+
+    ConfigKey key(100, "key");
+    EXPECT_CALL(mockMetricsManager, byteSize())
+            .Times(2)
+            .WillRepeatedly(Return(int(StatsdStats::kMaxMetricsBytesPerConfig * .95)));
+
+    // Expect only one broadcast despite always returning a size that should trigger broadcast.
+    p.flushIfNecessary(1, key, mockMetricsManager);
+    EXPECT_EQ(1, broadcastCount);
+
+    // This next call to flush should not trigger a broadcast.
+    p.mLastByteSizeTimes.clear();  // Force another check for byte size.
+    p.flushIfNecessary(2, key, mockMetricsManager);
+    EXPECT_EQ(1, broadcastCount);
+}
+
+TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge) {
+    sp<UidMap> m = new UidMap();
+    sp<AnomalyMonitor> anomalyMonitor;
+    int broadcastCount = 0;
+    StatsLogProcessor p(m, anomalyMonitor,
+                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+
+    MockMetricsManager mockMetricsManager;
+
+    ConfigKey key(100, "key");
+    EXPECT_CALL(mockMetricsManager, byteSize())
+            .Times(1)
+            .WillRepeatedly(Return(int(StatsdStats::kMaxMetricsBytesPerConfig * 1.2)));
+
+    EXPECT_CALL(mockMetricsManager, onDumpReport()).Times(1);
+
+    // Expect to call the onDumpReport and skip the broadcast.
+    p.flushIfNecessary(1, key, mockMetricsManager);
+    EXPECT_EQ(0, broadcastCount);
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index 65c2a05..f385763 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -30,6 +30,8 @@
 namespace os {
 namespace statsd {
 
+const ConfigKey kConfigKey(0, "test");
+
 void AddValueToBucket(const std::vector<std::pair<string, long>>& key_value_pair_list,
                       std::shared_ptr<DimToValMap> bucket) {
     for (auto itr = key_value_pair_list.begin(); itr != key_value_pair_list.end(); itr++) {
@@ -51,7 +53,7 @@
     alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
     alert.set_trigger_if_sum_gt(2);
 
-    AnomalyTracker anomalyTracker(alert);
+    AnomalyTracker anomalyTracker(alert, kConfigKey);
 
     std::shared_ptr<DimToValMap> bucket0 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
     int64_t eventTimestamp0 = 10;
@@ -168,7 +170,7 @@
     alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
     alert.set_trigger_if_sum_gt(2);
 
-    AnomalyTracker anomalyTracker(alert);
+    AnomalyTracker anomalyTracker(alert, kConfigKey);
 
     std::shared_ptr<DimToValMap> bucket9 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
     std::shared_ptr<DimToValMap> bucket16 = MockBucket({{"b", 4}});
diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
index a8193dd..9fed4f8 100644
--- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -118,6 +118,10 @@
     stats.noteMetricDimensionSize(key, "metric1", 201);
     stats.noteMetricDimensionSize(key, "metric1", 202);
 
+    stats.noteAnomalyDeclared(key, "alert1");
+    stats.noteAnomalyDeclared(key, "alert1");
+    stats.noteAnomalyDeclared(key, "alert2");
+
     // broadcast-> 2
     stats.noteBroadcastSent(key);
     stats.noteBroadcastSent(key);
@@ -142,7 +146,6 @@
     EXPECT_EQ(3, configReport.dump_report_time_sec_size());
 
     EXPECT_EQ(2, configReport.matcher_stats_size());
-
     // matcher1 is the first in the list
     if (!configReport.matcher_stats(0).name().compare("matcher1")) {
         EXPECT_EQ(2, configReport.matcher_stats(0).matched_times());
@@ -157,6 +160,13 @@
         EXPECT_EQ("matcher1", configReport.matcher_stats(1).name());
     }
 
+    EXPECT_EQ(2, configReport.alert_stats_size());
+    bool alert1first = !configReport.alert_stats(0).name().compare("alert1");
+    EXPECT_EQ("alert1", configReport.alert_stats(alert1first ? 0 : 1).name());
+    EXPECT_EQ(2, configReport.alert_stats(alert1first ? 0 : 1).declared_times());
+    EXPECT_EQ("alert2", configReport.alert_stats(alert1first ? 1 : 0).name());
+    EXPECT_EQ(1, configReport.alert_stats(alert1first ? 1 : 0).declared_times());
+
     EXPECT_EQ(1, configReport.condition_stats_size());
     EXPECT_EQ("condition1", configReport.condition_stats(0).name());
     EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts());
@@ -169,6 +179,7 @@
     stats.noteMatcherMatched(key, "matcher99");
     stats.noteConditionDimensionSize(key, "condition99", 300);
     stats.noteMetricDimensionSize(key, "metric99", 270);
+    stats.noteAnomalyDeclared(key, "alert99");
 
     // now the config stats should only contain the stats about the new event.
     stats.dumpStats(&output, false);
@@ -187,6 +198,10 @@
     EXPECT_EQ(1, configReport2.metric_stats_size());
     EXPECT_EQ("metric99", configReport2.metric_stats(0).name());
     EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts());
+
+    EXPECT_EQ(1, configReport2.alert_stats_size());
+    EXPECT_EQ("alert99", configReport2.alert_stats(0).name());
+    EXPECT_EQ(1, configReport2.alert_stats(0).declared_times());
 }
 
 TEST(StatsdStatsTest, TestAtomLog) {
@@ -224,6 +239,21 @@
     EXPECT_TRUE(sensorAtomGood);
 }
 
+
+TEST(StatsdStatsTest, TestAnomalyMonitor) {
+    StatsdStats stats;
+    stats.noteRegisteredAnomalyAlarmChanged();
+    stats.noteRegisteredAnomalyAlarmChanged();
+
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+    StatsdStatsReport report;
+    bool good = report.ParseFromArray(&output[0], output.size());
+    EXPECT_TRUE(good);
+
+    EXPECT_EQ(2, report.anomaly_alarm_stats().alarms_registered());
+}
+
 TEST(StatsdStatsTest, TestTimestampThreshold) {
     StatsdStats stats;
     vector<int32_t> timestamps;
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 9b94099..d973ba1 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -196,7 +196,7 @@
     int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
     int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
 
     CountMetric metric;
     metric.set_name("1");
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index ed13db2..59475d2 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -33,6 +33,8 @@
 namespace os {
 namespace statsd {
 
+const ConfigKey kConfigKey(0, "test");
+
 TEST(GaugeMetricProducerTest, TestWithCondition) {
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
@@ -148,7 +150,7 @@
     alert.set_metric_name("1");
     alert.set_trigger_if_sum_gt(25);
     alert.set_number_of_buckets(2);
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
     gaugeProducer.addAnomalyTracker(anomalyTracker);
 
     std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 1adcc11..4e5e0d6 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -208,7 +208,7 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
     MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs,
                                bucketSizeNs, {anomalyTracker});
 
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index fa7b9a7..99d3e05 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -269,7 +269,7 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
     OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
                                  bucketSizeNs, {anomalyTracker});
 
@@ -331,7 +331,7 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
     OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true /*nesting*/,
                                  bucketStartTimeNs, bucketSizeNs, {anomalyTracker});
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 20213199..8a4d29b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2557,8 +2557,8 @@
                 + " req=" + requestCode + " res=" + resultCode + " data=" + data);
         ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
         list.add(new ResultInfo(id, requestCode, resultCode, data));
-        final ClientTransaction clientTransaction = new ClientTransaction(mAppThread, token);
-        clientTransaction.addCallback(new ActivityResultItem(list));
+        final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread, token);
+        clientTransaction.addCallback(ActivityResultItem.obtain(list));
         try {
             mAppThread.scheduleTransaction(clientTransaction);
         } catch (RemoteException e) {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 42c1347..fb9efe6 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1941,6 +1941,7 @@
         mSortKey = parcel.readString();
 
         extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
+        fixDuplicateExtras();
 
         actions = parcel.createTypedArray(Action.CREATOR); // may be null
 
@@ -2389,6 +2390,33 @@
     };
 
     /**
+     * Parcelling creates multiple copies of objects in {@code extras}. Fix them.
+     * <p>
+     * For backwards compatibility {@code extras} holds some references to "real" member data such
+     * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly
+     * fine as long as the object stays in one process.
+     * <p>
+     * However, once the notification goes into a parcel each reference gets marshalled separately,
+     * wasting memory. Especially with large images on Auto and TV, this is worth fixing.
+     */
+    private void fixDuplicateExtras() {
+        if (extras != null) {
+            fixDuplicateExtra(mSmallIcon, EXTRA_SMALL_ICON);
+            fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
+        }
+    }
+
+    /**
+     * If we find an extra that's exactly the same as one of the "real" fields but refers to a
+     * separate object, replace it with the field's version to avoid holding duplicate copies.
+     */
+    private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
+        if (original != null && extras.getParcelable(extraName) != null) {
+            extras.putParcelable(extraName, original);
+        }
+    }
+
+    /**
      * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
      * layout.
      *
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index a3fe686c..a2b7d58 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -25,17 +25,15 @@
 import android.os.Parcel;
 import android.os.Trace;
 
+import java.util.Objects;
+
 /**
  * Activity configuration changed callback.
  * @hide
  */
 public class ActivityConfigurationChangeItem extends ClientTransactionItem {
 
-    private final Configuration mConfiguration;
-
-    public ActivityConfigurationChangeItem(Configuration configuration) {
-        mConfiguration = configuration;
-    }
+    private Configuration mConfiguration;
 
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
@@ -47,6 +45,29 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private ActivityConfigurationChangeItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static ActivityConfigurationChangeItem obtain(Configuration config) {
+        ActivityConfigurationChangeItem instance =
+                ObjectPool.obtain(ActivityConfigurationChangeItem.class);
+        if (instance == null) {
+            instance = new ActivityConfigurationChangeItem();
+        }
+        instance.mConfiguration = config;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mConfiguration = null;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
@@ -80,7 +101,7 @@
             return false;
         }
         final ActivityConfigurationChangeItem other = (ActivityConfigurationChangeItem) o;
-        return mConfiguration.equals(other.mConfiguration);
+        return Objects.equals(mConfiguration, other.mConfiguration);
     }
 
     @Override
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
index 3a3d5b9..73b5ec4 100644
--- a/core/java/android/app/servertransaction/ActivityResultItem.java
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -27,6 +27,7 @@
 import android.os.Trace;
 
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Activity result delivery callback.
@@ -34,11 +35,7 @@
  */
 public class ActivityResultItem extends ClientTransactionItem {
 
-    private final List<ResultInfo> mResultInfoList;
-
-    public ActivityResultItem(List<ResultInfo> resultInfos) {
-        mResultInfoList = resultInfos;
-    }
+    private List<ResultInfo> mResultInfoList;
 
     @Override
     public int getPreExecutionState() {
@@ -54,6 +51,28 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private ActivityResultItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static ActivityResultItem obtain(List<ResultInfo> resultInfoList) {
+        ActivityResultItem instance = ObjectPool.obtain(ActivityResultItem.class);
+        if (instance == null) {
+            instance = new ActivityResultItem();
+        }
+        instance.mResultInfoList = resultInfoList;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mResultInfoList = null;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
@@ -87,7 +106,7 @@
             return false;
         }
         final ActivityResultItem other = (ActivityResultItem) o;
-        return mResultInfoList.equals(other.mResultInfoList);
+        return Objects.equals(mResultInfoList, other.mResultInfoList);
     }
 
     @Override
diff --git a/core/java/android/app/servertransaction/BaseClientRequest.java b/core/java/android/app/servertransaction/BaseClientRequest.java
index e3473bf..c91e0ca 100644
--- a/core/java/android/app/servertransaction/BaseClientRequest.java
+++ b/core/java/android/app/servertransaction/BaseClientRequest.java
@@ -24,7 +24,7 @@
  * Each of them can be prepared before scheduling and, eventually, executed.
  * @hide
  */
-public interface BaseClientRequest {
+public interface BaseClientRequest extends ObjectPoolItem {
 
     /**
      * Prepare the client request before scheduling.
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 7a58962..764ceed 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -37,7 +37,7 @@
  * @see ActivityLifecycleItem
  * @hide
  */
-public class ClientTransaction implements Parcelable {
+public class ClientTransaction implements Parcelable, ObjectPoolItem {
 
     /** A list of individual callbacks to a client. */
     private List<ClientTransactionItem> mActivityCallbacks;
@@ -54,11 +54,6 @@
     /** Target client activity. Might be null if the entire transaction is targeting an app. */
     private IBinder mActivityToken;
 
-    public ClientTransaction(IApplicationThread client, IBinder activityToken) {
-        mClient = client;
-        mActivityToken = activityToken;
-    }
-
     /**
      * Add a message to the end of the sequence of callbacks.
      * @param activityCallback A single message that can contain a lifecycle request/callback.
@@ -127,6 +122,41 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private ClientTransaction() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static ClientTransaction obtain(IApplicationThread client, IBinder activityToken) {
+        ClientTransaction instance = ObjectPool.obtain(ClientTransaction.class);
+        if (instance == null) {
+            instance = new ClientTransaction();
+        }
+        instance.mClient = client;
+        instance.mActivityToken = activityToken;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        if (mActivityCallbacks != null) {
+            int size = mActivityCallbacks.size();
+            for (int i = 0; i < size; i++) {
+                mActivityCallbacks.get(i).recycle();
+            }
+            mActivityCallbacks.clear();
+        }
+        if (mLifecycleStateRequest != null) {
+            mLifecycleStateRequest.recycle();
+            mLifecycleStateRequest = null;
+        }
+        mClient = null;
+        mActivityToken = null;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
index ee1effa..4ab7251 100644
--- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -21,17 +21,15 @@
 import android.os.IBinder;
 import android.os.Parcel;
 
+import java.util.Objects;
+
 /**
  * App configuration change message.
  * @hide
  */
 public class ConfigurationChangeItem extends ClientTransactionItem {
 
-    private final Configuration mConfiguration;
-
-    public ConfigurationChangeItem(Configuration configuration) {
-        mConfiguration = new Configuration(configuration);
-    }
+    private Configuration mConfiguration;
 
     @Override
     public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
@@ -44,6 +42,29 @@
         client.handleConfigurationChanged(mConfiguration);
     }
 
+
+    // ObjectPoolItem implementation
+
+    private ConfigurationChangeItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static ConfigurationChangeItem obtain(Configuration config) {
+        ConfigurationChangeItem instance = ObjectPool.obtain(ConfigurationChangeItem.class);
+        if (instance == null) {
+            instance = new ConfigurationChangeItem();
+        }
+        instance.mConfiguration = config;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mConfiguration = null;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
@@ -77,7 +98,7 @@
             return false;
         }
         final ConfigurationChangeItem other = (ConfigurationChangeItem) o;
-        return mConfiguration.equals(other.mConfiguration);
+        return Objects.equals(mConfiguration, other.mConfiguration);
     }
 
     @Override
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index 3012a7a..83da5f3 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -29,13 +29,8 @@
  */
 public class DestroyActivityItem extends ActivityLifecycleItem {
 
-    private final boolean mFinished;
-    private final int mConfigChanges;
-
-    public DestroyActivityItem(boolean finished, int configChanges) {
-        mFinished = finished;
-        mConfigChanges = configChanges;
-    }
+    private boolean mFinished;
+    private int mConfigChanges;
 
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
@@ -52,6 +47,30 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private DestroyActivityItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static DestroyActivityItem obtain(boolean finished, int configChanges) {
+        DestroyActivityItem instance = ObjectPool.obtain(DestroyActivityItem.class);
+        if (instance == null) {
+            instance = new DestroyActivityItem();
+        }
+        instance.mFinished = finished;
+        instance.mConfigChanges = configChanges;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mFinished = false;
+        mConfigChanges = 0;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index e39042f..7be82bf 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -45,43 +45,21 @@
  */
 public class LaunchActivityItem extends ClientTransactionItem {
 
-    private final Intent mIntent;
-    private final int mIdent;
-    private final ActivityInfo mInfo;
-    private final Configuration mCurConfig;
-    private final Configuration mOverrideConfig;
-    private final CompatibilityInfo mCompatInfo;
-    private final String mReferrer;
-    private final IVoiceInteractor mVoiceInteractor;
-    private final int mProcState;
-    private final Bundle mState;
-    private final PersistableBundle mPersistentState;
-    private final List<ResultInfo> mPendingResults;
-    private final List<ReferrerIntent> mPendingNewIntents;
-    private final boolean mIsForward;
-    private final ProfilerInfo mProfilerInfo;
-
-    public LaunchActivityItem(Intent intent, int ident, ActivityInfo info,
-            Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo,
-            String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
-            PersistableBundle persistentState, List<ResultInfo> pendingResults,
-            List<ReferrerIntent> pendingNewIntents, boolean isForward, ProfilerInfo profilerInfo) {
-        mIntent = intent;
-        mIdent = ident;
-        mInfo = info;
-        mCurConfig = curConfig;
-        mOverrideConfig = overrideConfig;
-        mCompatInfo = compatInfo;
-        mReferrer = referrer;
-        mVoiceInteractor = voiceInteractor;
-        mProcState = procState;
-        mState = state;
-        mPersistentState = persistentState;
-        mPendingResults = pendingResults;
-        mPendingNewIntents = pendingNewIntents;
-        mIsForward = isForward;
-        mProfilerInfo = profilerInfo;
-    }
+    private Intent mIntent;
+    private int mIdent;
+    private ActivityInfo mInfo;
+    private Configuration mCurConfig;
+    private Configuration mOverrideConfig;
+    private CompatibilityInfo mCompatInfo;
+    private String mReferrer;
+    private IVoiceInteractor mVoiceInteractor;
+    private int mProcState;
+    private Bundle mState;
+    private PersistableBundle mPersistentState;
+    private List<ResultInfo> mPendingResults;
+    private List<ReferrerIntent> mPendingNewIntents;
+    private boolean mIsForward;
+    private ProfilerInfo mProfilerInfo;
 
     @Override
     public void preExecute(ClientTransactionHandler client, IBinder token) {
@@ -102,6 +80,35 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private LaunchActivityItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static LaunchActivityItem obtain(Intent intent, int ident, ActivityInfo info,
+            Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo,
+            String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
+            PersistableBundle persistentState, List<ResultInfo> pendingResults,
+            List<ReferrerIntent> pendingNewIntents, boolean isForward, ProfilerInfo profilerInfo) {
+        LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
+        if (instance == null) {
+            instance = new LaunchActivityItem();
+        }
+        setValues(instance, intent, ident, info, curConfig, overrideConfig, compatInfo, referrer,
+                voiceInteractor, procState, state, persistentState, pendingResults,
+                pendingNewIntents, isForward, profilerInfo);
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null,
+                false, null);
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write from Parcel. */
@@ -114,7 +121,7 @@
         dest.writeTypedObject(mOverrideConfig, flags);
         dest.writeTypedObject(mCompatInfo, flags);
         dest.writeString(mReferrer);
-        dest.writeStrongBinder(mVoiceInteractor != null ? mVoiceInteractor.asBinder() : null);
+        dest.writeStrongInterface(mVoiceInteractor);
         dest.writeInt(mProcState);
         dest.writeBundle(mState);
         dest.writePersistableBundle(mPersistentState);
@@ -126,21 +133,16 @@
 
     /** Read from Parcel. */
     private LaunchActivityItem(Parcel in) {
-        mIntent = in.readTypedObject(Intent.CREATOR);
-        mIdent = in.readInt();
-        mInfo = in.readTypedObject(ActivityInfo.CREATOR);
-        mCurConfig = in.readTypedObject(Configuration.CREATOR);
-        mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
-        mCompatInfo = in.readTypedObject(CompatibilityInfo.CREATOR);
-        mReferrer = in.readString();
-        mVoiceInteractor = (IVoiceInteractor) in.readStrongBinder();
-        mProcState = in.readInt();
-        mState = in.readBundle(getClass().getClassLoader());
-        mPersistentState = in.readPersistableBundle(getClass().getClassLoader());
-        mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR);
-        mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
-        mIsForward = in.readBoolean();
-        mProfilerInfo = in.readTypedObject(ProfilerInfo.CREATOR);
+        setValues(this, in.readTypedObject(Intent.CREATOR), in.readInt(),
+                in.readTypedObject(ActivityInfo.CREATOR), in.readTypedObject(Configuration.CREATOR),
+                in.readTypedObject(Configuration.CREATOR),
+                in.readTypedObject(CompatibilityInfo.CREATOR), in.readString(),
+                IVoiceInteractor.Stub.asInterface(in.readStrongBinder()), in.readInt(),
+                in.readBundle(getClass().getClassLoader()),
+                in.readPersistableBundle(getClass().getClassLoader()),
+                in.createTypedArrayList(ResultInfo.CREATOR),
+                in.createTypedArrayList(ReferrerIntent.CREATOR), in.readBoolean(),
+                in.readTypedObject(ProfilerInfo.CREATOR));
     }
 
     public static final Creator<LaunchActivityItem> CREATOR =
@@ -163,7 +165,9 @@
             return false;
         }
         final LaunchActivityItem other = (LaunchActivityItem) o;
-        return mIntent.filterEquals(other.mIntent) && mIdent == other.mIdent
+        final boolean intentsEqual = (mIntent == null && other.mIntent == null)
+                || (mIntent != null && mIntent.filterEquals(other.mIntent));
+        return intentsEqual && mIdent == other.mIdent
                 && activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig)
                 && Objects.equals(mOverrideConfig, other.mOverrideConfig)
                 && Objects.equals(mCompatInfo, other.mCompatInfo)
@@ -196,7 +200,11 @@
     }
 
     private boolean activityInfoEqual(ActivityInfo other) {
-        return mInfo.flags == other.flags && mInfo.maxAspectRatio == other.maxAspectRatio
+        if (mInfo == null) {
+            return other == null;
+        }
+        return other != null && mInfo.flags == other.flags
+                && mInfo.maxAspectRatio == other.maxAspectRatio
                 && Objects.equals(mInfo.launchToken, other.launchToken)
                 && Objects.equals(mInfo.getComponentName(), other.getComponentName());
     }
@@ -231,4 +239,28 @@
                 + ",pendingNewIntents=" + mPendingNewIntents + ",profilerInfo=" + mProfilerInfo
                 + "}";
     }
+
+    // Using the same method to set and clear values to make sure we don't forget anything
+    private static void setValues(LaunchActivityItem instance, Intent intent, int ident,
+            ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
+            CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
+            int procState, Bundle state, PersistableBundle persistentState,
+            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+            boolean isForward, ProfilerInfo profilerInfo) {
+        instance.mIntent = intent;
+        instance.mIdent = ident;
+        instance.mInfo = info;
+        instance.mCurConfig = curConfig;
+        instance.mOverrideConfig = overrideConfig;
+        instance.mCompatInfo = compatInfo;
+        instance.mReferrer = referrer;
+        instance.mVoiceInteractor = voiceInteractor;
+        instance.mProcState = procState;
+        instance.mState = state;
+        instance.mPersistentState = persistentState;
+        instance.mPendingResults = pendingResults;
+        instance.mPendingNewIntents = pendingNewIntents;
+        instance.mIsForward = isForward;
+        instance.mProfilerInfo = profilerInfo;
+    }
 }
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index ee87261..b3dddfb3 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -24,19 +24,16 @@
 import android.os.Parcel;
 import android.os.Trace;
 
+import java.util.Objects;
+
 /**
  * Activity move to a different display message.
  * @hide
  */
 public class MoveToDisplayItem extends ClientTransactionItem {
 
-    private final int mTargetDisplayId;
-    private final Configuration mConfiguration;
-
-    public MoveToDisplayItem(int targetDisplayId, Configuration configuration) {
-        mTargetDisplayId = targetDisplayId;
-        mConfiguration = configuration;
-    }
+    private int mTargetDisplayId;
+    private Configuration mConfiguration;
 
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
@@ -47,6 +44,30 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private MoveToDisplayItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static MoveToDisplayItem obtain(int targetDisplayId, Configuration configuration) {
+        MoveToDisplayItem instance = ObjectPool.obtain(MoveToDisplayItem.class);
+        if (instance == null) {
+            instance = new MoveToDisplayItem();
+        }
+        instance.mTargetDisplayId = targetDisplayId;
+        instance.mConfiguration = configuration;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mTargetDisplayId = 0;
+        mConfiguration = null;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
@@ -82,7 +103,7 @@
         }
         final MoveToDisplayItem other = (MoveToDisplayItem) o;
         return mTargetDisplayId == other.mTargetDisplayId
-                && mConfiguration.equals(other.mConfiguration);
+                && Objects.equals(mConfiguration, other.mConfiguration);
     }
 
     @Override
diff --git a/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java b/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java
index 04ddb5e..c3022d6 100644
--- a/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java
+++ b/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java
@@ -21,6 +21,8 @@
 import android.os.IBinder;
 import android.os.Parcel;
 
+import java.util.Objects;
+
 /**
  * Multi-window mode change message.
  * @hide
@@ -29,14 +31,8 @@
 // communicate multi-window mode change with WindowConfiguration.
 public class MultiWindowModeChangeItem extends ClientTransactionItem {
 
-    private final boolean mIsInMultiWindowMode;
-    private final Configuration mOverrideConfig;
-
-    public MultiWindowModeChangeItem(boolean isInMultiWindowMode,
-            Configuration overrideConfig) {
-        mIsInMultiWindowMode = isInMultiWindowMode;
-        mOverrideConfig = overrideConfig;
-    }
+    private boolean mIsInMultiWindowMode;
+    private Configuration mOverrideConfig;
 
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
@@ -45,6 +41,31 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private MultiWindowModeChangeItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static MultiWindowModeChangeItem obtain(boolean isInMultiWindowMode,
+            Configuration overrideConfig) {
+        MultiWindowModeChangeItem instance = ObjectPool.obtain(MultiWindowModeChangeItem.class);
+        if (instance == null) {
+            instance = new MultiWindowModeChangeItem();
+        }
+        instance.mIsInMultiWindowMode = isInMultiWindowMode;
+        instance.mOverrideConfig = overrideConfig;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mIsInMultiWindowMode = false;
+        mOverrideConfig = null;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
@@ -81,7 +102,7 @@
         }
         final MultiWindowModeChangeItem other = (MultiWindowModeChangeItem) o;
         return mIsInMultiWindowMode == other.mIsInMultiWindowMode
-                && mOverrideConfig.equals(other.mOverrideConfig);
+                && Objects.equals(mOverrideConfig, other.mOverrideConfig);
     }
 
     @Override
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
index d01b455..7dfde73 100644
--- a/core/java/android/app/servertransaction/NewIntentItem.java
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -25,6 +25,7 @@
 import com.android.internal.content.ReferrerIntent;
 
 import java.util.List;
+import java.util.Objects;
 
 /**
  * New intent message.
@@ -32,13 +33,8 @@
  */
 public class NewIntentItem extends ClientTransactionItem {
 
-    private final List<ReferrerIntent> mIntents;
-    private final boolean mPause;
-
-    public NewIntentItem(List<ReferrerIntent> intents, boolean pause) {
-        mIntents = intents;
-        mPause = pause;
-    }
+    private List<ReferrerIntent> mIntents;
+    private boolean mPause;
 
     // TODO(lifecycler): Switch new intent handling to this scheme.
     /*@Override
@@ -60,6 +56,30 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private NewIntentItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static NewIntentItem obtain(List<ReferrerIntent> intents, boolean pause) {
+        NewIntentItem instance = ObjectPool.obtain(NewIntentItem.class);
+        if (instance == null) {
+            instance = new NewIntentItem();
+        }
+        instance.mIntents = intents;
+        instance.mPause = pause;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mIntents = null;
+        mPause = false;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
@@ -95,7 +115,7 @@
             return false;
         }
         final NewIntentItem other = (NewIntentItem) o;
-        return mPause == other.mPause && mIntents.equals(other.mIntents);
+        return mPause == other.mPause && Objects.equals(mIntents, other.mIntents);
     }
 
     @Override
diff --git a/core/java/android/app/servertransaction/ObjectPool.java b/core/java/android/app/servertransaction/ObjectPool.java
new file mode 100644
index 0000000..9812125
--- /dev/null
+++ b/core/java/android/app/servertransaction/ObjectPool.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 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 android.app.servertransaction;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * An object pool that can provide reused objects if available.
+ * @hide
+ */
+class ObjectPool {
+
+    private static final Object sPoolSync = new Object();
+    private static final Map<Class, LinkedList<? extends ObjectPoolItem>> sPoolMap =
+            new HashMap<>();
+
+    private static final int MAX_POOL_SIZE = 50;
+
+    /**
+     * Obtain an instance of a specific class from the pool
+     * @param itemClass The class of the object we're looking for.
+     * @return An instance or null if there is none.
+     */
+    public static <T extends ObjectPoolItem> T obtain(Class<T> itemClass) {
+        synchronized (sPoolSync) {
+            @SuppressWarnings("unchecked")
+            LinkedList<T> itemPool = (LinkedList<T>) sPoolMap.get(itemClass);
+            if (itemPool != null && !itemPool.isEmpty()) {
+                return itemPool.poll();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Recycle the object to the pool. The object should be properly cleared before this.
+     * @param item The object to recycle.
+     * @see ObjectPoolItem#recycle()
+     */
+    public static <T extends ObjectPoolItem> void recycle(T item) {
+        synchronized (sPoolSync) {
+            @SuppressWarnings("unchecked")
+            LinkedList<T> itemPool = (LinkedList<T>) sPoolMap.get(item.getClass());
+            if (itemPool == null) {
+                itemPool = new LinkedList<>();
+                sPoolMap.put(item.getClass(), itemPool);
+            }
+            if (itemPool.contains(item)) {
+                throw new IllegalStateException("Trying to recycle already recycled item");
+            }
+
+            if (itemPool.size() < MAX_POOL_SIZE) {
+                itemPool.add(item);
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/servertransaction/ObjectPoolItem.java b/core/java/android/app/servertransaction/ObjectPoolItem.java
new file mode 100644
index 0000000..17bd4f3
--- /dev/null
+++ b/core/java/android/app/servertransaction/ObjectPoolItem.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 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 android.app.servertransaction;
+
+/**
+ * Base interface for all lifecycle items that can be put in object pool.
+ * @hide
+ */
+public interface ObjectPoolItem {
+    /**
+     * Clear the contents of the item and putting it to a pool. The implementation should call
+     * {@link ObjectPool#recycle(ObjectPoolItem)} passing itself.
+     */
+    void recycle();
+}
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index 634da04..880fef7 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -33,23 +33,10 @@
 
     private static final String TAG = "PauseActivityItem";
 
-    private final boolean mFinished;
-    private final boolean mUserLeaving;
-    private final int mConfigChanges;
-    private final boolean mDontReport;
-
-    public PauseActivityItem() {
-        this(false /* finished */, false /* userLeaving */, 0 /* configChanges */,
-                true /* dontReport */);
-    }
-
-    public PauseActivityItem(boolean finished, boolean userLeaving, int configChanges,
-            boolean dontReport) {
-        mFinished = finished;
-        mUserLeaving = userLeaving;
-        mConfigChanges = configChanges;
-        mDontReport = dontReport;
-    }
+    private boolean mFinished;
+    private boolean mUserLeaving;
+    private int mConfigChanges;
+    private boolean mDontReport;
 
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
@@ -79,6 +66,49 @@
         }
     }
 
+
+    // ObjectPoolItem implementation
+
+    private PauseActivityItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static PauseActivityItem obtain(boolean finished, boolean userLeaving, int configChanges,
+            boolean dontReport) {
+        PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class);
+        if (instance == null) {
+            instance = new PauseActivityItem();
+        }
+        instance.mFinished = finished;
+        instance.mUserLeaving = userLeaving;
+        instance.mConfigChanges = configChanges;
+        instance.mDontReport = dontReport;
+
+        return instance;
+    }
+
+    /** Obtain an instance initialized with default params. */
+    public static PauseActivityItem obtain() {
+        PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class);
+        if (instance == null) {
+            instance = new PauseActivityItem();
+        }
+        instance.mFinished = false;
+        instance.mUserLeaving = false;
+        instance.mConfigChanges = 0;
+        instance.mDontReport = true;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mFinished = false;
+        mUserLeaving = false;
+        mConfigChanges = 0;
+        mDontReport = false;
+        ObjectPool.recycle(this);
+    }
+
     // Parcelable implementation
 
     /** Write to Parcel. */
diff --git a/core/java/android/app/servertransaction/PipModeChangeItem.java b/core/java/android/app/servertransaction/PipModeChangeItem.java
index 7c74e6f..b999cd7 100644
--- a/core/java/android/app/servertransaction/PipModeChangeItem.java
+++ b/core/java/android/app/servertransaction/PipModeChangeItem.java
@@ -21,6 +21,8 @@
 import android.os.IBinder;
 import android.os.Parcel;
 
+import java.util.Objects;
+
 /**
  * Picture in picture mode change message.
  * @hide
@@ -29,13 +31,8 @@
 // communicate multi-window mode change with WindowConfiguration.
 public class PipModeChangeItem extends ClientTransactionItem {
 
-    private final boolean mIsInPipMode;
-    private final Configuration mOverrideConfig;
-
-    public PipModeChangeItem(boolean isInPipMode, Configuration overrideConfig) {
-        mIsInPipMode = isInPipMode;
-        mOverrideConfig = overrideConfig;
-    }
+    private boolean mIsInPipMode;
+    private Configuration mOverrideConfig;
 
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
@@ -44,6 +41,30 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private PipModeChangeItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static PipModeChangeItem obtain(boolean isInPipMode, Configuration overrideConfig) {
+        PipModeChangeItem instance = ObjectPool.obtain(PipModeChangeItem.class);
+        if (instance == null) {
+            instance = new PipModeChangeItem();
+        }
+        instance.mIsInPipMode = isInPipMode;
+        instance.mOverrideConfig = overrideConfig;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mIsInPipMode = false;
+        mOverrideConfig = null;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
@@ -78,7 +99,8 @@
             return false;
         }
         final PipModeChangeItem other = (PipModeChangeItem) o;
-        return mIsInPipMode == other.mIsInPipMode && mOverrideConfig.equals(other.mOverrideConfig);
+        return mIsInPipMode == other.mIsInPipMode
+                && Objects.equals(mOverrideConfig, other.mOverrideConfig);
     }
 
     @Override
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index d659b80..9249c6e 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -33,21 +33,9 @@
 
     private static final String TAG = "ResumeActivityItem";
 
-    private final int mProcState;
-    private final boolean mUpdateProcState;
-    private final boolean mIsForward;
-
-    public ResumeActivityItem(boolean isForward) {
-        mProcState = ActivityManager.PROCESS_STATE_UNKNOWN;
-        mUpdateProcState = false;
-        mIsForward = isForward;
-    }
-
-    public ResumeActivityItem(int procState, boolean isForward) {
-        mProcState = procState;
-        mUpdateProcState = true;
-        mIsForward = isForward;
-    }
+    private int mProcState;
+    private boolean mUpdateProcState;
+    private boolean mIsForward;
 
     @Override
     public void preExecute(ClientTransactionHandler client, IBinder token) {
@@ -81,6 +69,45 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private ResumeActivityItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static ResumeActivityItem obtain(int procState, boolean isForward) {
+        ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class);
+        if (instance == null) {
+            instance = new ResumeActivityItem();
+        }
+        instance.mProcState = procState;
+        instance.mUpdateProcState = true;
+        instance.mIsForward = isForward;
+
+        return instance;
+    }
+
+    /** Obtain an instance initialized with provided params. */
+    public static ResumeActivityItem obtain(boolean isForward) {
+        ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class);
+        if (instance == null) {
+            instance = new ResumeActivityItem();
+        }
+        instance.mProcState = ActivityManager.PROCESS_STATE_UNKNOWN;
+        instance.mUpdateProcState = false;
+        instance.mIsForward = isForward;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mProcState = ActivityManager.PROCESS_STATE_UNKNOWN;
+        mUpdateProcState = false;
+        mIsForward = false;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index 6e49386..5c5c304 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -31,13 +31,8 @@
 
     private static final String TAG = "StopActivityItem";
 
-    private final boolean mShowWindow;
-    private final int mConfigChanges;
-
-    public StopActivityItem(boolean showWindow, int configChanges) {
-        mShowWindow = showWindow;
-        mConfigChanges = configChanges;
-    }
+    private boolean mShowWindow;
+    private int mConfigChanges;
 
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
@@ -59,6 +54,30 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private StopActivityItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static StopActivityItem obtain(boolean showWindow, int configChanges) {
+        StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class);
+        if (instance == null) {
+            instance = new StopActivityItem();
+        }
+        instance.mShowWindow = showWindow;
+        instance.mConfigChanges = configChanges;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mShowWindow = false;
+        mConfigChanges = 0;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index bd24824..5b0ea6b 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -243,8 +243,6 @@
     }
 
     private static void log(String message) {
-        if (DEBUG_RESOLVER) {
-            Slog.d(TAG, message);
-        }
+        if (DEBUG_RESOLVER) Slog.d(TAG, message);
     }
 }
diff --git a/core/java/android/app/servertransaction/WindowVisibilityItem.java b/core/java/android/app/servertransaction/WindowVisibilityItem.java
index 6fcdcfa..d9956b1 100644
--- a/core/java/android/app/servertransaction/WindowVisibilityItem.java
+++ b/core/java/android/app/servertransaction/WindowVisibilityItem.java
@@ -29,11 +29,7 @@
  */
 public class WindowVisibilityItem extends ClientTransactionItem {
 
-    private final boolean mShowWindow;
-
-    public WindowVisibilityItem(boolean showWindow) {
-        mShowWindow = showWindow;
-    }
+    private boolean mShowWindow;
 
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
@@ -44,6 +40,28 @@
     }
 
 
+    // ObjectPoolItem implementation
+
+    private WindowVisibilityItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static WindowVisibilityItem obtain(boolean showWindow) {
+        WindowVisibilityItem instance = ObjectPool.obtain(WindowVisibilityItem.class);
+        if (instance == null) {
+            instance = new WindowVisibilityItem();
+        }
+        instance.mShowWindow = showWindow;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mShowWindow = false;
+        ObjectPool.recycle(this);
+    }
+
+
     // Parcelable implementation
 
     /** Write to Parcel. */
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 1241f23..838d315 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -678,33 +678,6 @@
     }
 
     /**
-     * Get battery usage hint for Bluetooth Headset service.
-     * This is a monotonically increasing integer. Wraps to 0 at
-     * Integer.MAX_INT, and at boot.
-     * Current implementation returns the number of AT commands handled since
-     * boot. This is a good indicator for spammy headset/handsfree units that
-     * can keep the device awake by polling for cellular status updates. As a
-     * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
-     *
-     * @param device the bluetooth headset.
-     * @return monotonically increasing battery usage hint, or a negative error code on error
-     * @hide
-     */
-    public int getBatteryUsageHint(BluetoothDevice device) {
-        if (VDBG) log("getBatteryUsageHint()");
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getBatteryUsageHint(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return -1;
-    }
-
-    /**
      * Indicates if current platform supports voice dialing over bluetooth SCO.
      *
      * @return true if voice dialing over bluetooth is supported, false otherwise.
@@ -716,49 +689,6 @@
     }
 
     /**
-     * Accept the incoming connection.
-     * Note: This is an internal function and shouldn't be exposed
-     *
-     * @hide
-     */
-    public boolean acceptIncomingConnect(BluetoothDevice device) {
-        if (DBG) log("acceptIncomingConnect");
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.acceptIncomingConnect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return false;
-    }
-
-    /**
-     * Reject the incoming connection.
-     *
-     * @hide
-     */
-    public boolean rejectIncomingConnect(BluetoothDevice device) {
-        if (DBG) log("rejectIncomingConnect");
-        final IBluetoothHeadset service = mService;
-        if (service != null) {
-            try {
-                return service.rejectIncomingConnect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return false;
-    }
-
-    /**
      * Get the current audio state of the Headset.
      * Note: This is an internal function and shouldn't be exposed
      *
@@ -1053,50 +983,6 @@
     }
 
     /**
-     * enable WBS codec setting.
-     *
-     * @return true if successful false if there was some error such as there is no connected
-     * headset
-     * @hide
-     */
-    public boolean enableWBS() {
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.enableWBS();
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return false;
-    }
-
-    /**
-     * disable WBS codec settting. It set NBS codec.
-     *
-     * @return true if successful false if there was some error such as there is no connected
-     * headset
-     * @hide
-     */
-    public boolean disableWBS() {
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.disableWBS();
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return false;
-    }
-
-    /**
      * check if in-band ringing is supported for this platform.
      *
      * @return true if in-band ringing is supported false if in-band ringing is not supported
@@ -1107,30 +993,6 @@
                 com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support);
     }
 
-    /**
-     * Send Headset the BIND response from AG to report change in the status of the
-     * HF indicators to the headset
-     *
-     * @param indId Assigned Number of the indicator (defined by SIG)
-     * @param indStatus possible values- false-Indicator is disabled, no value changes shall be
-     * sent for this indicator true-Indicator is enabled, value changes may be sent for this
-     * indicator
-     * @hide
-     */
-    public void bindResponse(int indId, boolean indStatus) {
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                service.bindResponse(indId, indStatus);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-    }
-
     private final IBluetoothProfileServiceConnection mConnection =
             new IBluetoothProfileServiceConnection.Stub() {
         @Override
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index bfee2797..4a00486 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -31,36 +31,33 @@
 import java.util.List;
 
 /**
- * Provides the public APIs to control the Bluetooth HID Device
- * profile.
+ * Provides the public APIs to control the Bluetooth HID Device profile.
  *
- * BluetoothHidDevice is a proxy object for controlling the Bluetooth HID
- * Device Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
- * the BluetoothHidDevice proxy object.
+ * <p>BluetoothHidDevice is a proxy object for controlling the Bluetooth HID Device Service via IPC.
+ * Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHidDevice proxy object.
  *
- * {@hide}
+ * <p>{@hide}
  */
 public final class BluetoothHidDevice implements BluetoothProfile {
 
     private static final String TAG = BluetoothHidDevice.class.getSimpleName();
 
     /**
-     * Intent used to broadcast the change in connection state of the Input
-     * Host profile.
+     * Intent used to broadcast the change in connection state of the Input Host profile.
      *
      * <p>This intent will have 3 extras:
+     *
      * <ul>
-     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
-     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
-     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     *   <li>{@link #EXTRA_STATE} - The current state of the profile.
+     *   <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
+     *   <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
      * </ul>
      *
-     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
-     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
-     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
+     * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
+     * #STATE_DISCONNECTING}.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
@@ -69,9 +66,8 @@
     /**
      * Constants representing device subclass.
      *
-     * @see #registerApp
-     * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
-     * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)
      */
     public static final byte SUBCLASS1_NONE = (byte) 0x00;
     public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
@@ -110,8 +106,8 @@
     public static final byte ERROR_RSP_UNKNOWN = (byte) 14;
 
     /**
-     * Constants representing protocol mode used set by host. Default is always
-     * {@link #PROTOCOL_REPORT_MODE} unless notified otherwise.
+     * Constants representing protocol mode used set by host. Default is always {@link
+     * #PROTOCOL_REPORT_MODE} unless notified otherwise.
      *
      * @see BluetoothHidDeviceCallback#onSetProtocol(BluetoothDevice, byte)
      */
@@ -126,8 +122,8 @@
 
     private BluetoothAdapter mAdapter;
 
-    private static class BluetoothHidDeviceCallbackWrapper extends
-            IBluetoothHidDeviceCallback.Stub {
+    private static class BluetoothHidDeviceCallbackWrapper
+            extends IBluetoothHidDeviceCallback.Stub {
 
         private BluetoothHidDeviceCallback mCallback;
 
@@ -184,13 +180,11 @@
                                     doBind();
                                 }
                             } catch (IllegalStateException e) {
-                                Log.e(TAG,
-                                        "onBluetoothStateChange: could not bind to HID Dev "
-                                                + "service: ", e);
+                                Log.e(TAG, "onBluetoothStateChange: could not bind to HID Dev "
+                                        + "service: ", e);
                             } catch (SecurityException e) {
-                                Log.e(TAG,
-                                        "onBluetoothStateChange: could not bind to HID Dev "
-                                                + "service: ", e);
+                                Log.e(TAG, "onBluetoothStateChange: could not bind to HID Dev "
+                                        + "service: ", e);
                             }
                         } else {
                             Log.d(TAG, "Unbinding service...");
@@ -200,23 +194,25 @@
                 }
             };
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            Log.d(TAG, "onServiceConnected()");
-            mService = IBluetoothHidDevice.Stub.asInterface(service);
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.HID_DEVICE,
-                        BluetoothHidDevice.this);
-            }
-        }
-        public void onServiceDisconnected(ComponentName className) {
-            Log.d(TAG, "onServiceDisconnected()");
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.HID_DEVICE);
-            }
-        }
-    };
+    private final ServiceConnection mConnection =
+            new ServiceConnection() {
+                public void onServiceConnected(ComponentName className, IBinder service) {
+                    Log.d(TAG, "onServiceConnected()");
+                    mService = IBluetoothHidDevice.Stub.asInterface(service);
+                    if (mServiceListener != null) {
+                        mServiceListener.onServiceConnected(
+                                BluetoothProfile.HID_DEVICE, BluetoothHidDevice.this);
+                    }
+                }
+
+                public void onServiceDisconnected(ComponentName className) {
+                    Log.d(TAG, "onServiceDisconnected()");
+                    mService = null;
+                    if (mServiceListener != null) {
+                        mServiceListener.onServiceDisconnected(BluetoothProfile.HID_DEVICE);
+                    }
+                }
+            };
 
     BluetoothHidDevice(Context context, ServiceListener listener) {
         Log.v(TAG, "BluetoothHidDevice");
@@ -280,9 +276,7 @@
         mServiceListener = null;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
         Log.v(TAG, "getConnectedDevices()");
@@ -301,9 +295,7 @@
         return new ArrayList<BluetoothDevice>();
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
@@ -322,9 +314,7 @@
         return new ArrayList<BluetoothDevice>();
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     @Override
     public int getConnectionState(BluetoothDevice device) {
         Log.v(TAG, "getConnectionState(): device=" + device);
@@ -344,33 +334,31 @@
     }
 
     /**
-     * Registers application to be used for HID device. Connections to HID
-     * Device are only possible when application is registered. Only one
-     * application can be registered at time. When no longer used, application
-     * should be unregistered using
-     * {@link #unregisterApp()}.
-     * The registration status should be tracked by the application by handling callback from
-     * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related
-     * to the return value of this method.
+     * Registers application to be used for HID device. Connections to HID Device are only possible
+     * when application is registered. Only one application can be registered at one time. When an
+     * application is registered, the HID Host service will be disabled until it is unregistered.
+     * When no longer used, application should be unregistered using {@link #unregisterApp()}. The
+     * registration status should be tracked by the application by handling callback from
+     * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related to
+     * the return value of this method.
      *
-     * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record.
-     * The HID Device SDP record is required.
-     * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings.
-     * The Incoming QoS Settings is not required. Use null or default
-     * BluetoothHidDeviceAppQosSettings.Builder for default values.
-     * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings.
-     * The Outgoing QoS Settings is not required. Use null or default
-     * BluetoothHidDeviceAppQosSettings.Builder for default values.
+     * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record. The HID
+     *     Device SDP record is required.
+     * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings. The
+     *     Incoming QoS Settings is not required. Use null or default
+     *     BluetoothHidDeviceAppQosSettings.Builder for default values.
+     * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings. The
+     *     Outgoing QoS Settings is not required. Use null or default
+     *     BluetoothHidDeviceAppQosSettings.Builder for default values.
      * @param callback {@link BluetoothHidDeviceCallback} object to which callback messages will be
-     * sent.
-     * The BluetoothHidDeviceCallback object is required.
+     *     sent. The BluetoothHidDeviceCallback object is required.
      * @return true if the command is successfully sent; otherwise false.
      */
     public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
             BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
             BluetoothHidDeviceCallback callback) {
         Log.v(TAG, "registerApp(): sdp=" + sdp + " inQos=" + inQos + " outQos=" + outQos
-                + " callback=" + callback);
+                        + " callback=" + callback);
 
         boolean result = false;
 
@@ -395,14 +383,13 @@
     }
 
     /**
-     * Unregisters application. Active connection will be disconnected and no
-     * new connections will be allowed until registered again using
-     * {@link #registerApp
+     * Unregisters application. Active connection will be disconnected and no new connections will
+     * be allowed until registered again using {@link #registerApp
      * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
-     * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)}
-     * The registration status should be tracked by the application by handling callback from
-     * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related
-     * to the return value of this method.
+     * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)} The registration status should
+     * be tracked by the application by handling callback from
+     * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related to
+     * the return value of this method.
      *
      * @return true if the command is successfully sent; otherwise false.
      */
@@ -429,7 +416,7 @@
      * Sends report to remote host using interrupt channel.
      *
      * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in
-     * descriptor.
+     *     descriptor.
      * @param data Report data, not including Report Id.
      * @return true if the command is successfully sent; otherwise false.
      */
@@ -451,8 +438,8 @@
     }
 
     /**
-     * Sends report to remote host as reply for GET_REPORT request from
-     * {@link BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)}.
+     * Sends report to remote host as reply for GET_REPORT request from {@link
+     * BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)}.
      *
      * @param type Report Type, as in request.
      * @param id Report Id, as in request.
@@ -479,8 +466,8 @@
     }
 
     /**
-     * Sends error handshake message as reply for invalid SET_REPORT request
-     * from {@link BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
+     * Sends error handshake message as reply for invalid SET_REPORT request from {@link
+     * BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
      *
      * @param error Error to be sent for SET_REPORT via HANDSHAKE.
      * @return true if the command is successfully sent; otherwise false.
@@ -508,6 +495,7 @@
      * Sends Virtual Cable Unplug to currently connected host.
      *
      * @return
+     * {@hide}
      */
     public boolean unplug(BluetoothDevice device) {
         Log.v(TAG, "unplug(): device=" + device);
@@ -529,11 +517,11 @@
     }
 
     /**
-     * Initiates connection to host which is currently paired with this device.
-     * If the application is not registered, #connect(BluetoothDevice) will fail.
-     * The connection state should be tracked by the application by handling callback from
-     * BluetoothHidDeviceCallback#onConnectionStateChanged. The connection state is not related
-     * to the return value of this method.
+     * Initiates connection to host which is currently paired with this device. If the application
+     * is not registered, #connect(BluetoothDevice) will fail. The connection state should be
+     * tracked by the application by handling callback from
+     * BluetoothHidDeviceCallback#onConnectionStateChanged. The connection state is not related to
+     * the return value of this method.
      *
      * @return true if the command is successfully sent; otherwise false.
      */
@@ -557,10 +545,9 @@
     }
 
     /**
-     * Disconnects from currently connected host.
-     * The connection state should be tracked by the application by handling callback from
-     * BluetoothHidDeviceCallback#onConnectionStateChanged. The connection state is not related
-     * to the return value of this method.
+     * Disconnects from currently connected host. The connection state should be tracked by the
+     * application by handling callback from BluetoothHidDeviceCallback#onConnectionStateChanged.
+     * The connection state is not related to the return value of this method.
      *
      * @return true if the command is successfully sent; otherwise false.
      */
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
index 881ae98..4609d52 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
@@ -20,15 +20,14 @@
 import android.os.Parcelable;
 
 /**
- * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device
- * application.
+ * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device application.
  *
- * The BluetoothHidDevice framework will update the L2CAP QoS settings for the
- * app during registration.
+ * <p>The BluetoothHidDevice framework will update the L2CAP QoS settings for the app during
+ * registration.
  *
- * {@see BluetoothHidDevice}
+ * <p>{@see BluetoothHidDevice}
  *
- * {@hide}
+ * <p>{@hide}
  */
 public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
 
@@ -46,13 +45,12 @@
     public static final int MAX = (int) 0xffffffff;
 
     /**
-     * Create a BluetoothHidDeviceAppQosSettings object for the Bluetooth L2CAP channel.
-     * The QoS Settings is optional.
-     * Recommended to use BluetoothHidDeviceAppQosSettings.Builder.
-     * {@see <a href="https://www.bluetooth.com/specifications/profiles-overview">
-     *     https://www.bluetooth.com/specifications/profiles-overview
-     *     </a>
-     *     Bluetooth HID Specfication v1.1.1 Section 5.2 and Appendix D }
+     * Create a BluetoothHidDeviceAppQosSettings object for the Bluetooth L2CAP channel. The QoS
+     * Settings is optional. Recommended to use BluetoothHidDeviceAppQosSettings.Builder. {@see <a
+     * href="https://www.bluetooth.com/specifications/profiles-overview">
+     * https://www.bluetooth.com/specifications/profiles-overview </a> Bluetooth HID Specfication
+     * v1.1.1 Section 5.2 and Appendix D }
+     *
      * @param serviceType L2CAP service type
      * @param tokenRate L2CAP token rate
      * @param tokenBucketSize L2CAP token bucket size
@@ -123,13 +121,11 @@
     /** @return an int array representation of this instance */
     public int[] toArray() {
         return new int[] {
-                serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation
+            serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation
         };
     }
 
-    /**
-     * A helper to build the BluetoothHidDeviceAppQosSettings object.
-     */
+    /** A helper to build the BluetoothHidDeviceAppQosSettings object. */
     public static class Builder {
         // Optional parameters - initialized to default values
         private int mServiceType = SERVICE_BEST_EFFORT;
@@ -141,8 +137,9 @@
 
         /**
          * Set the service type.
+         *
          * @param val service type. Should be one of {SERVICE_NO_TRAFFIC, SERVICE_BEST_EFFORT,
-         * SERVICE_GUARANTEED}, with SERVICE_BEST_EFFORT being the default one.
+         *     SERVICE_GUARANTEED}, with SERVICE_BEST_EFFORT being the default one.
          * @return BluetoothHidDeviceAppQosSettings Builder with specified service type.
          */
         public Builder serviceType(int val) {
@@ -151,6 +148,7 @@
         }
         /**
          * Set the token rate.
+         *
          * @param val token rate
          * @return BluetoothHidDeviceAppQosSettings Builder with specified token rate.
          */
@@ -161,6 +159,7 @@
 
         /**
          * Set the bucket size.
+         *
          * @param val bucket size
          * @return BluetoothHidDeviceAppQosSettings Builder with specified bucket size.
          */
@@ -171,6 +170,7 @@
 
         /**
          * Set the peak bandwidth.
+         *
          * @param val peak bandwidth
          * @return BluetoothHidDeviceAppQosSettings Builder with specified peak bandwidth.
          */
@@ -180,6 +180,7 @@
         }
         /**
          * Set the latency.
+         *
          * @param val latency
          * @return BluetoothHidDeviceAppQosSettings Builder with specified latency.
          */
@@ -190,6 +191,7 @@
 
         /**
          * Set the delay variation.
+         *
          * @param val delay variation
          * @return BluetoothHidDeviceAppQosSettings Builder with specified delay variation.
          */
@@ -200,6 +202,7 @@
 
         /**
          * Build the BluetoothHidDeviceAppQosSettings object.
+         *
          * @return BluetoothHidDeviceAppQosSettings object with current settings.
          */
         public BluetoothHidDeviceAppQosSettings build() {
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
index 4669637..2da64e5 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -22,16 +22,14 @@
 import java.util.Arrays;
 
 /**
- * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth
- * HID Device application.
+ * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth HID Device application.
  *
- * The BluetoothHidDevice framework adds the SDP record during app
- * registration, so that the Android device can be discovered as a Bluetooth
- * HID Device.
+ * <p>The BluetoothHidDevice framework adds the SDP record during app registration, so that the
+ * Android device can be discovered as a Bluetooth HID Device.
  *
- * {@see BluetoothHidDevice}
+ * <p>{@see BluetoothHidDevice}
  *
- * {@hide}
+ * <p>{@hide}
  */
 public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
 
@@ -43,18 +41,19 @@
 
     /**
      * Create a BluetoothHidDeviceAppSdpSettings object for the Bluetooth SDP record.
+     *
      * @param name Name of this Bluetooth HID device. Maximum length is 50 bytes.
      * @param description Description for this Bluetooth HID device. Maximum length is 50 bytes.
      * @param provider Provider of this Bluetooth HID device. Maximum length is 50 bytes.
-     * @param subclass Subclass of this Bluetooth HID device.
-     * See <a href="www.usb.org/developers/hidpage/HID1_11.pdf">
+     * @param subclass Subclass of this Bluetooth HID device. See <a
+     *     href="www.usb.org/developers/hidpage/HID1_11.pdf">
      *     www.usb.org/developers/hidpage/HID1_11.pdf Section 4.2</a>
-     * @param descriptors Descriptors of this Bluetooth HID device.
-     * See <a href="www.usb.org/developers/hidpage/HID1_11.pdf">
+     * @param descriptors Descriptors of this Bluetooth HID device. See <a
+     *     href="www.usb.org/developers/hidpage/HID1_11.pdf">
      *     www.usb.org/developers/hidpage/HID1_11.pdf Chapter 6</a> Maximum length is 2048 bytes.
      */
-    public BluetoothHidDeviceAppSdpSettings(String name, String description, String provider,
-            byte subclass, byte[] descriptors) {
+    public BluetoothHidDeviceAppSdpSettings(
+            String name, String description, String provider, byte subclass, byte[] descriptors) {
         this.name = name;
         this.description = description;
         this.provider = provider;
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
index dc6f9fa..6ed1965 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -19,46 +19,43 @@
 import android.util.Log;
 
 /**
- * The template class that applications use to call callback functions on
- * events from the HID host. Callback functions are wrapped in this class and
- * registered to the Android system during app registration.
+ * The template class that applications use to call callback functions on events from the HID host.
+ * Callback functions are wrapped in this class and registered to the Android system during app
+ * registration.
  *
- * {@see BluetoothHidDevice}
+ * <p>{@see BluetoothHidDevice}
  *
- * {@hide}
+ * <p>{@hide}
  */
 public abstract class BluetoothHidDeviceCallback {
 
     private static final String TAG = "BluetoothHidDevCallback";
 
     /**
-     * Callback called when application registration state changes. Usually it's
-     * called due to either
-     * {@link BluetoothHidDevice#registerApp
-     * (String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
-     * or
-     * {@link BluetoothHidDevice#unregisterApp()}
-     * , but can be also unsolicited in case e.g. Bluetooth was turned off in
-     * which case application is unregistered automatically.
+     * Callback called when application registration state changes. Usually it's called due to
+     * either {@link BluetoothHidDevice#registerApp (String, String, String, byte, byte[],
+     * BluetoothHidDeviceCallback)} or {@link BluetoothHidDevice#unregisterApp()} , but can be also
+     * unsolicited in case e.g. Bluetooth was turned off in which case application is unregistered
+     * automatically.
      *
      * @param pluggedDevice {@link BluetoothDevice} object which represents host that currently has
-     * Virtual Cable established with device. Only valid when application is registered, can be
-     * <code>null</code>.
+     *     Virtual Cable established with device. Only valid when application is registered, can be
+     *     <code>null</code>.
      * @param registered <code>true</code> if application is registered, <code>false</code>
-     * otherwise.
+     *     otherwise.
      */
     public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
-        Log.d(TAG, "onAppStatusChanged: pluggedDevice=" + pluggedDevice + " registered="
-                + registered);
+        Log.d(TAG,
+                "onAppStatusChanged: pluggedDevice=" + pluggedDevice + " registered=" + registered);
     }
 
     /**
-     * Callback called when connection state with remote host was changed.
-     * Application can assume than Virtual Cable is established when called with
-     * {@link BluetoothProfile#STATE_CONNECTED} <code>state</code>.
+     * Callback called when connection state with remote host was changed. Application can assume
+     * than Virtual Cable is established when called with {@link BluetoothProfile#STATE_CONNECTED}
+     * <code>state</code>.
      *
      * @param device {@link BluetoothDevice} object representing host device which connection state
-     * was changed.
+     *     was changed.
      * @param state Connection state as defined in {@link BluetoothProfile}.
      */
     public void onConnectionStateChanged(BluetoothDevice device, int state) {
@@ -66,14 +63,14 @@
     }
 
     /**
-     * Callback called when GET_REPORT is received from remote host. Should be
-     * replied by application using
-     * {@link BluetoothHidDevice#replyReport(BluetoothDevice, byte, byte, byte[])}.
+     * Callback called when GET_REPORT is received from remote host. Should be replied by
+     * application using {@link BluetoothHidDevice#replyReport(BluetoothDevice, byte, byte,
+     * byte[])}.
      *
      * @param type Requested Report Type.
      * @param id Requested Report Id, can be 0 if no Report Id are defined in descriptor.
      * @param bufferSize Requested buffer size, application shall respond with at least given number
-     * of bytes.
+     *     of bytes.
      */
     public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
         Log.d(TAG, "onGetReport: device=" + device + " type=" + type + " id=" + id + " bufferSize="
@@ -81,9 +78,9 @@
     }
 
     /**
-     * Callback called when SET_REPORT is received from remote host. In case
-     * received data are invalid, application shall respond with
-     * {@link BluetoothHidDevice#reportError(BluetoothDevice, byte)}.
+     * Callback called when SET_REPORT is received from remote host. In case received data are
+     * invalid, application shall respond with {@link
+     * BluetoothHidDevice#reportError(BluetoothDevice, byte)}.
      *
      * @param type Report Type.
      * @param id Report Id.
@@ -94,10 +91,9 @@
     }
 
     /**
-     * Callback called when SET_PROTOCOL is received from remote host.
-     * Application shall use this information to send only reports valid for
-     * given protocol mode. By default,
-     * {@link BluetoothHidDevice#PROTOCOL_REPORT_MODE} shall be assumed.
+     * Callback called when SET_PROTOCOL is received from remote host. Application shall use this
+     * information to send only reports valid for given protocol mode. By default, {@link
+     * BluetoothHidDevice#PROTOCOL_REPORT_MODE} shall be assumed.
      *
      * @param protocol Protocol Mode.
      */
@@ -106,9 +102,8 @@
     }
 
     /**
-     * Callback called when report data is received over interrupt channel.
-     * Report Type is assumed to be
-     * {@link BluetoothHidDevice#REPORT_TYPE_OUTPUT}.
+     * Callback called when report data is received over interrupt channel. Report Type is assumed
+     * to be {@link BluetoothHidDevice#REPORT_TYPE_OUTPUT}.
      *
      * @param reportId Report Id.
      * @param data Report data.
@@ -118,10 +113,8 @@
     }
 
     /**
-     * Callback called when Virtual Cable is removed. This can be either due to
-     * {@link BluetoothHidDevice#unplug(BluetoothDevice)} or request from remote
-     * side. After this callback is received connection will be disconnected
-     * automatically.
+     * Callback called when Virtual Cable is removed. After this callback is
+     * received connection will be disconnected automatically.
      */
     public void onVirtualCableUnplug(BluetoothDevice device) {
         Log.d(TAG, "onVirtualCableUnplug: device=" + device);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 9e54e23..d09ba0b 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -265,6 +265,14 @@
 
         /**
          * Include pinned shortcuts in the result.
+         *
+         * <p>If you are the selected assistant app, and wishes to fetch all shortcuts that the
+         * user owns on the launcher (or by other launchers, in case the user has multiple), use
+         * {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} instead.
+         *
+         * <p>If you're a regular launcher app, there's no way to get shortcuts pinned by other
+         * launchers, and {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} will be ignored. So use this
+         * flag to get own pinned shortcuts.
          */
         public static final int FLAG_MATCH_PINNED = 1 << 1;
 
@@ -285,8 +293,15 @@
          * Include all pinned shortcuts by any launchers, not just by the caller,
          * in the result.
          *
-         * The caller must be the selected assistant app to use this flag, or have the system
+         * <p>The caller must be the selected assistant app to use this flag, or have the system
          * {@code ACCESS_SHORTCUTS} permission.
+         *
+         * <p>If you are the selected assistant app, and wishes to fetch all shortcuts that the
+         * user owns on the launcher (or by other launchers, in case the user has multiple), use
+         * {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} instead.
+         *
+         * <p>If you're a regular launcher app (or any app that's not the selected assistant app)
+         * then this flag will be ignored.
          */
         public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1 << 10;
 
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index dadfaa9..e6f682d 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -81,4 +81,7 @@
             @Nullable IntentSender resultIntent, int userId);
 
     public abstract boolean isRequestPinItemSupported(int callingUserId, int requestType);
+
+    public abstract boolean isForegroundDefaultLauncher(@NonNull String callingPackage,
+            int callingUid);
 }
diff --git a/core/java/android/content/res/XmlResourceParser.java b/core/java/android/content/res/XmlResourceParser.java
index 5af49d4..6be9b9e 100644
--- a/core/java/android/content/res/XmlResourceParser.java
+++ b/core/java/android/content/res/XmlResourceParser.java
@@ -16,20 +16,19 @@
 
 package android.content.res;
 
-import org.xmlpull.v1.XmlPullParser;
-
 import android.util.AttributeSet;
 
+import org.xmlpull.v1.XmlPullParser;
+
 /**
  * The XML parsing interface returned for an XML resource.  This is a standard
- * XmlPullParser interface, as well as an extended AttributeSet interface and
- * an additional close() method on this interface for the client to indicate
- * when it is done reading the resource.
+ * {@link XmlPullParser} interface but also extends {@link AttributeSet} and
+ * adds an additional {@link #close()} method for the client to indicate when
+ * it is done reading the resource.
  */
 public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable {
     /**
-     * Close this interface to the resource.  Calls on the interface are no
-     * longer value after this call.
+     * Close this parser. Calls on the interface are no longer valid after this call.
      */
     public void close();
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1d4477a..6b1632a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10164,6 +10164,16 @@
         public static final String POLICY_CONTROL = "policy_control";
 
         /**
+         * {@link android.view.DisplayCutout DisplayCutout} emulation mode.
+         *
+         * @hide
+         */
+        public static final String EMULATE_DISPLAY_CUTOUT = "emulate_display_cutout";
+
+        /** @hide */ public static final int EMULATE_DISPLAY_CUTOUT_OFF = 0;
+        /** @hide */ public static final int EMULATE_DISPLAY_CUTOUT_ON = 1;
+
+        /**
          * Defines global zen mode.  ZEN_MODE_OFF, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
          * or ZEN_MODE_NO_INTERRUPTIONS.
          *
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 29baea1..80785fd 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -21,6 +21,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 
+import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -33,6 +34,17 @@
     public static final String FFLAG_PREFIX = "sys.fflag.";
     public static final String FFLAG_OVERRIDE_PREFIX = FFLAG_PREFIX + "override.";
 
+    private static final Map<String, String> DEFAULT_FLAGS;
+    static {
+        DEFAULT_FLAGS = new HashMap<>();
+        DEFAULT_FLAGS.put("device_info_v2", "true");
+        DEFAULT_FLAGS.put("new_settings_suggestion", "true");
+        DEFAULT_FLAGS.put("settings_search_v2", "false");
+        DEFAULT_FLAGS.put("settings_app_info_v2", "false");
+        DEFAULT_FLAGS.put("settings_connected_device_v2", "false");
+        DEFAULT_FLAGS.put("settings_battery_v2", "false");
+    }
+
     /**
      * Whether or not a flag is enabled.
      *
@@ -41,7 +53,7 @@
      */
     public static boolean isEnabled(Context context, String feature) {
         // Override precedence:
-        // Settings.Global -> sys.fflag.override.* -> sys.fflag.*
+        // Settings.Global -> sys.fflag.override.* -> static list
 
         // Step 1: check if feature flag is set in Settings.Global.
         String value;
@@ -57,8 +69,8 @@
         if (!TextUtils.isEmpty(value)) {
             return Boolean.parseBoolean(value);
         }
-        // Step 3: check if feature flag has any default value. Flag name: sys.fflag.<feature>
-        value = SystemProperties.get(FFLAG_PREFIX + feature);
+        // Step 3: check if feature flag has any default value.
+        value = getAllFeatureFlags().get(feature);
         return Boolean.parseBoolean(value);
     }
 
@@ -73,6 +85,6 @@
      * Returns all feature flags in their raw form.
      */
     public static Map<String, String> getAllFeatureFlags() {
-        return null;
+        return DEFAULT_FLAGS;
     }
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 905c071..7c2c12f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1268,6 +1268,35 @@
         }, formatToHexString = true)
         public int flags;
 
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(
+            flag = true,
+            value = {
+                    LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA,
+            })
+        @interface Flags2 {}
+
+        /**
+         * Window flag: allow placing the window within the area that overlaps with the
+         * display cutout.
+         *
+         * <p>
+         * The window must correctly position its contents to take the display cutout into account.
+         *
+         * @see DisplayCutout
+         * @hide for now
+         */
+        public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001;
+
+        /**
+         * Various behavioral options/flags.  Default is none.
+         *
+         * @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA
+         * @hide for now
+         */
+        @Flags2 public long flags2;
+
         /**
          * If the window has requested hardware acceleration, but this is not
          * allowed in the process it is in, then still render it as if it is
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index 26023b4..e013553 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -21,6 +21,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.view.Gravity;
@@ -203,6 +204,11 @@
     public void onViewAdded(View child) {
         super.onViewAdded(child);
         clearMeasureOrder();
+        // For some reason ripples + notification actions seem to be an unhappy combination
+        // b/69474443 so just turn them off for now.
+        if (child.getBackground() instanceof RippleDrawable) {
+            ((RippleDrawable)child.getBackground()).setForceSoftware(true);
+        }
     }
 
     @Override
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 0228edb..915fe9d 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -200,6 +200,7 @@
 message WindowContainerProto {
   optional ConfigurationContainerProto configuration_container = 1;
   optional int32 orientation = 2;
+  optional bool visible = 3;
 }
 
 /* represents ConfigurationContainer */
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index b4240cd..440b2690 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -79,8 +79,7 @@
     <string name="RestrictedOnAllVoiceTitle" msgid="158800171499150681">"কোনো ভয়েস/জরুরী পরিষেবা নেই"</string>
     <string name="RestrictedStateContent" msgid="4278821484643362350">"সাময়িকভাবে মোবাইল নেটওয়ার্ক আপনার অবস্থানে এই পরিষেবা দিচ্ছে না"</string>
     <string name="NetworkPreferenceSwitchTitle" msgid="4008877505368566980">"নেটওয়ার্কের সিগন্যাল নেই"</string>
-    <!-- no translation found for NetworkPreferenceSwitchSummary (7056776609127756440) -->
-    <skip />
+    <string name="NetworkPreferenceSwitchSummary" msgid="7056776609127756440">"আরও ভাল সিগন্যাল পাওয়ার জন্য সেটিংস &gt; নেটওয়ার্ক এবং ইন্টারনেট &gt; মোবাইল নেটওয়ার্ক &gt; পছন্দের নেটওয়ার্কের ধরন বিকল্পে গিয়ে অন্য ধরনের নেটওয়ার্ক বেছে নিয়ে দেখুন।"</string>
     <string name="EmergencyCallWarningTitle" msgid="4790413876281901612">"ওয়াই-ফাই কলিং সক্রিয় আছে"</string>
     <string name="EmergencyCallWarningSummary" msgid="8973232888021643293">"জরুরি কলের জন্য মোবাইল নেটওয়ার্ক থাকতে হবে।"</string>
     <string name="notification_channel_network_alert" msgid="4427736684338074967">"সতর্কতা"</string>
@@ -213,6 +212,8 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"পাওয়ার বন্ধ করুন"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"জরুরী"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"ত্রুটির প্রতিবেদন"</string>
+    <!-- no translation found for global_action_logout (935179188218826050) -->
+    <skip />
     <string name="bugreport_title" msgid="2667494803742548533">"ত্রুটির অভিযোগ করুন"</string>
     <string name="bugreport_message" msgid="398447048750350456">"এটি একটি ই-মেল মেসেজ পাঠানোর জন্য আপনার ডিভাইসের বর্তমান অবস্থা সম্পর্কে তথ্য সংগ্রহ করবে৷ ত্রুটির প্রতিবেদন শুরুর সময় থেকে এটি পাঠানোর জন্য প্রস্তুত হতে কিছুটা সময় নেবে; অনুগ্রহ করে ধৈর্য রাখুন৷"</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"ইন্টারেক্টিভ প্রতিবেদন"</string>
@@ -1052,12 +1053,9 @@
     <string name="screen_compat_mode_hint" msgid="1064524084543304459">"সিস্টেম সেটিংস&gt; অ্যাপ্স&gt; ডাউনলোড করাগুলি এ এটি পুনঃসক্ষম করুন৷"</string>
     <string name="unsupported_display_size_message" msgid="6545327290756295232">"<xliff:g id="APP_NAME">%1$s</xliff:g>, বর্তমান প্রদর্শনের আকারের সেটিংস সমর্থন করে না এবং অপ্রত্যাশিত আচরণ করতে পারে৷"</string>
     <string name="unsupported_display_size_show" msgid="7969129195360353041">"সর্বদা দেখান"</string>
-    <!-- no translation found for unsupported_compile_sdk_message (5030433583092006591) -->
-    <skip />
-    <!-- no translation found for unsupported_compile_sdk_show (2681877855260970231) -->
-    <skip />
-    <!-- no translation found for unsupported_compile_sdk_check_update (3312723623323216101) -->
-    <skip />
+    <string name="unsupported_compile_sdk_message" msgid="4253168368781441759">"<xliff:g id="APP_NAME">%1$s</xliff:g> Android OS এর একটি সামঞ্জস্যহীন ভার্সনের জন্য তৈরি করা হয়েছিল, তাই এখানে সেটি অস্বাভাবিক আচরণ করতে পারে। মনে হয় অ্যাপটির আপডেট করা ভার্সনও আছে।"</string>
+    <string name="unsupported_compile_sdk_show" msgid="2681877855260970231">"সবসময় দেখুন"</string>
+    <string name="unsupported_compile_sdk_check_update" msgid="3312723623323216101">"আপডেট পাওয়া যাচ্ছে কিনা দেখুন"</string>
     <string name="smv_application" msgid="3307209192155442829">"অ্যাপ্লিকেশানটি <xliff:g id="APPLICATION">%1$s</xliff:g> (প্রক্রিয়া <xliff:g id="PROCESS">%2$s</xliff:g>) তার স্ব-প্রয়োগ করা কঠোর মোড নীতি লঙ্ঘন করেছে৷"</string>
     <string name="smv_process" msgid="5120397012047462446">"প্রক্রিয়াটি <xliff:g id="PROCESS">%1$s</xliff:g> তার স্ব-প্রয়োগ করা কঠোর মোড নীতি লঙ্ঘন করেছে৷"</string>
     <string name="android_upgrading_title" msgid="1584192285441405746">"Android আপগ্রেড করা হচ্ছে..."</string>
@@ -1123,12 +1121,10 @@
     <string name="network_available_sign_in" msgid="1848877297365446605">"নেটওয়ার্কে সাইন-ইন করুন"</string>
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
     <skip />
-    <!-- no translation found for wifi_no_internet (8938267198124654938) -->
-    <skip />
+    <string name="wifi_no_internet" msgid="8938267198124654938">"ওয়াই-ফাই এ ইন্টারনেট অ্যাক্সেস নেই"</string>
     <string name="wifi_no_internet_detailed" msgid="8083079241212301741">"বিকল্পগুলির জন্য আলতো চাপুন"</string>
     <string name="network_switch_metered" msgid="4671730921726992671">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> এ পাল্টানো হয়েছে"</string>
-    <!-- no translation found for network_switch_metered_detail (775163331794506615) -->
-    <skip />
+    <string name="network_switch_metered_detail" msgid="775163331794506615">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> এ ইন্টারনেট অ্যাক্সেস না থাকলে <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ব্যবহার করা হয়৷ ডেটা চার্জ প্রযোজ্য৷"</string>
     <string name="network_switch_metered_toast" msgid="5779283181685974304">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> থেকে <xliff:g id="NEW_NETWORK">%2$s</xliff:g> এ পাল্টানো হয়েছে"</string>
   <string-array name="network_switch_type_name">
     <item msgid="3979506840912951943">"মোবাইল ডেটা"</item>
@@ -1505,6 +1501,10 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"শর্টকাটটি চালু থাকলে দুটি ভলিউম বোতাম একসাথে ৩ সেকেন্ড টিপে ধরে রাখলে একটি অ্যাকসেসিবিলিটি বৈশিষ্ট্য চালু হবে।\n\n বর্তমান অ্যাকসেসিবিলিটি বৈশিষ্ট্য:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n আপনি এই বৈশিষ্ট্যটি সেটিংস &gt; অ্যাকসেসিবিলিটিতে গিয়ে পরিবর্তন করতে পারবেন।"</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"শর্টকাট বন্ধ করুন"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"শর্টকাট ব্যবহার করুন"</string>
+    <!-- no translation found for color_inversion_feature_name (4231186527799958644) -->
+    <skip />
+    <!-- no translation found for color_correction_feature_name (6779391426096954933) -->
+    <skip />
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"অ্যাক্সেসযোগ্যতা শর্টকাট <xliff:g id="SERVICE_NAME">%1$s</xliff:g> কে চালু করেছে"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"অ্যাক্সেসযোগ্যতা শর্টকাট <xliff:g id="SERVICE_NAME">%1$s</xliff:g> কে বন্ধ করেছে"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"অ্যাক্সেসযোগ্যতা বোতামের সাহায্যে যে বৈশিষ্ট্যটি নিয়ন্ত্রণ করতে চান, সেটি বেছে নিন:"</string>
@@ -1644,7 +1644,7 @@
     <string name="package_installed_device_owner" msgid="6875717669960212648">"আপনার প্রশাসক ইনস্টল করেছেন"</string>
     <string name="package_updated_device_owner" msgid="1847154566357862089">"আপনার প্রশাসক আপডেট করেছেন"</string>
     <string name="package_deleted_device_owner" msgid="2307122077550236438">"আপনার প্রশাসক মুছে দিয়েছেন"</string>
-    <string name="battery_saver_description" msgid="1960431123816253034">"ব্যাটরির লাইফ উন্নত করতে, ব্যাটারি সাশ্রয়কারী আপনার ডিভাইসের কার্যসম্পাদনা হ্রাস করে এবং কম্পন, লোকেশন পরিষেবাগুলি এবং অধিকাংশ ব্যাকগ্রাউন্ড ডেটা সীমিত করে৷ ইমেল, মেসেজিং এবং অন্যান্য অ্যাপ্লিকেশনগুলিকে যেগুলি সিঙ্কের উপর নির্ভর করে সেগুলিকে আপনি না খোলা পর্যন্ত নাও আপডেট হতে পারে৷\n\nআপনার ডিভাইসটিকে যখন চার্জ করা হয় তখন ব্যাটারি সাশ্রয়কারী নিজে থেকে বন্ধ হয়ে যায়৷"</string>
+    <string name="battery_saver_description" msgid="5394663545060026162">"ব্যাটরির লাইফ উন্নত করে সাহায্য করতে, ব্যাটারি সেভার আপনার ডিভাইসের পারফরম্যান্স কমিয়ে দেয় এবং কম্পন, লোকেশন পরিষেবা এবং অধিকাংশ ব্যাকগ্রাউন্ড ডেটা সীমিত করে। ইমেল, মেসেজিং এবং সিঙ্কের উপর নির্ভর করে এমন অন্যান্য অ্যাপগুলিকে আপনি না খোলা পর্যন্ত নাও আপডেট হতে পারে।\n\nআপনার ডিভাইসটিকে যখন চার্জ করা হয় তখন ব্যাটারি সেভার নিজে থেকে বন্ধ হয়ে যায়।"</string>
     <string name="data_saver_description" msgid="6015391409098303235">"ডেটার ব্যবহার কমাতে সহায়তা করার জন্য, ডেটা সেভার পটভূমিতে কিছু অ্যাপ্লিকেশনকে ডেটা পাঠাতে বা গ্রহণ করতে বাধা দেয়৷ আপনি বর্তমানে এমন একটি অ্যাপ্লিকেশন ব্যবহার করছেন যেটি ডেটা অ্যাক্সেস করতে পারে, তবে সেটি কমই করে৷ এর ফলে যা হতে পারে, উদাহরণস্বরূপ, আপনি ছবিগুলিতে আলতো চাপ না দেওয়া পর্যন্ত সেগুলি প্রদর্শিত হবে না৷"</string>
     <string name="data_saver_enable_title" msgid="4674073932722787417">"ডেটা সেভার চালু করবেন?"</string>
     <string name="data_saver_enable_button" msgid="7147735965247211818">"চালু করুন"</string>
@@ -1691,6 +1691,8 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"সপ্তাহান্তের রাত্রি"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"সপ্তাহান্ত"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"ইভেন্ট"</string>
+    <!-- no translation found for zen_mode_default_every_night_name (3012363838882944175) -->
+    <skip />
     <string name="muted_by" msgid="6147073845094180001">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> দ্বারা নিঃশব্দ করা হয়েছে"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"আপনার ডিভাইসে একটি অভ্যন্তরীন সমস্যা হয়েছে, এবং আপনি যতক্ষণ না পর্যন্ত এটিকে ফ্যাক্টরি ডেটা রিসেট করছেন ততক্ষণ এটি ঠিকভাবে কাজ নাও করতে পারে৷"</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"আপনার ডিভাইসে একটি অভ্যন্তরীন সমস্যা হয়েছে৷ বিস্তারিত জানার জন্য প্রস্তুতকারকের সাথে যোগাযোগ করুন৷"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index ef30b91..c9c98eb 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -214,6 +214,8 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Isključi telefon"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Hitno"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Izvještaj o greškama"</string>
+    <!-- no translation found for global_action_logout (935179188218826050) -->
+    <skip />
     <string name="bugreport_title" msgid="2667494803742548533">"Kreirajte izvještaj o greškama"</string>
     <string name="bugreport_message" msgid="398447048750350456">"Ovim će se prikupljati informacije o trenutnom stanju uređaja, koji će biti poslani kao e-poruka. Može malo potrajati dok se izvještaj o greškama ne kreira i bude spreman za slanje. Budite strpljivi."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interaktivni izvještaj"</string>
@@ -1026,9 +1028,9 @@
     <string name="whichEditApplication" msgid="144727838241402655">"Uredi koristeći"</string>
     <string name="whichEditApplicationNamed" msgid="1775815530156447790">"Uredi koristeći %1$s"</string>
     <string name="whichEditApplicationLabel" msgid="7183524181625290300">"Uredi"</string>
-    <string name="whichSendApplication" msgid="6902512414057341668">"Podijeli koristeći"</string>
-    <string name="whichSendApplicationNamed" msgid="2799370240005424391">"Podijeli koristeći %1$s"</string>
-    <string name="whichSendApplicationLabel" msgid="4579076294675975354">"Podijeli"</string>
+    <string name="whichSendApplication" msgid="6902512414057341668">"Dijeli koristeći"</string>
+    <string name="whichSendApplicationNamed" msgid="2799370240005424391">"Dijeli koristeći %1$s"</string>
+    <string name="whichSendApplicationLabel" msgid="4579076294675975354">"Dijeli"</string>
     <string name="whichSendToApplication" msgid="8272422260066642057">"Pošalji koristeći"</string>
     <string name="whichSendToApplicationNamed" msgid="7768387871529295325">"Pošalji koristeći %1$s"</string>
     <string name="whichSendToApplicationLabel" msgid="8878962419005813500">"Pošalji"</string>
@@ -1353,7 +1355,7 @@
     <string name="progress_erasing" product="default" msgid="6596988875507043042">"Brisanje SD kartice..."</string>
     <string name="share" msgid="1778686618230011964">"Podijelite"</string>
     <string name="find" msgid="4808270900322985960">"Pronađi"</string>
-    <string name="websearch" msgid="4337157977400211589">"Internet pretraga"</string>
+    <string name="websearch" msgid="4337157977400211589">"Internet pretraživanje"</string>
     <string name="find_next" msgid="5742124618942193978">"Nađi sljedeći"</string>
     <string name="find_previous" msgid="2196723669388360506">"Nađi prethodni"</string>
     <string name="gpsNotifTicker" msgid="5622683912616496172">"Korisnik <xliff:g id="NAME">%s</xliff:g> je poslao zahtjev za utvrđivanje lokacije"</string>
@@ -1397,7 +1399,7 @@
     <string name="activitychooserview_choose_application" msgid="2125168057199941199">"Odaberite aplikaciju"</string>
     <string name="activitychooserview_choose_application_error" msgid="8624618365481126668">"Aplikacija <xliff:g id="APPLICATION_NAME">%s</xliff:g> se ne može pokrenuti."</string>
     <string name="shareactionprovider_share_with" msgid="806688056141131819">"Podijeliti sa"</string>
-    <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"Podijeli koristeći aplikaciju <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"Dijeli koristeći aplikaciju <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="content_description_sliding_handle" msgid="415975056159262248">"Klizni regulator. Dodirnite &amp; držite."</string>
     <string name="description_target_unlock_tablet" msgid="3833195335629795055">"Prevucite za otključavanje ekrana."</string>
     <string name="action_bar_home_description" msgid="5293600496601490216">"Vratite se na početnu stranicu"</string>
@@ -1524,6 +1526,10 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"Kada je prečica uključena, pritiskom na oba dugmeta za podešavanje jačine zvuka u trajanju od 3 sekunde pokrenut će se funkcija za pristupačnost.\n\n Trenutna funkcija za pristupačnost je:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Funkciju možete promijeniti ako odete u Postavke &gt; Pristupačnost."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Isključi prečicu"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Koristi prečicu"</string>
+    <!-- no translation found for color_inversion_feature_name (4231186527799958644) -->
+    <skip />
+    <!-- no translation found for color_correction_feature_name (6779391426096954933) -->
+    <skip />
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Prečica za pristupačnost je uključila uslugu <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Prečica za pristupačnost je isključila uslugu <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Odaberite funkciju koja će se koristiti kada dodirnete dugme Pristupačnost:"</string>
@@ -1719,6 +1725,8 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Radni dan uvečer"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Vikend"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Događaj"</string>
+    <!-- no translation found for zen_mode_default_every_night_name (3012363838882944175) -->
+    <skip />
     <string name="muted_by" msgid="6147073845094180001">"Ton isključila aplikacija <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"Postoji problem u vašem uređaju i može biti nestabilan dok ga ne vratite na fabričke postavke."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"Postoji problem u vašem uređaju. Za više informacija obratite se proizvođaču."</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 7d9e531..e1b4e0d 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -212,6 +212,8 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"خاموش کردن"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"اضطراری"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"گزارش اشکال"</string>
+    <!-- no translation found for global_action_logout (935179188218826050) -->
+    <skip />
     <string name="bugreport_title" msgid="2667494803742548533">"گرفتن گزارش اشکال"</string>
     <string name="bugreport_message" msgid="398447048750350456">"این گزارش اطلاعات مربوط به وضعیت دستگاه کنونی شما را جمع‌آوری می‌کند تا به صورت یک پیام رایانامه ارسال شود. از زمان شروع گزارش اشکال تا آماده شدن برای ارسال اندکی زمان می‌برد؛ لطفاً شکیبا باشید."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"گزارش تعاملی"</string>
@@ -1498,6 +1500,10 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"وقتی میان‌بر روشن است،‌ اگر هر دو دکمه صدا را ۳ ثانیه فشار دهید یکی از قابلیت‌های دسترس‌پذیری شروع می‌شود.\n\n قابلیت دسترس‌پذیری کنونی:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n می‌توانید در «تنظیمات &gt; دسترس‌پذیری»، قابلیت را تغییر دهید."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"خاموش کردن میان‌بر"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"استفاده از میان‌بر"</string>
+    <!-- no translation found for color_inversion_feature_name (4231186527799958644) -->
+    <skip />
+    <!-- no translation found for color_correction_feature_name (6779391426096954933) -->
+    <skip />
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"«میان‌بر دسترس‌پذیری» <xliff:g id="SERVICE_NAME">%1$s</xliff:g> را روشن کرد"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"«میان‌بر دسترس‌پذیری» <xliff:g id="SERVICE_NAME">%1$s</xliff:g> را خاموش کرد"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"قابلیتی را انتخاب کنید که هنگام ضربه زدن روی دکمه «دسترس‌پذیری» استفاده می‌شود:"</string>
@@ -1684,6 +1690,8 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"شب آخر هفته"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"آخر هفته"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"رویداد"</string>
+    <!-- no translation found for zen_mode_default_every_night_name (3012363838882944175) -->
+    <skip />
     <string name="muted_by" msgid="6147073845094180001">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> آن را بی‌صدا کرد"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"دستگاهتان یک مشکل داخلی دارد، و ممکن است تا زمانی که بازنشانی داده‌های کارخانه انجام نگیرد، بی‌ثبات بماند."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"دستگاهتان یک مشکل داخلی دارد. برای جزئیات آن با سازنده‌تان تماس بگیرید."</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 19e7b34..c499ea7 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -212,6 +212,8 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Matikan perangkat"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Darurat"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Laporan bug"</string>
+    <!-- no translation found for global_action_logout (935179188218826050) -->
+    <skip />
     <string name="bugreport_title" msgid="2667494803742548533">"Ambil laporan bug"</string>
     <string name="bugreport_message" msgid="398447048750350456">"Ini akan mengumpulkan informasi status perangkat Anda saat ini, untuk dikirimkan sebagai pesan email. Harap bersabar, mungkin perlu waktu untuk memulai laporan bug hingga siap dikirim."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Laporan interaktif"</string>
@@ -1498,6 +1500,10 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"Saat pintasan aktif, menekan kedua tombol volume selama 3 detik akan memulai fitur aksesibilitas.\n\n Fitur aksesibilitas saat ini:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Anda dapat mengubah fitur di Setelan &gt; Aksesibilitas."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Nonaktifkan Pintasan"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Gunakan Pintasan"</string>
+    <!-- no translation found for color_inversion_feature_name (4231186527799958644) -->
+    <skip />
+    <!-- no translation found for color_correction_feature_name (6779391426096954933) -->
+    <skip />
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Pintasan Aksesibilitas mengaktifkan <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Pintasan Aksesibilitas menonaktifkan <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Pilih fitur yang akan digunakan saat menge-tap tombol Aksesibilitas:"</string>
@@ -1684,6 +1690,8 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Malam hari kerja"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Akhir pekan"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Acara"</string>
+    <!-- no translation found for zen_mode_default_every_night_name (3012363838882944175) -->
+    <skip />
     <string name="muted_by" msgid="6147073845094180001">"Dinonaktifkan oleh <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"Ada masalah dengan perangkat. Hal ini mungkin membuat perangkat jadi tidak stabil dan perlu dikembalikan ke setelan pabrik."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"Ada masalah dengan perangkat. Hubungi produsen perangkat untuk informasi selengkapnya."</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 00bc9c2..ce07805 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -212,6 +212,8 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"კვების გამორთვა"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"საგანგებო სამსახურები"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"ხარვეზის შესახებ ანგარიში"</string>
+    <!-- no translation found for global_action_logout (935179188218826050) -->
+    <skip />
     <string name="bugreport_title" msgid="2667494803742548533">"შექმენით შეცდომის ანგარიში"</string>
     <string name="bugreport_message" msgid="398447048750350456">"იგი შეაგროვებს ინფორმაციას თქვენი მოწყობილობის ამჟამინდელი მდგომარეობის შესახებ, რათა ის ელფოსტის შეტყობინების სახით გააგზავნოს. ხარვეზის ანგარიშის მომზადებასა და შეტყობინების გაგზავნას გარკვეული დრო სჭირდება. გთხოვთ, მოითმინოთ."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"ინტერაქტიული ანგარიში"</string>
@@ -1051,7 +1053,7 @@
     <string name="screen_compat_mode_hint" msgid="1064524084543304459">"ხელახალი გააქტიურება განყოფილებაში: სისტემის პარამეტრები &gt; აპები &gt; ჩამოტვირთულები."</string>
     <string name="unsupported_display_size_message" msgid="6545327290756295232">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ ეკრანის ამჟამინდელი პარამეტრები მხარდაუჭერელია და შეიძლება არასათანადოდ იმუშაოს."</string>
     <string name="unsupported_display_size_show" msgid="7969129195360353041">"ყოველთვის ჩვენება"</string>
-    <string name="unsupported_compile_sdk_message" msgid="4253168368781441759">"<xliff:g id="APP_NAME">%1$s</xliff:g> შექმნილია Android-ის ოპერატიული სისტემის არათავსებადი ვერსიისთვის და შეიძლება გაუმართავად იმუშაოს. შესაძლოა ხელმისაწვდომი იყოს აპის განახლებული ვერსია."</string>
+    <string name="unsupported_compile_sdk_message" msgid="4253168368781441759">"<xliff:g id="APP_NAME">%1$s</xliff:g> შექმნილია Android-ის ოპერაციული სისტემის არათავსებადი ვერსიისთვის და შეიძლება გაუმართავად იმუშაოს. შესაძლოა ხელმისაწვდომი იყოს აპის განახლებული ვერსია."</string>
     <string name="unsupported_compile_sdk_show" msgid="2681877855260970231">"ყოველთვის ჩვენება"</string>
     <string name="unsupported_compile_sdk_check_update" msgid="3312723623323216101">"განახლების შემოწმება"</string>
     <string name="smv_application" msgid="3307209192155442829">"აპმა <xliff:g id="APPLICATION">%1$s</xliff:g> (პროცესი <xliff:g id="PROCESS">%2$s</xliff:g>) დაარღვია საკუთარი StrictMode დებულება."</string>
@@ -1498,6 +1500,10 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"მალსახმობის ჩართვის შემთხვევაში, ხმის ორივე ღილაკზე 3 წამის განმავლობაში დაჭერით მარტივი წვდომის ფუნქცია ჩაირთვება.\n\n მარტივი წვდომის ამჟამინდელი ფუნქციაა:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ამ ფუნქციის შეცვლა შეგიძლიათ აქ: პარამეტრები &gt; მარტივი წვდომა."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"მალსახმობის გამორთვა"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"მალსახმობის გამოყენება"</string>
+    <!-- no translation found for color_inversion_feature_name (4231186527799958644) -->
+    <skip />
+    <!-- no translation found for color_correction_feature_name (6779391426096954933) -->
+    <skip />
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"მარტივი წვდომის მალსახმობმა ჩართო <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"მარტივი წვდომის მალსახმობმა გამორთო <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"აირჩიეთ მარტივი წვდომის ღილაკზე შეხებისას გამოსაყენებელი ფუნქცია:"</string>
@@ -1684,6 +1690,8 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"სამუშაო კვირის ღამე"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"შაბათ-კვირა"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"მოვლენა"</string>
+    <!-- no translation found for zen_mode_default_every_night_name (3012363838882944175) -->
+    <skip />
     <string name="muted_by" msgid="6147073845094180001">"დადუმებულია <xliff:g id="THIRD_PARTY">%1$s</xliff:g>-ის მიერ"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"ფიქსირდება თქვენი მ ოწყობილობის შიდა პრობლემა და შეიძლება არასტაბილური იყოს, სანამ ქარხნულ მონაცემების არ განაახლებთ."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"ფიქსირდება თქვენი მოწყობილობის შიდა პრობლემა. დეტალებისათვის, მიმართეთ თქვენს მწარმოებელს."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 8d02283..3329961 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -47,8 +47,8 @@
     <string name="needPuk2" msgid="4526033371987193070">"Introduza o PUK2 para desbloquear o cartão SIM."</string>
     <string name="enablePin" msgid="209412020907207950">"Ação sem êxito. Ative o bloqueio do SIM/RUIM."</string>
     <plurals name="pinpuk_attempts" formatted="false" msgid="1251012001539225582">
-      <item quantity="one">Tem mais <xliff:g id="NUMBER_0">%d</xliff:g> tentativa antes de o cartão SIM ficar bloqueado.</item>
       <item quantity="other">Tem mais <xliff:g id="NUMBER_1">%d</xliff:g> tentativas antes de o cartão SIM ficar bloqueado.</item>
+      <item quantity="one">Tem mais <xliff:g id="NUMBER_0">%d</xliff:g> tentativa antes de o cartão SIM ficar bloqueado.</item>
     </plurals>
     <string name="imei" msgid="2625429890869005782">"IMEI"</string>
     <string name="meid" msgid="4841221237681254195">"MEID"</string>
@@ -162,8 +162,8 @@
     <string name="low_memory" product="tv" msgid="516619861191025923">"O armazenamento da TV está cheio. Elimine alguns ficheiros para libertar espaço."</string>
     <string name="low_memory" product="default" msgid="3475999286680000541">"O armazenamento do telemóvel está cheio. Elimine alguns ficheiros para libertar espaço."</string>
     <plurals name="ssl_ca_cert_warning" formatted="false" msgid="5106721205300213569">
-      <item quantity="one">Autoridade de certificação instalada</item>
       <item quantity="other">Autoridades de certificação instaladas</item>
+      <item quantity="one">Autoridade de certificação instalada</item>
     </plurals>
     <string name="ssl_ca_cert_noti_by_unknown" msgid="4475437862189850602">"Por um terceiro desconhecido"</string>
     <string name="ssl_ca_cert_noti_by_administrator" msgid="3541729986326153557">"Pelo gestor do seu perfil de trabalho"</string>
@@ -212,6 +212,8 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Desligar"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Emergência"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Relatório de erros"</string>
+    <!-- no translation found for global_action_logout (935179188218826050) -->
+    <skip />
     <string name="bugreport_title" msgid="2667494803742548533">"Criar relatório de erros"</string>
     <string name="bugreport_message" msgid="398447048750350456">"Será recolhida informação sobre o estado atual do seu dispositivo a enviar através de uma mensagem de email. Demorará algum tempo até que o relatório de erro esteja pronto para ser enviado. Aguarde um pouco."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Relatório interativo"</string>
@@ -219,8 +221,8 @@
     <string name="bugreport_option_full_title" msgid="6354382025840076439">"Relatório completo"</string>
     <string name="bugreport_option_full_summary" msgid="7210859858969115745">"Utilize esta opção para uma interferência mínima do sistema quando o dispositivo não responder ou estiver demasiado lento, ou quando precisar de todas as secções de relatório. Não permite introduzir mais detalhes ou tirar capturas de ecrã adicionais."</string>
     <plurals name="bugreport_countdown" formatted="false" msgid="6878900193900090368">
-      <item quantity="one">A tirar uma captura de ecrã do relatório de erro dentro de <xliff:g id="NUMBER_0">%d</xliff:g> segundo…</item>
       <item quantity="other">A tirar uma captura de ecrã do relatório de erro dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos.</item>
+      <item quantity="one">A tirar uma captura de ecrã do relatório de erro dentro de <xliff:g id="NUMBER_0">%d</xliff:g> segundo…</item>
     </plurals>
     <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"Modo silencioso"</string>
     <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Som desativado"</string>
@@ -864,8 +866,8 @@
     <string name="oneMonthDurationPast" msgid="7396384508953779925">"Há 1 mês"</string>
     <string name="beforeOneMonthDurationPast" msgid="909134546836499826">"Há mais de 1 mês"</string>
     <plurals name="last_num_days" formatted="false" msgid="5104533550723932025">
-      <item quantity="one">Último <xliff:g id="COUNT_0">%d</xliff:g> dia</item>
       <item quantity="other">Últimos <xliff:g id="COUNT_1">%d</xliff:g> dias</item>
+      <item quantity="one">Último <xliff:g id="COUNT_0">%d</xliff:g> dia</item>
     </plurals>
     <string name="last_month" msgid="3959346739979055432">"Último mês"</string>
     <string name="older" msgid="5211975022815554840">"Mais antiga"</string>
@@ -886,68 +888,68 @@
     <string name="years" msgid="6881577717993213522">"anos"</string>
     <string name="now_string_shortest" msgid="8912796667087856402">"agora"</string>
     <plurals name="duration_minutes_shortest" formatted="false" msgid="3957499975064245495">
-      <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> m</item>
       <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> m</item>
+      <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> m</item>
     </plurals>
     <plurals name="duration_hours_shortest" formatted="false" msgid="3552182110578602356">
-      <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> h</item>
       <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> h</item>
+      <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> h</item>
     </plurals>
     <plurals name="duration_days_shortest" formatted="false" msgid="5213655532597081640">
-      <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> d</item>
       <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> d</item>
+      <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> d</item>
     </plurals>
     <plurals name="duration_years_shortest" formatted="false" msgid="7848711145196397042">
-      <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> a</item>
       <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> a</item>
+      <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> a</item>
     </plurals>
     <plurals name="duration_minutes_shortest_future" formatted="false" msgid="3277614521231489951">
-      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> min</item>
       <item quantity="other">dentro de <xliff:g id="COUNT_1">%d</xliff:g> min</item>
+      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> min</item>
     </plurals>
     <plurals name="duration_hours_shortest_future" formatted="false" msgid="2152452368397489370">
-      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> h</item>
       <item quantity="other">dentro de <xliff:g id="COUNT_1">%d</xliff:g> h</item>
+      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> h</item>
     </plurals>
     <plurals name="duration_days_shortest_future" formatted="false" msgid="8088331502820295701">
-      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> d</item>
       <item quantity="other">dentro de <xliff:g id="COUNT_1">%d</xliff:g> d</item>
+      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> d</item>
     </plurals>
     <plurals name="duration_years_shortest_future" formatted="false" msgid="2317006667145250301">
-      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> a</item>
       <item quantity="other">dentro de <xliff:g id="COUNT_1">%d</xliff:g> a</item>
+      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> a</item>
     </plurals>
     <plurals name="duration_minutes_relative" formatted="false" msgid="3178131706192980192">
-      <item quantity="one">há <xliff:g id="COUNT_0">%d</xliff:g> minuto</item>
       <item quantity="other">há <xliff:g id="COUNT_1">%d</xliff:g> minutos</item>
+      <item quantity="one">há <xliff:g id="COUNT_0">%d</xliff:g> minuto</item>
     </plurals>
     <plurals name="duration_hours_relative" formatted="false" msgid="676894109982008411">
-      <item quantity="one">há <xliff:g id="COUNT_0">%d</xliff:g> hora</item>
       <item quantity="other">há <xliff:g id="COUNT_1">%d</xliff:g> horas</item>
+      <item quantity="one">há <xliff:g id="COUNT_0">%d</xliff:g> hora</item>
     </plurals>
     <plurals name="duration_days_relative" formatted="false" msgid="2203515825765397130">
-      <item quantity="one">há <xliff:g id="COUNT_0">%d</xliff:g> dia</item>
       <item quantity="other">há <xliff:g id="COUNT_1">%d</xliff:g> dias</item>
+      <item quantity="one">há <xliff:g id="COUNT_0">%d</xliff:g> dia</item>
     </plurals>
     <plurals name="duration_years_relative" formatted="false" msgid="4820062134188885734">
-      <item quantity="one">há <xliff:g id="COUNT_0">%d</xliff:g> ano</item>
       <item quantity="other">há <xliff:g id="COUNT_1">%d</xliff:g> anos</item>
+      <item quantity="one">há <xliff:g id="COUNT_0">%d</xliff:g> ano</item>
     </plurals>
     <plurals name="duration_minutes_relative_future" formatted="false" msgid="4655043589817680966">
-      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> minuto</item>
       <item quantity="other">dentro de <xliff:g id="COUNT_1">%d</xliff:g> minutos</item>
+      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> minuto</item>
     </plurals>
     <plurals name="duration_hours_relative_future" formatted="false" msgid="8084579714205223891">
-      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> hora</item>
       <item quantity="other">dentro de <xliff:g id="COUNT_1">%d</xliff:g> horas</item>
+      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> hora</item>
     </plurals>
     <plurals name="duration_days_relative_future" formatted="false" msgid="333215369363433992">
-      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> dia</item>
       <item quantity="other">dentro de <xliff:g id="COUNT_1">%d</xliff:g> dias</item>
+      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> dia</item>
     </plurals>
     <plurals name="duration_years_relative_future" formatted="false" msgid="8644862986413104011">
-      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> ano</item>
       <item quantity="other">dentro de <xliff:g id="COUNT_1">%d</xliff:g> anos</item>
+      <item quantity="one">dentro de <xliff:g id="COUNT_0">%d</xliff:g> ano</item>
     </plurals>
     <string name="VideoView_error_title" msgid="3534509135438353077">"Problema com o vídeo"</string>
     <string name="VideoView_error_text_invalid_progressive_playback" msgid="3186670335938670444">"Este vídeo não é válido para transmissão em fluxo contínuo neste aparelho."</string>
@@ -1101,12 +1103,12 @@
     <string name="ringtone_picker_title_notification" msgid="4837740874822788802">"Sons de notificação"</string>
     <string name="ringtone_unknown" msgid="3914515995813061520">"Desconhecido"</string>
     <plurals name="wifi_available" formatted="false" msgid="7900333017752027322">
-      <item quantity="one">Rede Wi-Fi disponível</item>
       <item quantity="other">Redes Wi-Fi disponíveis</item>
+      <item quantity="one">Rede Wi-Fi disponível</item>
     </plurals>
     <plurals name="wifi_available_detailed" formatted="false" msgid="1140699367193975606">
-      <item quantity="one">Rede Wi-Fi aberta disponível</item>
       <item quantity="other">Redes Wi-Fi abertas disponíveis</item>
+      <item quantity="one">Rede Wi-Fi aberta disponível</item>
     </plurals>
     <string name="wifi_available_title" msgid="3817100557900599505">"Ligar à rede Wi-Fi aberta"</string>
     <string name="wifi_available_title_connecting" msgid="1557292688310330032">"A ligar à rede Wi-Fi aberta…"</string>
@@ -1320,8 +1322,8 @@
     <string name="no_matches" msgid="8129421908915840737">"Sem correspondências"</string>
     <string name="find_on_page" msgid="1946799233822820384">"Localizar na página"</string>
     <plurals name="matches_found" formatted="false" msgid="1210884353962081884">
-      <item quantity="one">1 correspondência</item>
       <item quantity="other"><xliff:g id="INDEX">%d</xliff:g> de <xliff:g id="TOTAL">%d</xliff:g></item>
+      <item quantity="one">1 correspondência</item>
     </plurals>
     <string name="action_mode_done" msgid="7217581640461922289">"Concluído"</string>
     <string name="progress_erasing" product="nosdcard" msgid="4521573321524340058">"A apagar memória de armazenamento USB..."</string>
@@ -1455,8 +1457,8 @@
     <string name="kg_wrong_password" msgid="2333281762128113157">"Palavra-passe Incorreta"</string>
     <string name="kg_wrong_pin" msgid="1131306510833563801">"PIN Incorreto"</string>
     <plurals name="kg_too_many_failed_attempts_countdown" formatted="false" msgid="8790651267324125694">
-      <item quantity="one">Tente novamente dentro de 1 segundo.</item>
       <item quantity="other">Tente novamente dentro de <xliff:g id="NUMBER">%d</xliff:g> segundos.</item>
+      <item quantity="one">Tente novamente dentro de 1 segundo.</item>
     </plurals>
     <string name="kg_pattern_instructions" msgid="398978611683075868">"Desenhe a sua sequência"</string>
     <string name="kg_sim_pin_instructions" msgid="2319508550934557331">"Introduzir PIN do cartão SIM"</string>
@@ -1498,6 +1500,10 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"Quando o atalho está ativado, premir ambos os botões de volume durante 3 segundos inicia uma funcionalidade de acessibilidade.\n\n Funcionalidade de acessibilidade atual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Pode alterar a funcionalidade em Definições &gt; Acessibilidade."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Desativar atalho"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Utilizar atalho"</string>
+    <!-- no translation found for color_inversion_feature_name (4231186527799958644) -->
+    <skip />
+    <!-- no translation found for color_correction_feature_name (6779391426096954933) -->
+    <skip />
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"O Atalho de acessibilidade ativou o serviço <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"O Atalho de acessibilidade desativou o serviço <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Escolha uma funcionalidade para utilizar quando tocar no botão Acessibilidade:"</string>
@@ -1610,8 +1616,8 @@
     <string name="restr_pin_error_doesnt_match" msgid="2224214190906994548">"Os PINs não correspondem. Tente novamente."</string>
     <string name="restr_pin_error_too_short" msgid="8173982756265777792">"O PIN é demasiado pequeno. Deve ter, no mínimo, 4 dígitos."</string>
     <plurals name="restr_pin_countdown" formatted="false" msgid="9061246974881224688">
-      <item quantity="one">Tente novamente dentro de 1 segundo</item>
       <item quantity="other">Tente novamente dentro de <xliff:g id="COUNT">%d</xliff:g> segundos</item>
+      <item quantity="one">Tente novamente dentro de 1 segundo</item>
     </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Tente novamente mais tarde"</string>
     <string name="immersive_cling_title" msgid="8394201622932303336">"Visualização de ecrã inteiro"</string>
@@ -1642,36 +1648,36 @@
     <string name="data_saver_enable_title" msgid="4674073932722787417">"Ativar a Poupança de dados?"</string>
     <string name="data_saver_enable_button" msgid="7147735965247211818">"Ativar"</string>
     <plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="4367877408072000848">
-      <item quantity="one">Durante um minuto (até à(s) <xliff:g id="FORMATTEDTIME_0">%2$s</xliff:g>)</item>
       <item quantity="other">Durante %1$d minutos (até à(s) <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
+      <item quantity="one">Durante um minuto (até à(s) <xliff:g id="FORMATTEDTIME_0">%2$s</xliff:g>)</item>
     </plurals>
     <plurals name="zen_mode_duration_minutes_summary_short" formatted="false" msgid="6830154222366042597">
-      <item quantity="one">Durante 1 min (até à(s) <xliff:g id="FORMATTEDTIME_0">%2$s</xliff:g>)</item>
       <item quantity="other">Durante %1$d min (até à(s) <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
+      <item quantity="one">Durante 1 min (até à(s) <xliff:g id="FORMATTEDTIME_0">%2$s</xliff:g>)</item>
     </plurals>
     <plurals name="zen_mode_duration_hours_summary" formatted="false" msgid="8152974162096743862">
-      <item quantity="one">Durante uma hora (até à(s) <xliff:g id="FORMATTEDTIME_0">%2$s</xliff:g>)</item>
       <item quantity="other">Durante %1$d horas (até à(s) <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
+      <item quantity="one">Durante uma hora (até à(s) <xliff:g id="FORMATTEDTIME_0">%2$s</xliff:g>)</item>
     </plurals>
     <plurals name="zen_mode_duration_hours_summary_short" formatted="false" msgid="4787552595253082371">
-      <item quantity="one">Durante 1 h (até à(s) <xliff:g id="FORMATTEDTIME_0">%2$s</xliff:g>)</item>
       <item quantity="other">Durante %1$d h (até à(s) <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
+      <item quantity="one">Durante 1 h (até à(s) <xliff:g id="FORMATTEDTIME_0">%2$s</xliff:g>)</item>
     </plurals>
     <plurals name="zen_mode_duration_minutes" formatted="false" msgid="5127407202506485571">
-      <item quantity="one">Durante um minuto</item>
       <item quantity="other">Durante %d minutos</item>
+      <item quantity="one">Durante um minuto</item>
     </plurals>
     <plurals name="zen_mode_duration_minutes_short" formatted="false" msgid="2199350154433426128">
-      <item quantity="one">Durante 1 min</item>
       <item quantity="other">Durante %d min</item>
+      <item quantity="one">Durante 1 min</item>
     </plurals>
     <plurals name="zen_mode_duration_hours" formatted="false" msgid="3938821308277433854">
-      <item quantity="one">Durante uma hora</item>
       <item quantity="other">Durante %d horas</item>
+      <item quantity="one">Durante uma hora</item>
     </plurals>
     <plurals name="zen_mode_duration_hours_short" formatted="false" msgid="6748277774662434217">
-      <item quantity="one">Durante 1 h</item>
       <item quantity="other">Durante %d h</item>
+      <item quantity="one">Durante 1 h</item>
     </plurals>
     <string name="zen_mode_until" msgid="7336308492289875088">"Até às <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
     <string name="zen_mode_alarm" msgid="9128205721301330797">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próximo alarme)"</string>
@@ -1684,6 +1690,8 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Dias da semana à noite"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Fim de semana"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Evento"</string>
+    <!-- no translation found for zen_mode_default_every_night_name (3012363838882944175) -->
+    <skip />
     <string name="muted_by" msgid="6147073845094180001">"Som desativado por <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"Existe um problema interno no seu dispositivo e pode ficar instável até efetuar uma reposição de dados de fábrica."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"Existe um problema interno no seu dispositivo. Contacte o fabricante para obter mais informações."</string>
@@ -1706,8 +1714,8 @@
     <string name="close_button_text" msgid="3937902162644062866">"Fechar"</string>
     <string name="notification_messaging_title_template" msgid="3452480118762691020">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g>: <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string>
     <plurals name="selected_count" formatted="false" msgid="7187339492915744615">
-      <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> selecionado</item>
       <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> selecionados</item>
+      <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> selecionado</item>
     </plurals>
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sem categoria"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Definiu a importância destas notificações."</string>
@@ -1764,8 +1772,8 @@
     <string name="autofill_error_cannot_autofill" msgid="7402758580060110371">"Não é possível preencher automaticamente o conteúdo"</string>
     <string name="autofill_picker_no_suggestions" msgid="3908514303773350735">"Sem sugestões do preenchimento automático"</string>
     <plurals name="autofill_picker_some_suggestions" formatted="false" msgid="5506565809835815274">
-      <item quantity="one">Uma sugestão do preenchimento automático</item>
       <item quantity="other"><xliff:g id="COUNT">%1$s</xliff:g> sugestões do preenchimento automático</item>
+      <item quantity="one">Uma sugestão do preenchimento automático</item>
     </plurals>
     <string name="autofill_save_title" msgid="3345527308992082601">"Pretende guardar no &lt;b&gt;<xliff:g id="LABEL">%1$s</xliff:g>&lt;/b&gt;?"</string>
     <string name="autofill_save_title_with_type" msgid="8637809388029313305">"Pretende guardar <xliff:g id="TYPE">%1$s</xliff:g> no &lt;b&gt;<xliff:g id="LABEL">%2$s</xliff:g>&lt;/b&gt;?"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 20b1bbe..ad6c6bc 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -216,6 +216,8 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"Вимкнути"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Екстрений виклик"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Звіт про помилки"</string>
+    <!-- no translation found for global_action_logout (935179188218826050) -->
+    <skip />
     <string name="bugreport_title" msgid="2667494803742548533">"Звіт про помилку"</string>
     <string name="bugreport_message" msgid="398447048750350456">"Інформація про поточний стан вашого пристрою буде зібрана й надіслана електронною поштою. Підготовка звіту триватиме певний час."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Інтерактивний звіт"</string>
@@ -1546,6 +1548,10 @@
     <string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"Коли ярлик увімкнено, після натискання обох клавіш гучності й утримування їх протягом 3 секунд увімкнеться функція спеціальних можливостей.\n\n Поточна функція спеціальних можливостей:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Цю функцію можна змінити в меню \"Налаштування\" &gt; \"Спеціальні можливості\"."</string>
     <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Вимкнути ярлик"</string>
     <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Використовувати ярлик"</string>
+    <!-- no translation found for color_inversion_feature_name (4231186527799958644) -->
+    <skip />
+    <!-- no translation found for color_correction_feature_name (6779391426096954933) -->
+    <skip />
     <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Ярлик спеціальних можливостей увімкнув <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Ярлик спеціальних можливостей вимкнув <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Виберіть функцію для кнопки спеціальних можливостей:"</string>
@@ -1750,6 +1756,8 @@
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Увечері в будні"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"На вихідних"</string>
     <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Подія"</string>
+    <!-- no translation found for zen_mode_default_every_night_name (3012363838882944175) -->
+    <skip />
     <string name="muted_by" msgid="6147073845094180001">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> вимикає звук"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"Через внутрішню помилку ваш пристрій може працювати нестабільно. Відновіть заводські налаштування."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"На пристрої сталася внутрішня помилка. Зв’яжіться з виробником пристрою, щоб дізнатися більше."</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ef8f6af..5ccaf5c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4490,7 +4490,9 @@
     <string name="stk_cc_ussd_to_dial">USSD request is modified to DIAL request.</string>
     <string name="stk_cc_ussd_to_ss">USSD request is modified to SS request.</string>
     <string name="stk_cc_ussd_to_ussd">USSD request is modified to new USSD request.</string>
+    <string name="stk_cc_ussd_to_dial_video">USSD request is modified to Video DIAL request.</string>
     <string name="stk_cc_ss_to_dial">SS request is modified to DIAL request.</string>
+    <string name="stk_cc_ss_to_dial_video">SS request is modified to Video DIAL request.</string>
     <string name="stk_cc_ss_to_ussd">SS request is modified to USSD request.</string>
     <string name="stk_cc_ss_to_ss">SS request is modified to new SS request.</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6da24bd..f659360 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1048,9 +1048,11 @@
   <java-symbol type="string" name="stk_cc_ss_to_dial" />
   <java-symbol type="string" name="stk_cc_ss_to_ss" />
   <java-symbol type="string" name="stk_cc_ss_to_ussd" />
+  <java-symbol type="string" name="stk_cc_ss_to_dial_video" />
   <java-symbol type="string" name="stk_cc_ussd_to_dial" />
   <java-symbol type="string" name="stk_cc_ussd_to_ss" />
   <java-symbol type="string" name="stk_cc_ussd_to_ussd" />
+  <java-symbol type="string" name="stk_cc_ussd_to_dial_video" />
   <java-symbol type="string" name="safe_media_volume_warning" />
   <java-symbol type="string" name="media_route_status_scanning" />
   <java-symbol type="string" name="media_route_status_connecting" />
diff --git a/core/tests/ConnectivityManagerTest/Android.mk b/core/tests/ConnectivityManagerTest/Android.mk
index 39cf4a4..5ed93f3 100644
--- a/core/tests/ConnectivityManagerTest/Android.mk
+++ b/core/tests/ConnectivityManagerTest/Android.mk
@@ -18,8 +18,8 @@
 # We only want this apk build for tests.
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/core/tests/bandwidthtests/Android.mk b/core/tests/bandwidthtests/Android.mk
index 2af92df..ff9a0fe 100644
--- a/core/tests/bandwidthtests/Android.mk
+++ b/core/tests/bandwidthtests/Android.mk
@@ -22,8 +22,12 @@
 LOCAL_SRC_FILES := \
 	$(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner org.apache.http.legacy
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner \
+    org.apache.http.legacy \
+    android.test.base \
+
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 LOCAL_PACKAGE_NAME := BandwidthTests
 
 include $(BUILD_PACKAGE)
diff --git a/core/tests/bluetoothtests/Android.mk b/core/tests/bluetoothtests/Android.mk
index f53419a..744e5b0 100644
--- a/core/tests/bluetoothtests/Android.mk
+++ b/core/tests/bluetoothtests/Android.mk
@@ -8,8 +8,8 @@
 LOCAL_SRC_FILES := \
 	$(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 LOCAL_PACKAGE_NAME := BluetoothTests
 LOCAL_CERTIFICATE := platform
 
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index bec862a..c14dc90 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -19,12 +19,17 @@
 import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
 import android.media.session.MediaSession;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -142,6 +147,36 @@
     }
 
     @Test
+    public void largeIconMultipleReferences_keptAfterParcelling() {
+        Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+        Notification n = new Notification.Builder(mContext).setLargeIcon(originalIcon).build();
+        assertSame(n.getLargeIcon(), originalIcon);
+
+        Notification q = writeAndReadParcelable(n);
+        assertNotSame(q.getLargeIcon(), n.getLargeIcon());
+
+        assertTrue(q.getLargeIcon().getBitmap().sameAs(n.getLargeIcon().getBitmap()));
+        assertSame(q.getLargeIcon(), q.extras.getParcelable(Notification.EXTRA_LARGE_ICON));
+    }
+
+    @Test
+    public void largeIconReferenceInExtrasOnly_keptAfterParcelling() {
+        Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+        Notification n = new Notification.Builder(mContext).build();
+        n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, originalIcon);
+        assertSame(n.getLargeIcon(), null);
+
+        Notification q = writeAndReadParcelable(n);
+        assertSame(q.getLargeIcon(), null);
+        assertTrue(((Icon) q.extras.getParcelable(Notification.EXTRA_LARGE_ICON)).getBitmap()
+                .sameAs(originalIcon.getBitmap()));
+    }
+
+    @Test
     public void allPendingIntents_recollectedAfterReusingBuilder() {
         PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), 0);
         PendingIntent intent2 = PendingIntent.getActivity(mContext, 0, new Intent("test2"), 0);
@@ -187,4 +222,15 @@
                 .setContentText("Text")
                 .setStyle(new Notification.MediaStyle().setMediaSession(session.getSessionToken()));
     }
+
+    /**
+      * Writes an arbitrary {@link Parcelable} into a {@link Parcel} using its writeToParcel
+      * method before reading it out again to check that it was sent properly.
+      */
+    private static <T extends Parcelable> T writeAndReadParcelable(T original) {
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(original, /* flags */ 0);
+        p.setDataPosition(0);
+        return p.readParcelable(/* classLoader */ null);
+    }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index ea2dd59..b1f8552 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -42,7 +42,7 @@
         ClientTransactionHandler clientTransactionHandler = mock(ClientTransactionHandler.class);
         IBinder token = mock(IBinder.class);
 
-        ClientTransaction transaction = new ClientTransaction(null /* client */,
+        ClientTransaction transaction = ClientTransaction.obtain(null /* client */,
                 token /* activityToken */);
         transaction.addCallback(callback1);
         transaction.addCallback(callback2);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
new file mode 100644
index 0000000..aefc47e
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 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 android.app.servertransaction;
+
+import static android.app.servertransaction.TestUtils.config;
+import static android.app.servertransaction.TestUtils.referrerIntentList;
+import static android.app.servertransaction.TestUtils.resultInfoList;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class ObjectPoolTests {
+
+    // 1. Check if two obtained objects from pool are not the same.
+    // 2. Check if the state of the object is cleared after recycling.
+    // 3. Check if the same object is obtained from pool after recycling.
+
+    @Test
+    public void testRecycleActivityConfigurationChangeItem() {
+        ActivityConfigurationChangeItem emptyItem = ActivityConfigurationChangeItem.obtain(null);
+        ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config());
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        ActivityConfigurationChangeItem item2 = ActivityConfigurationChangeItem.obtain(config());
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecycleActivityResultItem() {
+        ActivityResultItem emptyItem = ActivityResultItem.obtain(null);
+        ActivityResultItem item = ActivityResultItem.obtain(resultInfoList());
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        ActivityResultItem item2 = ActivityResultItem.obtain(resultInfoList());
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecycleConfigurationChangeItem() {
+        ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null);
+        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config());
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config());
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecycleDestroyActivityItem() {
+        DestroyActivityItem emptyItem = DestroyActivityItem.obtain(false, 0);
+        DestroyActivityItem item = DestroyActivityItem.obtain(true, 117);
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        DestroyActivityItem item2 = DestroyActivityItem.obtain(true, 14);
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecycleLaunchActivityItem() {
+        Intent intent = new Intent("action");
+        int ident = 57;
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.flags = 42;
+        activityInfo.maxAspectRatio = 2.4f;
+        activityInfo.launchToken = "token";
+        activityInfo.applicationInfo = new ApplicationInfo();
+        activityInfo.packageName = "packageName";
+        activityInfo.name = "name";
+        Configuration overrideConfig = new Configuration();
+        overrideConfig.assetsSeq = 5;
+        CompatibilityInfo compat = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+        String referrer = "referrer";
+        int procState = 4;
+        Bundle bundle = new Bundle();
+        bundle.putString("key", "value");
+        PersistableBundle persistableBundle = new PersistableBundle();
+        persistableBundle.putInt("k", 4);
+
+        LaunchActivityItem emptyItem = LaunchActivityItem.obtain(null, 0, null, null, null, null,
+                null, null, 0, null, null, null, null, false, null);
+        LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo,
+                config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
+                procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
+                true /* isForward */, null /* profilerInfo */);
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        LaunchActivityItem item2 = LaunchActivityItem.obtain(intent, ident, activityInfo,
+                config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
+                procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
+                true /* isForward */, null /* profilerInfo */);
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecycleMoveToDisplayItem() {
+        MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(0, null);
+        MoveToDisplayItem item = MoveToDisplayItem.obtain(4, config());
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        MoveToDisplayItem item2 = MoveToDisplayItem.obtain(3, config());
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecycleMultiWindowModeChangeItem() {
+        MultiWindowModeChangeItem emptyItem = MultiWindowModeChangeItem.obtain(false, null);
+        MultiWindowModeChangeItem item = MultiWindowModeChangeItem.obtain(true, config());
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        MultiWindowModeChangeItem item2 = MultiWindowModeChangeItem.obtain(true, config());
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecycleNewIntentItem() {
+        NewIntentItem emptyItem = NewIntentItem.obtain(null, false);
+        NewIntentItem item = NewIntentItem.obtain(referrerIntentList(), true);
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        NewIntentItem item2 = NewIntentItem.obtain(referrerIntentList(), true);
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecyclePauseActivityItemItem() {
+        PauseActivityItem emptyItem = PauseActivityItem.obtain(false, false, 0, false);
+        PauseActivityItem item = PauseActivityItem.obtain(true, true, 5, true);
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        PauseActivityItem item2 = PauseActivityItem.obtain(true, false, 5, true);
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecyclePipModeChangeItem() {
+        PipModeChangeItem emptyItem = PipModeChangeItem.obtain(false, null);
+        PipModeChangeItem item = PipModeChangeItem.obtain(true, config());
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        PipModeChangeItem item2 = PipModeChangeItem.obtain(true, config());
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecycleResumeActivityItem() {
+        ResumeActivityItem emptyItem = ResumeActivityItem.obtain(false);
+        ResumeActivityItem item = ResumeActivityItem.obtain(3, true);
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        ResumeActivityItem item2 = ResumeActivityItem.obtain(2, true);
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecycleStopItem() {
+        StopActivityItem emptyItem = StopActivityItem.obtain(false, 0);
+        StopActivityItem item = StopActivityItem.obtain(true, 4);
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        StopActivityItem item2 = StopActivityItem.obtain(true, 3);
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecycleWindowVisibleItem() {
+        WindowVisibilityItem emptyItem = WindowVisibilityItem.obtain(false);
+        WindowVisibilityItem item = WindowVisibilityItem.obtain(true);
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        WindowVisibilityItem item2 = WindowVisibilityItem.obtain(true);
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+
+    @Test
+    public void testRecycleClientTransaction() {
+        ClientTransaction emptyItem = ClientTransaction.obtain(null, null);
+        ClientTransaction item = ClientTransaction.obtain(null, new Binder());
+        assertNotSame(item, emptyItem);
+        assertFalse(item.equals(emptyItem));
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        ClientTransaction item2 = ClientTransaction.obtain(null, new Binder());
+        assertSame(item, item2);
+        assertFalse(item2.equals(emptyItem));
+    }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
new file mode 100644
index 0000000..e923516
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 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 android.app.servertransaction;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
+import android.app.ResultInfo;
+import android.content.Intent;
+import android.content.res.Configuration;
+
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class TestUtils {
+
+    static Configuration config() {
+        Configuration config = new Configuration();
+        config.densityDpi = 10;
+        config.fontScale = 0.3f;
+        config.screenHeightDp = 15;
+        config.orientation = ORIENTATION_LANDSCAPE;
+        return config;
+    }
+
+    static List<ResultInfo> resultInfoList() {
+        String resultWho1 = "resultWho1";
+        int requestCode1 = 7;
+        int resultCode1 = 4;
+        Intent data1 = new Intent("action1");
+        ResultInfo resultInfo1 = new ResultInfo(resultWho1, requestCode1, resultCode1, data1);
+
+        String resultWho2 = "resultWho2";
+        int requestCode2 = 8;
+        int resultCode2 = 6;
+        Intent data2 = new Intent("action2");
+        ResultInfo resultInfo2 = new ResultInfo(resultWho2, requestCode2, resultCode2, data2);
+
+        List<ResultInfo> resultInfoList = new ArrayList<>();
+        resultInfoList.add(resultInfo1);
+        resultInfoList.add(resultInfo2);
+
+        return resultInfoList;
+    }
+
+    static List<ReferrerIntent> referrerIntentList() {
+        Intent intent1 = new Intent("action1");
+        ReferrerIntent referrerIntent1 = new ReferrerIntent(intent1, "referrer1");
+
+        Intent intent2 = new Intent("action2");
+        ReferrerIntent referrerIntent2 = new ReferrerIntent(intent2, "referrer2");
+
+        List<ReferrerIntent> referrerIntents = new ArrayList<>();
+        referrerIntents.add(referrerIntent1);
+        referrerIntents.add(referrerIntent2);
+
+        return referrerIntents;
+    }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index d283004..e575650 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -38,6 +38,7 @@
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -51,6 +52,7 @@
 /** Test {@link TransactionExecutor} logic. */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@Presubmit
 public class TransactionExecutorTests {
 
     private TransactionExecutor mExecutor;
@@ -171,7 +173,7 @@
         ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
         IBinder token = mock(IBinder.class);
 
-        ClientTransaction transaction = new ClientTransaction(null /* client */,
+        ClientTransaction transaction = ClientTransaction.obtain(null /* client */,
                 token /* activityToken */);
         transaction.addCallback(callback1);
         transaction.addCallback(callback2);
@@ -188,10 +190,10 @@
 
     @Test
     public void testRequiredStateResolution() {
-        ActivityResultItem activityResultItem = new ActivityResultItem(new ArrayList<>());
+        ActivityResultItem activityResultItem = ActivityResultItem.obtain(new ArrayList<>());
 
         IBinder token = mock(IBinder.class);
-        ClientTransaction transaction = new ClientTransaction(null /* client */,
+        ClientTransaction transaction = ClientTransaction.obtain(null /* client */,
                 token /* activityToken */);
         transaction.addCallback(activityResultItem);
 
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index f6c656d..4b1f2da 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -16,7 +16,9 @@
 
 package android.app.servertransaction;
 
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.app.servertransaction.TestUtils.config;
+import static android.app.servertransaction.TestUtils.referrerIntentList;
+import static android.app.servertransaction.TestUtils.resultInfoList;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
@@ -57,7 +59,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -77,7 +78,7 @@
     @Test
     public void testConfigurationChange() {
         // Write to parcel
-        ConfigurationChangeItem item = new ConfigurationChangeItem(config());
+        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -90,7 +91,7 @@
     @Test
     public void testActivityConfigChange() {
         // Write to parcel
-        ActivityConfigurationChangeItem item = new ActivityConfigurationChangeItem(config());
+        ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -104,7 +105,7 @@
     @Test
     public void testMoveToDisplay() {
         // Write to parcel
-        MoveToDisplayItem item = new MoveToDisplayItem(4 /* targetDisplayId */, config());
+        MoveToDisplayItem item = MoveToDisplayItem.obtain(4 /* targetDisplayId */, config());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -117,7 +118,7 @@
     @Test
     public void testNewIntent() {
         // Write to parcel
-        NewIntentItem item = new NewIntentItem(referrerIntentList(), true /* pause */);
+        NewIntentItem item = NewIntentItem.obtain(referrerIntentList(), true /* pause */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -130,7 +131,7 @@
     @Test
     public void testActivityResult() {
         // Write to parcel
-        ActivityResultItem item = new ActivityResultItem(resultInfoList());
+        ActivityResultItem item = ActivityResultItem.obtain(resultInfoList());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -143,7 +144,7 @@
     @Test
     public void testPipModeChange() {
         // Write to parcel
-        PipModeChangeItem item = new PipModeChangeItem(true /* isInPipMode */, config());
+        PipModeChangeItem item = PipModeChangeItem.obtain(true /* isInPipMode */, config());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -156,7 +157,7 @@
     @Test
     public void testMultiWindowModeChange() {
         // Write to parcel
-        MultiWindowModeChangeItem item = new MultiWindowModeChangeItem(
+        MultiWindowModeChangeItem item = MultiWindowModeChangeItem.obtain(
                 true /* isInMultiWindowMode */, config());
         writeAndPrepareForReading(item);
 
@@ -171,7 +172,7 @@
     @Test
     public void testWindowVisibilityChange() {
         // Write to parcel
-        WindowVisibilityItem item = new WindowVisibilityItem(true /* showWindow */);
+        WindowVisibilityItem item = WindowVisibilityItem.obtain(true /* showWindow */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -181,7 +182,7 @@
         assertTrue(item.equals(result));
 
         // Check different value
-        item = new WindowVisibilityItem(false);
+        item = WindowVisibilityItem.obtain(false);
 
         mParcel = Parcel.obtain();
         writeAndPrepareForReading(item);
@@ -195,7 +196,7 @@
 
     @Test
     public void testDestroy() {
-        DestroyActivityItem item = new DestroyActivityItem(true /* finished */,
+        DestroyActivityItem item = DestroyActivityItem.obtain(true /* finished */,
                 135 /* configChanges */);
         writeAndPrepareForReading(item);
 
@@ -228,7 +229,7 @@
         PersistableBundle persistableBundle = new PersistableBundle();
         persistableBundle.putInt("k", 4);
 
-        LaunchActivityItem item = new LaunchActivityItem(intent, ident, activityInfo,
+        LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo,
                 config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
                 procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
                 true /* isForward */, null /* profilerInfo */);
@@ -244,8 +245,8 @@
     @Test
     public void testPause() {
         // Write to parcel
-        PauseActivityItem item = new PauseActivityItem(true /* finished */, true /* userLeaving */,
-                135 /* configChanges */, true /* dontReport */);
+        PauseActivityItem item = PauseActivityItem.obtain(true /* finished */,
+                true /* userLeaving */, 135 /* configChanges */, true /* dontReport */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -258,7 +259,8 @@
     @Test
     public void testResume() {
         // Write to parcel
-        ResumeActivityItem item = new ResumeActivityItem(27 /* procState */, true /* isForward */);
+        ResumeActivityItem item = ResumeActivityItem.obtain(27 /* procState */,
+                true /* isForward */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -271,7 +273,8 @@
     @Test
     public void testStop() {
         // Write to parcel
-        StopActivityItem item = new StopActivityItem(true /* showWindow */, 14 /* configChanges */);
+        StopActivityItem item = StopActivityItem.obtain(true /* showWindow */,
+                14 /* configChanges */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -284,16 +287,17 @@
     @Test
     public void testClientTransaction() {
         // Write to parcel
-        WindowVisibilityItem callback1 = new WindowVisibilityItem(true);
-        ActivityConfigurationChangeItem callback2 = new ActivityConfigurationChangeItem(config());
+        WindowVisibilityItem callback1 = WindowVisibilityItem.obtain(true);
+        ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
+                config());
 
-        StopActivityItem lifecycleRequest = new StopActivityItem(true /* showWindow */,
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(true /* showWindow */,
                 78 /* configChanges */);
 
         IApplicationThread appThread = new StubAppThread();
         Binder activityToken = new Binder();
 
-        ClientTransaction transaction = new ClientTransaction(appThread, activityToken);
+        ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
         transaction.addCallback(callback1);
         transaction.addCallback(callback2);
         transaction.setLifecycleStateRequest(lifecycleRequest);
@@ -310,13 +314,14 @@
     @Test
     public void testClientTransactionCallbacksOnly() {
         // Write to parcel
-        WindowVisibilityItem callback1 = new WindowVisibilityItem(true);
-        ActivityConfigurationChangeItem callback2 = new ActivityConfigurationChangeItem(config());
+        WindowVisibilityItem callback1 = WindowVisibilityItem.obtain(true);
+        ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
+                config());
 
         IApplicationThread appThread = new StubAppThread();
         Binder activityToken = new Binder();
 
-        ClientTransaction transaction = new ClientTransaction(appThread, activityToken);
+        ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
         transaction.addCallback(callback1);
         transaction.addCallback(callback2);
 
@@ -332,13 +337,13 @@
     @Test
     public void testClientTransactionLifecycleOnly() {
         // Write to parcel
-        StopActivityItem lifecycleRequest = new StopActivityItem(true /* showWindow */,
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(true /* showWindow */,
                 78 /* configChanges */);
 
         IApplicationThread appThread = new StubAppThread();
         Binder activityToken = new Binder();
 
-        ClientTransaction transaction = new ClientTransaction(appThread, activityToken);
+        ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
         transaction.setLifecycleStateRequest(lifecycleRequest);
 
         writeAndPrepareForReading(transaction);
@@ -350,49 +355,6 @@
         assertTrue(transaction.equals(result));
     }
 
-    private static List<ResultInfo> resultInfoList() {
-        String resultWho1 = "resultWho1";
-        int requestCode1 = 7;
-        int resultCode1 = 4;
-        Intent data1 = new Intent("action1");
-        ResultInfo resultInfo1 = new ResultInfo(resultWho1, requestCode1, resultCode1, data1);
-
-        String resultWho2 = "resultWho2";
-        int requestCode2 = 8;
-        int resultCode2 = 6;
-        Intent data2 = new Intent("action2");
-        ResultInfo resultInfo2 = new ResultInfo(resultWho2, requestCode2, resultCode2, data2);
-
-        List<ResultInfo> resultInfoList = new ArrayList<>();
-        resultInfoList.add(resultInfo1);
-        resultInfoList.add(resultInfo2);
-
-        return resultInfoList;
-    }
-
-    private static List<ReferrerIntent> referrerIntentList() {
-        Intent intent1 = new Intent("action1");
-        ReferrerIntent referrerIntent1 = new ReferrerIntent(intent1, "referrer1");
-
-        Intent intent2 = new Intent("action2");
-        ReferrerIntent referrerIntent2 = new ReferrerIntent(intent2, "referrer2");
-
-        List<ReferrerIntent> referrerIntents = new ArrayList<>();
-        referrerIntents.add(referrerIntent1);
-        referrerIntents.add(referrerIntent2);
-
-        return referrerIntents;
-    }
-
-    private static Configuration config() {
-        Configuration config = new Configuration();
-        config.densityDpi = 10;
-        config.fontScale = 0.3f;
-        config.screenHeightDp = 15;
-        config.orientation = ORIENTATION_LANDSCAPE;
-        return config;
-    }
-
     /** Write to {@link #mParcel} and reset its position to prepare for reading from the start. */
     private void writeAndPrepareForReading(Parcelable parcelable) {
         parcelable.writeToParcel(mParcel, 0 /* flags */);
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 77c6c3e..0982a4b 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -194,6 +194,7 @@
                     Settings.Global.DROPBOX_RESERVE_PERCENT,
                     Settings.Global.DROPBOX_TAG_PREFIX,
                     Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
+                    Settings.Global.EMULATE_DISPLAY_CUTOUT,
                     Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
                     Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION,
                     Settings.Global.ENABLE_CELLULAR_ON_BOOT,
diff --git a/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java b/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
index b8c4123..0b1b333 100644
--- a/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
+++ b/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
@@ -18,6 +18,7 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
 import android.content.Context;
@@ -59,7 +60,7 @@
 
     @Test
     public void testGetFlag_enabled_shouldReturnTrue() {
-        SystemProperties.set(FeatureFlagUtils.FFLAG_PREFIX + TEST_FEATURE_NAME, "true");
+        FeatureFlagUtils.getAllFeatureFlags().put(TEST_FEATURE_NAME, "true");
 
         assertTrue(FeatureFlagUtils.isEnabled(mContext, TEST_FEATURE_NAME));
     }
@@ -96,7 +97,11 @@
 
     @Test
     public void testGetFlag_notSet_shouldReturnFalse() {
-        assertFalse(FeatureFlagUtils.isEnabled(mContext, TEST_FEATURE_NAME));
+        assertFalse(FeatureFlagUtils.isEnabled(mContext, TEST_FEATURE_NAME + "does_not_exist"));
     }
 
+    @Test
+    public void getAllFeatureFlags_shouldNotBeNull() {
+        assertNotNull(FeatureFlagUtils.getAllFeatureFlags());
+    }
 }
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk
index 47ee2cf..1d9f624 100644
--- a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk
@@ -20,8 +20,8 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-common mockwebserver junit legacy-android-test
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := android-common mockwebserver junit
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
 
 LOCAL_PACKAGE_NAME := DownloadManagerTestApp
 
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
index b35cbae..193e601 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 LOCAL_SDK_VERSION := current
 
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
index 06812b5..9f44767 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 LOCAL_SDK_VERSION := current
 
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
index 48bceaf..b4d354c 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 LOCAL_SDK_VERSION := current
 
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
index 569d102..969e588 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 LOCAL_SDK_VERSION := current
 
diff --git a/core/tests/notificationtests/Android.mk b/core/tests/notificationtests/Android.mk
index 0551eb6..30c2dca 100644
--- a/core/tests/notificationtests/Android.mk
+++ b/core/tests/notificationtests/Android.mk
@@ -8,12 +8,11 @@
 LOCAL_SRC_FILES := \
 	$(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
 LOCAL_PACKAGE_NAME := NotificationStressTests
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     junit \
-    legacy-android-test \
     ub-uiautomator
 
 include $(BUILD_PACKAGE)
diff --git a/keystore/tests/Android.mk b/keystore/tests/Android.mk
index 51adde4..1167f76 100644
--- a/keystore/tests/Android.mk
+++ b/keystore/tests/Android.mk
@@ -21,8 +21,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test \
-    legacy-android-test
+    android-support-test
 
 LOCAL_PACKAGE_NAME := KeystoreTests
 
diff --git a/media/mca/tests/Android.mk b/media/mca/tests/Android.mk
index eb451f7..394f542 100644
--- a/media/mca/tests/Android.mk
+++ b/media/mca/tests/Android.mk
@@ -4,8 +4,8 @@
 # We only want this apk build for tests.
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/media/tests/MediaFrameworkTest/Android.mk b/media/tests/MediaFrameworkTest/Android.mk
index 0d9f42b..145cde6 100644
--- a/media/tests/MediaFrameworkTest/Android.mk
+++ b/media/tests/MediaFrameworkTest/Android.mk
@@ -5,13 +5,12 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     mockito-target-minus-junit4 \
     android-support-test \
-    android-ex-camera2 \
-    legacy-android-test
+    android-ex-camera2
 
 LOCAL_PACKAGE_NAME := mediaframeworktest
 
diff --git a/nfc-extras/tests/Android.mk b/nfc-extras/tests/Android.mk
index d8fe5a6..be2bb53 100644
--- a/nfc-extras/tests/Android.mk
+++ b/nfc-extras/tests/Android.mk
@@ -22,7 +22,7 @@
     android.test.runner \
     com.android.nfc_extras
 
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.mk b/packages/CarrierDefaultApp/tests/unit/Android.mk
index 63bd0b1..6e120a6 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.mk
+++ b/packages/CarrierDefaultApp/tests/unit/Android.mk
@@ -19,9 +19,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_CERTIFICATE := platform
 
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.test.base
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test mockito-target-minus-junit4 legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test mockito-target-minus-junit4
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/packages/EasterEgg/Android.mk b/packages/EasterEgg/Android.mk
index d4c1e70..a825581 100644
--- a/packages/EasterEgg/Android.mk
+++ b/packages/EasterEgg/Android.mk
@@ -2,17 +2,23 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := optional
+
 LOCAL_STATIC_JAVA_LIBRARIES := \
+    jsr305
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
     android-support-v4 \
     android-support-v13 \
     android-support-dynamic-animation \
     android-support-v7-recyclerview \
     android-support-v7-preference \
     android-support-v7-appcompat \
-    android-support-v14-preference \
-    jsr305
+    android-support-v14-preference
+
+LOCAL_USE_AAPT2 := true
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := EasterEgg
 LOCAL_CERTIFICATE := platform
diff --git a/packages/ExtServices/tests/Android.mk b/packages/ExtServices/tests/Android.mk
index 92afbeb..1eb5847 100644
--- a/packages/ExtServices/tests/Android.mk
+++ b/packages/ExtServices/tests/Android.mk
@@ -5,14 +5,13 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_CERTIFICATE := platform
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     mockito-target-minus-junit4 \
     espresso-core \
     truth-prebuilt \
-    legacy-android-test \
     testables
 
 # Include all test java files.
diff --git a/packages/InputDevices/res/values-in/strings.xml b/packages/InputDevices/res/values-in/strings.xml
index 33b9ae3..f2409d4 100644
--- a/packages/InputDevices/res/values-in/strings.xml
+++ b/packages/InputDevices/res/values-in/strings.xml
@@ -25,7 +25,7 @@
     <string name="keyboard_layout_swedish" msgid="732959109088479351">"Swedia"</string>
     <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finlandia"</string>
     <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroasia"</string>
-    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Cheska"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Ceko"</string>
     <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estonia"</string>
     <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Hungaria"</string>
     <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandia"</string>
diff --git a/packages/MtpDocumentsProvider/tests/Android.mk b/packages/MtpDocumentsProvider/tests/Android.mk
index 148cd0d..ba346f4 100644
--- a/packages/MtpDocumentsProvider/tests/Android.mk
+++ b/packages/MtpDocumentsProvider/tests/Android.mk
@@ -3,8 +3,8 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 LOCAL_PACKAGE_NAME := MtpDocumentsProviderTests
 LOCAL_INSTRUMENTATION_FOR := MtpDocumentsProvider
 LOCAL_CERTIFICATE := media
diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk
index 49c5467..9d1c4ca 100644
--- a/packages/SettingsLib/common.mk
+++ b/packages/SettingsLib/common.mk
@@ -21,6 +21,10 @@
 LOCAL_STATIC_ANDROID_LIBRARIES += \
     android-support-v4 \
     apptoolkit-lifecycle-runtime \
+    android-support-v7-recyclerview \
+    android-support-v7-preference \
+    android-support-v7-appcompat \
+    android-support-v14-preference \
     SettingsLib
 else
 LOCAL_RESOURCE_DIR += $(call my-dir)/res
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index dd420b3..ed0958c 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -225,7 +225,7 @@
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Pokaži opcije za certifikaciju Bežičnog prikaza"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povećajte nivo Wi-Fi zapisivanja, pokazati po SSID RSSI Wi-Fi Picker"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kada je omogućeno, Wi-Fi veza će u slučaju slabog signala agresivnije predavati vezu za prijenos podataka na mobilnu vezu"</string>
-    <string name="wifi_allow_scan_with_traffic_summary" msgid="2575101424972686310">"Dozvoli/Zabrani Wi-Fi lutajuće skeniranje na osnovu količine podatkovnog prometa prisutnog na sučelju"</string>
+    <string name="wifi_allow_scan_with_traffic_summary" msgid="2575101424972686310">"Dozvoli/Zabrani Wi-Fi lutajuće skeniranje na osnovu količine podatkovnog prometa prisutnog na interfejsu"</string>
     <string name="select_logd_size_title" msgid="7433137108348553508">"Veličine bafera za zapisnik"</string>
     <string name="select_logd_size_dialog_title" msgid="1206769310236476760">"Odaberite veličine za Logger prema međumemoriji evidencije"</string>
     <string name="dev_logpersist_clear_warning_title" msgid="684806692440237967">"Želite li izbrisati trajnu pohranu zapisivača?"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index aeb0ddb..bb0a337 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -97,7 +97,7 @@
     <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"कनेक्‍ट केल्यावर पेअरींग तुमचे संपर्क आणि कॉल इतिहास यामध्ये अॅक्सेस देते."</string>
     <string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> शी जोडू शकलो नाही."</string>
     <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_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>
diff --git a/packages/SettingsLib/tests/integ/Android.mk b/packages/SettingsLib/tests/integ/Android.mk
index 7ace048..cfb3519d 100644
--- a/packages/SettingsLib/tests/integ/Android.mk
+++ b/packages/SettingsLib/tests/integ/Android.mk
@@ -20,18 +20,19 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.test.base
 
 LOCAL_JACK_FLAGS := --multi-dex native
 
 LOCAL_PACKAGE_NAME := SettingsLibTests
 LOCAL_COMPATIBILITY_SUITE := device-tests
 
+LOCAL_USE_AAPT2 := true
+
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     espresso-core \
     mockito-target-minus-junit4 \
-    legacy-android-test \
     truth-prebuilt
 
 # Code coverage puts us over the dex limit, so enable multi-dex for coverage-enabled builds
diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk
index bc1a834..2738027 100644
--- a/packages/SettingsLib/tests/robotests/Android.mk
+++ b/packages/SettingsLib/tests/robotests/Android.mk
@@ -28,6 +28,8 @@
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/res
 
+LOCAL_USE_AAPT2 := true
+
 include frameworks/base/packages/SettingsLib/common.mk
 
 include $(BUILD_PACKAGE)
diff --git a/packages/SettingsProvider/Android.mk b/packages/SettingsProvider/Android.mk
index 069e83a..0f2c5ab 100644
--- a/packages/SettingsProvider/Android.mk
+++ b/packages/SettingsProvider/Android.mk
@@ -7,7 +7,7 @@
     src/com/android/providers/settings/EventLogTags.logtags
 
 LOCAL_JAVA_LIBRARIES := telephony-common ims-common
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 LOCAL_PACKAGE_NAME := SettingsProvider
 LOCAL_CERTIFICATE := platform
diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml
index 9374d52..287a888 100644
--- a/packages/SettingsProvider/AndroidManifest.xml
+++ b/packages/SettingsProvider/AndroidManifest.xml
@@ -11,6 +11,8 @@
                  android:icon="@mipmap/ic_launcher_settings"
                  android:defaultToDeviceProtectedStorage="true"
                  android:directBootAware="true">
+        <uses-library android:name="android.test.runner" />
+
 
         <provider android:name="SettingsProvider"
                   android:authorities="settings"
diff --git a/packages/Shell/res/values-fa/strings.xml b/packages/Shell/res/values-fa/strings.xml
index 75e7760..d4bb3c6 100644
--- a/packages/Shell/res/values-fa/strings.xml
+++ b/packages/Shell/res/values-fa/strings.xml
@@ -23,8 +23,8 @@
     <string name="bugreport_updating_title" msgid="4423539949559634214">"اضافه کردن جزئیات به گزارش اشکال"</string>
     <string name="bugreport_updating_wait" msgid="3322151947853929470">"لطفاً منتظر بمانید..."</string>
     <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"گزارش مشکل به‌زودی در تلفن نشان داده می‌شود"</string>
-    <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"برای به اشتراک گذاشتن گزارش اشکالتان، انتخاب کنید"</string>
-    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"برای به اشتراک گذاشتن گزارش اشکال، ضربه بزنید"</string>
+    <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"برای هم‌رسانی گزارش اشکالتان، انتخاب کنید"</string>
+    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"برای هم‌رسانی گزارش اشکال، ضربه بزنید"</string>
     <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"انتخاب کنید تا گزارش اشکالتان بدون عکس صفحه‌نمایش به اشتراک گذاشته شود یا منتظر بمانید گرفتن عکس از صفحه‌نمایش تمام شود"</string>
     <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"برای اشتراک‌گذاری گزارش مشکل بدون عکس صفحه‌نمایش، ضربه بزنید یا صبر کنید تا عکس صفحه‌نمایش گرفته شود."</string>
     <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"برای اشتراک‌گذاری گزارش مشکل بدون عکس صفحه‌نمایش، ضربه بزنید یا صبر کنید تا عکس صفحه‌نمایش گرفته شود."</string>
diff --git a/packages/Shell/tests/Android.mk b/packages/Shell/tests/Android.mk
index 48b757c..7f24a38 100644
--- a/packages/Shell/tests/Android.mk
+++ b/packages/Shell/tests/Android.mk
@@ -6,14 +6,13 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     mockito-target-minus-junit4 \
     ub-uiautomator \
     junit \
-    legacy-android-test \
 
 LOCAL_PACKAGE_NAME := ShellTests
 LOCAL_COMPATIBILITY_SUITE := device-tests
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 8cc2ce2..5b8881c 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -15,6 +15,7 @@
 -keep class com.android.systemui.statusbar.tv.TvStatusBar
 -keep class com.android.systemui.car.CarSystemUIFactory
 -keep class com.android.systemui.SystemUIFactory
+-keep class * extends com.android.systemui.SystemUI
 
 -keepclassmembers class ** {
     public void onBusEvent(**);
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index 4f6fcb9..70d13e6 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -59,8 +59,8 @@
     <string name="kg_wrong_password" msgid="4580683060277329277">"Palavra-passe incorreta"</string>
     <string name="kg_wrong_pin" msgid="4785660766909463466">"PIN incorreto"</string>
     <plurals name="kg_too_many_failed_attempts_countdown" formatted="false" msgid="4368805541257003755">
-      <item quantity="one">Tente novamente dentro de 1 segundo.</item>
       <item quantity="other">Tente novamente dentro de <xliff:g id="NUMBER">%d</xliff:g> segundos.</item>
+      <item quantity="one">Tente novamente dentro de 1 segundo.</item>
     </plurals>
     <string name="kg_pattern_instructions" msgid="5547646893001491340">"Desenhe o seu padrão"</string>
     <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Introduza o PIN do cartão SIM."</string>
@@ -97,13 +97,13 @@
     <string name="kg_failed_attempts_almost_at_login" product="default" msgid="8364140853305528449">"Desenhou a sequência de desbloqueio incorretamente <xliff:g id="NUMBER_0">%1$d</xliff:g> vezes. Após mais <xliff:g id="NUMBER_1">%2$d</xliff:g> tentativas sem êxito, ser-lhe-á pedido para desbloquear o telemóvel através de uma conta de email.\n\n Tente novamente dentro de <xliff:g id="NUMBER_2">%3$d</xliff:g> segundos."</string>
     <string name="kg_password_wrong_pin_code_pukked" msgid="3389829202093674267">"Código PIN do cartão SIM incorreto. Tem de contactar o seu operador para desbloquear o dispositivo."</string>
     <plurals name="kg_password_wrong_pin_code" formatted="false" msgid="4314341367727055967">
-      <item quantity="one">Código PIN do cartão SIM incorreto. Tem mais <xliff:g id="NUMBER_0">%d</xliff:g> tentativa antes de precisar de contactar o seu operador para desbloquear o dispositivo.</item>
       <item quantity="other">Código PIN do cartão SIM incorreto. Tem mais <xliff:g id="NUMBER_1">%d</xliff:g> tentativas.</item>
+      <item quantity="one">Código PIN do cartão SIM incorreto. Tem mais <xliff:g id="NUMBER_0">%d</xliff:g> tentativa antes de precisar de contactar o seu operador para desbloquear o dispositivo.</item>
     </plurals>
     <string name="kg_password_wrong_puk_code_dead" msgid="3329017604125179374">"Cartão SIM inutilizável. Contacte o seu operador."</string>
     <plurals name="kg_password_wrong_puk_code" formatted="false" msgid="2287504898931957513">
-      <item quantity="one">Código PUK do cartão SIM incorreto. Tem mais <xliff:g id="NUMBER_0">%d</xliff:g> tentativa antes de o cartão SIM ficar permanentemente inutilizável.</item>
       <item quantity="other">Código PUK do cartão SIM incorreto. Tem mais <xliff:g id="NUMBER_1">%d</xliff:g> tentativas antes de o cartão SIM ficar permanentemente inutilizável.</item>
+      <item quantity="one">Código PUK do cartão SIM incorreto. Tem mais <xliff:g id="NUMBER_0">%d</xliff:g> tentativa antes de o cartão SIM ficar permanentemente inutilizável.</item>
     </plurals>
     <string name="kg_password_pin_failed" msgid="8769990811451236223">"Falha ao introduzir o PIN do cartão SIM!"</string>
     <string name="kg_password_puk_failed" msgid="1331621440873439974">"Falha ao introduzir o PUK do cartão SIM!"</string>
@@ -123,16 +123,16 @@
     <string name="kg_prompt_reason_device_admin" msgid="3452168247888906179">"Dispositivo bloqueado pelo gestor"</string>
     <string name="kg_prompt_reason_user_request" msgid="8236951765212462286">"O dispositivo foi bloqueado manualmente"</string>
     <plurals name="kg_prompt_reason_time_pattern" formatted="false" msgid="71299470072448533">
-      <item quantity="one">O dispositivo não é desbloqueado há <xliff:g id="NUMBER_0">%d</xliff:g> hora. Confirme o padrão.</item>
       <item quantity="other">O dispositivo não é desbloqueado há <xliff:g id="NUMBER_1">%d</xliff:g> horas. Confirme o padrão.</item>
+      <item quantity="one">O dispositivo não é desbloqueado há <xliff:g id="NUMBER_0">%d</xliff:g> hora. Confirme o padrão.</item>
     </plurals>
     <plurals name="kg_prompt_reason_time_pin" formatted="false" msgid="34586942088144385">
-      <item quantity="one">O dispositivo não é desbloqueado há <xliff:g id="NUMBER_0">%d</xliff:g> hora. Confirme o PIN.</item>
       <item quantity="other">O dispositivo não é desbloqueado há <xliff:g id="NUMBER_1">%d</xliff:g> horas. Confirme o PIN.</item>
+      <item quantity="one">O dispositivo não é desbloqueado há <xliff:g id="NUMBER_0">%d</xliff:g> hora. Confirme o PIN.</item>
     </plurals>
     <plurals name="kg_prompt_reason_time_password" formatted="false" msgid="257297696215346527">
-      <item quantity="one">O dispositivo não é desbloqueado há <xliff:g id="NUMBER_0">%d</xliff:g> hora. Confirme a palavra-passe.</item>
       <item quantity="other">O dispositivo não é desbloqueado há <xliff:g id="NUMBER_1">%d</xliff:g> horas. Confirme a palavra-passe.</item>
+      <item quantity="one">O dispositivo não é desbloqueado há <xliff:g id="NUMBER_0">%d</xliff:g> hora. Confirme a palavra-passe.</item>
     </plurals>
     <string name="fingerprint_not_recognized" msgid="348813995267914625">"Não reconhecido"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 3dd0e6c..3a41681 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -367,4 +367,20 @@
     <!-- Fingerprint hint message when finger was not recognized.-->
     <string name="fingerprint_not_recognized">Not recognized</string>
 
+    <!-- Instructions telling the user remaining times when enter SIM PIN view.  -->
+    <plurals name="kg_password_default_pin_message">
+        <item quantity="one">Enter SIM PIN, you have <xliff:g id="number">%d</xliff:g> remaining
+attempt before you must contact your carrier to unlock your device.</item>
+        <item quantity="other">Enter SIM PIN, you have <xliff:g id="number">%d</xliff:g> remaining
+attempts.</item>
+    </plurals>
+
+    <!-- Instructions telling the user remaining times when enter SIM PUK view.  -->
+    <plurals name="kg_password_default_puk_message">
+        <item quantity="one">SIM is now disabled. Enter PUK code to continue. You have <xliff:g id="
+number">%d</xliff:g> remaining attempt before SIM becomes permanently unusable. Contact carrier for details.</item>
+        <item quantity="other">SIM is now disabled. Enter PUK code to continue. You have <xliff:g id="
+number">%d</xliff:g> remaining attempts before SIM becomes permanently unusable. Contact carrier for details.</item>
+    </plurals>
+
 </resources>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index fac254a..f563794 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.HardwareUiLayout
+<com.android.systemui.volume.VolumeUiLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -21,18 +21,19 @@
     android:clipChildren="false" >
     <RelativeLayout
         android:id="@+id/volume_dialog"
-        android:layout_width="@dimen/volume_dialog_panel_width"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical|end"
         android:paddingTop="@dimen/volume_row_padding_bottom"
-        android:layout_margin="12dp"
-        android:background="?android:attr/actionBarItemBackground"
-        android:translationZ="8dp" >
+        android:background="@drawable/rounded_full_bg_bottom"
+        android:translationZ="8dp"
+        android:clipChildren="false" >
 
         <LinearLayout
             android:id="@+id/volume_dialog_content"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:clipChildren="false"
+            android:clipToPadding="false"
             android:orientation="vertical" >
 
             <LinearLayout
@@ -42,7 +43,55 @@
                 android:orientation="vertical" >
                 <!-- volume rows added and removed here! :-) -->
             </LinearLayout>
-        </LinearLayout>
 
+            <!-- special row for ringer mode -->
+            <RelativeLayout
+                android:id="@+id/ringer_mode"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/rounded_bg_full"
+                android:clipChildren="false"
+                android:clipToPadding="false"
+                android:layout_margin="10dp">
+
+                <com.android.keyguard.AlphaOptimizedImageButton
+                    android:id="@+id/ringer_icon"
+                    style="@style/VolumeButtons"
+                    android:background="?android:selectableItemBackgroundBorderless"
+                    android:layout_width="@dimen/volume_button_size"
+                    android:layout_height="@dimen/volume_button_size"
+                    android:layout_alignParentStart="true"
+                    android:layout_centerVertical="true"
+                    android:soundEffectsEnabled="false" />
+
+                <TextView
+                    android:id="@+id/ringer_title"
+                    android:text="@string/stream_ring"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:ellipsize="end"
+                    android:maxLines="1"
+                    android:layout_alignParentStart="true"
+                    android:layout_centerVertical="true"
+                    android:layout_toEndOf="@+id/ringer_icon"
+                    android:layout_marginStart="64dp"
+                    android:textColor="?android:attr/colorControlNormal"
+                    android:textAppearance="?android:attr/textAppearanceSmall"
+                    android:paddingStart="@dimen/volume_row_header_padding_start" />
+
+                <TextView
+                    android:id="@+id/ringer_status"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:ellipsize="end"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginEnd="14dp"
+                    android:maxLines="1"
+                    android:textColor="?android:attr/colorControlNormal"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            </RelativeLayout>
+        </LinearLayout>
     </RelativeLayout>
-</com.android.systemui.HardwareUiLayout>
+</com.android.systemui.volume.VolumeUiLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 581ac73..5770779 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -34,14 +34,14 @@
     <string name="status_bar_latest_events_title" msgid="6594767438577593172">"বিজ্ঞপ্তিগুলি"</string>
     <string name="battery_low_title" msgid="6456385927409742437">"ব্যাটারি কম"</string>
     <string name="battery_low_percent_format" msgid="2900940511201380775">"<xliff:g id="PERCENTAGE">%s</xliff:g> অবশিষ্ট আছে"</string>
-    <string name="battery_low_percent_format_saver_started" msgid="6859235584035338833">"<xliff:g id="PERCENTAGE">%s</xliff:g> অবশিষ্ট আছে৷ ব্যাটারি সাশ্রয়কারী চালু আছে৷"</string>
+    <string name="battery_low_percent_format_saver_started" msgid="7879389868952879166">"<xliff:g id="PERCENTAGE">%s</xliff:g> অবশিষ্ট আছে। ব্যাটারি সেভার চালু আছে।"</string>
     <string name="invalid_charger" msgid="4549105996740522523">"USB চার্জিং সমর্থিত নয়৷\nকেবলমাত্র সরবহারকৃত চার্জার ব্যবহার করুন৷"</string>
     <string name="invalid_charger_title" msgid="3515740382572798460">"USB চার্জিং সমর্থিত নয়।"</string>
     <string name="invalid_charger_text" msgid="5474997287953892710">"শুধুমাত্র সরবরাহকৃত চার্জার ব্যবহার করুন।"</string>
     <string name="battery_low_why" msgid="4553600287639198111">"সেটিংস"</string>
-    <string name="battery_saver_confirmation_title" msgid="5299585433050361634">"ব্যাটারি সঞ্চয়কারী চালু করবেন?"</string>
+    <string name="battery_saver_confirmation_title" msgid="2052100465684817154">"ব্যাটারি সেভার চালু করবেন?"</string>
     <string name="battery_saver_confirmation_ok" msgid="7507968430447930257">"চালু করুন"</string>
-    <string name="battery_saver_start_action" msgid="5576697451677486320">"ব্যাটারি সঞ্চয়কারী চালু"</string>
+    <string name="battery_saver_start_action" msgid="8187820911065797519">"ব্যাটারি সেভার চালু করুন"</string>
     <string name="status_bar_settings_settings_button" msgid="3023889916699270224">"সেটিংস"</string>
     <string name="status_bar_settings_wifi_button" msgid="1733928151698311923">"ওয়াই-ফাই"</string>
     <string name="status_bar_settings_auto_rotation" msgid="3790482541357798421">"স্বতঃ-ঘূর্ণায়মান স্ক্রিন"</string>
@@ -243,8 +243,6 @@
     <string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"ডেট বিরতি দেওয়া হয়েছে"</string>
     <string name="data_usage_disabled_dialog" msgid="4919541636934603816">"আপনার সেট করা ডেটার সীমা ফুরিয়ে গেছে। আপনি এখন আর মোবাইল ডেটা ব্যবহার করতে পারবেন না।\n\nযদি আপনি আবার শুরু করেন, ডেটা ব্যবহারের জন্য মূল্য প্রযোজ্য হতে পারে।"</string>
     <string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"পুনঃসূচনা করুন"</string>
-    <string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"কোনো ইন্টারনেট সংযোগ নেই"</string>
-    <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"ওয়াই-ফাই সংযুক্ত হয়েছে"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"GPS এর জন্য অনুসন্ধান করা হচ্ছে"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"GPS এর দ্বারা সেট করা অবস্থান"</string>
     <string name="accessibility_location_active" msgid="2427290146138169014">"অবস্থান অনুরোধ সক্রিয় রয়েছে"</string>
@@ -401,9 +399,9 @@
     <string name="user_remove_user_title" msgid="4681256956076895559">"ব্যবহারকারী সরাবেন?"</string>
     <string name="user_remove_user_message" msgid="1453218013959498039">"এই ব্যবহারকারীর সমস্ত অ্যাপ্লিকেশান ও ডেটা মুছে ফেলা হবে।"</string>
     <string name="user_remove_user_remove" msgid="7479275741742178297">"সরান"</string>
-    <string name="battery_saver_notification_title" msgid="237918726750955859">"ব্যাটারি সেভার চালু রয়েছে"</string>
+    <string name="battery_saver_notification_title" msgid="8614079794522291840">"ব্যাটারি সেভার চালু আছে"</string>
     <string name="battery_saver_notification_text" msgid="820318788126672692">"কার্য-সম্পাদনা ও পশ্চাদপট ডেটাকে কমিয়ে দেয়"</string>
-    <string name="battery_saver_notification_action_text" msgid="109158658238110382">"ব্যাটারি সঞ্চয়কারী বন্ধ করুন"</string>
+    <string name="battery_saver_notification_action_text" msgid="132118784269455533">"ব্যাটারি সেভার বন্ধ করুন"</string>
     <string name="media_projection_dialog_text" msgid="3071431025448218928">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> আপনার স্ক্রীনে দেখানো সব কিছু ক্যাপচার করা শুরু করবে।"</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"আর দেখাবেন না"</string>
     <string name="clear_all_notifications_text" msgid="814192889771462828">"সবকিছু সাফ করুন"</string>
@@ -582,8 +580,8 @@
       <item quantity="other">%d মিনিট</item>
     </plurals>
     <string name="battery_panel_title" msgid="7944156115535366613">"ব্যাটারির ব্যবহার"</string>
-    <string name="battery_detail_charging_summary" msgid="4055327085770378335">"চার্জ করার সময় ব্যাটারি সেভার কাজ করে না"</string>
-    <string name="battery_detail_switch_title" msgid="8763441006881907058">"ব্যাটারি সেভার"</string>
+    <string name="battery_detail_charging_summary" msgid="1279095653533044008">"চার্জ করার সময় ব্যাটারি সেভার উপলব্ধ নয়"</string>
+    <string name="battery_detail_switch_title" msgid="6285872470260795421">"ব্যাটারি সেভার"</string>
     <string name="battery_detail_switch_summary" msgid="9049111149407626804">"কার্য-সম্পাদনা ও পশ্চাদপট ডেটাকে কমিয়ে দেয়"</string>
     <string name="keyboard_key_button_template" msgid="6230056639734377300">"<xliff:g id="NAME">%1$s</xliff:g> বোতাম"</string>
     <string name="keyboard_key_home" msgid="2243500072071305073">"হোম"</string>
@@ -732,10 +730,10 @@
     <string name="pip_skip_to_prev" msgid="1955311326688637914">"পিছনে যাওয়ার জন্য এড়িয়ে যান"</string>
     <string name="thermal_shutdown_title" msgid="4458304833443861111">"আপনার ফোন গরম হওয়ার জন্য বন্ধ হয়ে গেছে"</string>
     <string name="thermal_shutdown_message" msgid="9006456746902370523">"আপনার ফোন এখন ঠিক-ঠাক চলছে"</string>
-    <string name="thermal_shutdown_dialog_message" msgid="566347880005304139">"আপনার ফোন খুব বেশি গরম হয়েছিল বলে ঠান্ডা হওয়ার জন্য বন্ধ হয়ে গেছে। আপনার ফোন ঠিক-ঠাক ভাবে চলছে না।\n\nআপনার ফোন খুব বেশি গরম হয়ে যাবে যদি আপনি:\n	•এমন অ্যাপ ব্যবহার করলে যেটি আপনার ডিভাইসের রিসোর্স বেশি ব্যবহার করে (যেমন গেমিং, ভিডিও বা নেভিগেশন অ্যাপ)\n	• বড় ফাইল ডাউনলোড বা আপলোড করলে\n	• বেশি তাপমাত্রায় আপনার ফোন ব্যবহার করলে"</string>
+    <string name="thermal_shutdown_dialog_message" msgid="566347880005304139">"আপনার ফোন খুব বেশি গরম হয়েছিল বলে ঠাণ্ডা হওয়ার জন্য বন্ধ হয়ে গেছে। আপনার ফোন ঠিক-ঠাক ভাবে চলছে না।\n\nআপনার ফোন খুব বেশি গরম হয়ে যাবে যদি আপনি:\n	•এমন অ্যাপ ব্যবহার করলে যেটি আপনার ডিভাইসের রিসোর্স বেশি ব্যবহার করে (যেমন গেমিং, ভিডিও বা নেভিগেশন অ্যাপ)\n	• বড় ফাইল ডাউনলোড বা আপলোড করলে\n	• বেশি তাপমাত্রায় আপনার ফোন ব্যবহার করলে"</string>
     <string name="high_temp_title" msgid="4589508026407318374">"ফোনটি গরম হচ্ছে"</string>
-    <string name="high_temp_notif_message" msgid="5642466103153429279">"ফোনটি ঠান্ডা হওয়ার সময় কিছু বৈশিষ্ট্য সীমিত হতে পারে"</string>
-    <string name="high_temp_dialog_message" msgid="6840700639374113553">"আপনার ফোনটি নিজে থেকেই ঠান্ডা হওয়ার চেষ্টা করবে৷ আপনি তবুও আপনার ফোন ব্যবহার করতে পারেন, কিন্তু এটি একটু ধীরে চলতে পারে৷\n\nআপনার ফোনটি পুরোপুরি ঠান্ডা হয়ে গেলে এটি স্বাভাবিকভাবে চলবে৷"</string>
+    <string name="high_temp_notif_message" msgid="5642466103153429279">"ফোনটি ঠাণ্ডা হওয়ার সময় কিছু বৈশিষ্ট্য সীমিত হতে পারে"</string>
+    <string name="high_temp_dialog_message" msgid="6840700639374113553">"আপনার ফোনটি নিজে থেকেই ঠাণ্ডা হওয়ার চেষ্টা করবে৷ আপনি তবুও আপনার ফোন ব্যবহার করতে পারেন, কিন্তু এটি একটু ধীরে চলতে পারে৷\n\nআপনার ফোনটি পুরোপুরি ঠাণ্ডা হয়ে গেলে এটি স্বাভাবিকভাবে চলবে৷"</string>
     <string name="lockscreen_shortcut_left" msgid="2182769107618938629">"বাঁদিকের শর্টকাট"</string>
     <string name="lockscreen_shortcut_right" msgid="3328683699505226536">"ডানদিকের শর্টকাট"</string>
     <string name="lockscreen_unlock_left" msgid="2043092136246951985">"বাঁদিকের শর্টকাট দিয়েও আনলক করা যায়"</string>
@@ -758,7 +756,8 @@
     <string name="instant_apps" msgid="6647570248119804907">"ঝটপট অ্যাপ"</string>
     <string name="instant_apps_message" msgid="8116608994995104836">"ঝটপট অ্যাপ ইনস্টল করার প্রয়োজন হয় না।"</string>
     <string name="app_info" msgid="6856026610594615344">"অ্যাপের তথ্য"</string>
-    <string name="go_to_web" msgid="1106022723459948514">"ওয়েবে যান"</string>
+    <!-- no translation found for go_to_web (2650669128861626071) -->
+    <skip />
     <string name="mobile_data" msgid="7094582042819250762">"মোবাইল ডেটা"</string>
     <string name="wifi_is_off" msgid="1838559392210456893">"ওয়াই ফাই বন্ধ আছে"</string>
     <string name="bt_is_off" msgid="2640685272289706392">"ব্লুটুথ বন্ধ আছে"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 601d8e3..530c1a8 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -339,9 +339,9 @@
     <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Podjela po horizontali"</string>
     <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Podjela po vertikali"</string>
     <string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Prilagođena podjela"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Podijeli ekran nagore"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Podijeli ekran nalijevo"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Podijeli ekran nadesno"</string>
+    <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Dijeli ekran nagore"</string>
+    <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Dijeli ekran nalijevo"</string>
+    <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Dijeli ekran nadesno"</string>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Napunjeno"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Punjenje"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Do kraja punjenja preostalo <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
@@ -764,7 +764,8 @@
     <string name="instant_apps" msgid="6647570248119804907">"Instant-aplikacije"</string>
     <string name="instant_apps_message" msgid="8116608994995104836">"Za instant aplikacije nije potrebna instalacija"</string>
     <string name="app_info" msgid="6856026610594615344">"Informacije o aplikaciji"</string>
-    <string name="go_to_web" msgid="1106022723459948514">"Idite na internet"</string>
+    <!-- no translation found for go_to_web (2650669128861626071) -->
+    <skip />
     <string name="mobile_data" msgid="7094582042819250762">"Mobilni podaci"</string>
     <string name="wifi_is_off" msgid="1838559392210456893">"Wi-Fi veza je isključena"</string>
     <string name="bt_is_off" msgid="2640685272289706392">"Bluetooth je isključen"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index cd1f057..d107fea 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -756,7 +756,8 @@
     <string name="instant_apps" msgid="6647570248119804907">"برنامه‌های فوری"</string>
     <string name="instant_apps_message" msgid="8116608994995104836">"برنامه‌های فوری نیاز به نصب ندارند."</string>
     <string name="app_info" msgid="6856026610594615344">"اطلاعات برنامه"</string>
-    <string name="go_to_web" msgid="1106022723459948514">"رفتن به وب"</string>
+    <!-- no translation found for go_to_web (2650669128861626071) -->
+    <skip />
     <string name="mobile_data" msgid="7094582042819250762">"داده تلفن همراه"</string>
     <string name="wifi_is_off" msgid="1838559392210456893">"‏Wi-Fi خاموش است"</string>
     <string name="bt_is_off" msgid="2640685272289706392">"بلوتوث خاموش است"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 075edf1..b4709bb 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -756,7 +756,8 @@
     <string name="instant_apps" msgid="6647570248119804907">"Aplikasi Instan"</string>
     <string name="instant_apps_message" msgid="8116608994995104836">"Aplikasi instan tidak perlu diinstal."</string>
     <string name="app_info" msgid="6856026610594615344">"Info aplikasi"</string>
-    <string name="go_to_web" msgid="1106022723459948514">"Buka di web"</string>
+    <!-- no translation found for go_to_web (2650669128861626071) -->
+    <skip />
     <string name="mobile_data" msgid="7094582042819250762">"Data seluler"</string>
     <string name="wifi_is_off" msgid="1838559392210456893">"Wi-Fi nonaktif"</string>
     <string name="bt_is_off" msgid="2640685272289706392">"Bluetooth nonaktif"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 1b80410..e871c6a 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -756,7 +756,8 @@
     <string name="instant_apps" msgid="6647570248119804907">"მყისიერი აპები"</string>
     <string name="instant_apps_message" msgid="8116608994995104836">"მყისიერი აპები ინსტალაციას არ საჭიროებს."</string>
     <string name="app_info" msgid="6856026610594615344">"აპის შესახებ"</string>
-    <string name="go_to_web" msgid="1106022723459948514">"ვებზე გადასვლა"</string>
+    <!-- no translation found for go_to_web (2650669128861626071) -->
+    <skip />
     <string name="mobile_data" msgid="7094582042819250762">"მობილური ინტერნეტი"</string>
     <string name="wifi_is_off" msgid="1838559392210456893">"Wi-Fi გამორთულია"</string>
     <string name="bt_is_off" msgid="2640685272289706392">"Bluetooth გამორთულია"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index ec4e082..b5b3475 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -26,8 +26,8 @@
     <string name="status_bar_no_recent_apps" msgid="7374907845131203189">"Os ecrãs recentes aparecem aqui"</string>
     <string name="status_bar_accessibility_dismiss_recents" msgid="4576076075226540105">"Ignorar aplicações recentes"</string>
     <plurals name="status_bar_accessibility_recent_apps" formatted="false" msgid="9138535907802238759">
-      <item quantity="one">1 ecrã na Vista geral</item>
       <item quantity="other">%d ecrãs na Vista geral</item>
+      <item quantity="one">1 ecrã na Vista geral</item>
     </plurals>
     <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Sem notificações"</string>
     <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Em curso"</string>
@@ -249,8 +249,8 @@
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Limpar todas as notificações."</string>
     <string name="notification_group_overflow_indicator" msgid="1863231301642314183">"+ <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <plurals name="notification_group_overflow_description" formatted="false" msgid="4579313201268495404">
-      <item quantity="one">Mais <xliff:g id="NUMBER_0">%s</xliff:g> notificação no grupo.</item>
       <item quantity="other">Mais <xliff:g id="NUMBER_1">%s</xliff:g> notificações no grupo.</item>
+      <item quantity="one">Mais <xliff:g id="NUMBER_0">%s</xliff:g> notificação no grupo.</item>
     </plurals>
     <string name="status_bar_notification_inspect_item_title" msgid="5668348142410115323">"Definições de notificação"</string>
     <string name="status_bar_notification_app_settings_title" msgid="5525260160341558869">"Definições do <xliff:g id="APP_NAME">%s</xliff:g>"</string>
@@ -551,13 +551,13 @@
     <string name="notification_default_channel_desc" msgid="2506053815870808359">"Esta aplicação não tem categorias de notificação"</string>
     <string name="notification_unblockable_desc" msgid="3561016061737896906">"Não é possível desativar as notificações desta aplicação"</string>
     <plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
-      <item quantity="one">1 de <xliff:g id="NUMBER_0">%s</xliff:g> categoria de notificação desta aplicação</item>
       <item quantity="other">1 de <xliff:g id="NUMBER_1">%s</xliff:g> categorias de notificação desta aplicação</item>
+      <item quantity="one">1 de <xliff:g id="NUMBER_0">%s</xliff:g> categoria de notificação desta aplicação</item>
     </plurals>
     <string name="notification_channels_list_desc_2" msgid="6214732715833946441">"<xliff:g id="CHANNEL_NAME_1">%1$s</xliff:g>, <xliff:g id="CHANNEL_NAME_2">%2$s</xliff:g>"</string>
     <plurals name="notification_channels_list_desc_2_and_others" formatted="false" msgid="2747813553355336157">
-      <item quantity="one"><xliff:g id="CHANNEL_NAME_1_0">%1$s</xliff:g>, <xliff:g id="CHANNEL_NAME_2_1">%2$s</xliff:g> e mais <xliff:g id="NUMBER_2">%3$d</xliff:g></item>
       <item quantity="other"><xliff:g id="CHANNEL_NAME_1_3">%1$s</xliff:g>, <xliff:g id="CHANNEL_NAME_2_4">%2$s</xliff:g> e mais <xliff:g id="NUMBER_5">%3$d</xliff:g></item>
+      <item quantity="one"><xliff:g id="CHANNEL_NAME_1_0">%1$s</xliff:g>, <xliff:g id="CHANNEL_NAME_2_1">%2$s</xliff:g> e mais <xliff:g id="NUMBER_2">%3$d</xliff:g></item>
     </plurals>
     <string name="notification_channel_controls_opened_accessibility" msgid="6553950422055908113">"Controlos de notificações da aplicação <xliff:g id="APP_NAME">%1$s</xliff:g> abertos"</string>
     <string name="notification_channel_controls_closed_accessibility" msgid="7521619812603693144">"Controlos de notificações da aplicação <xliff:g id="APP_NAME">%1$s</xliff:g> fechados"</string>
@@ -572,12 +572,12 @@
     <string name="snooze_undo" msgid="6074877317002985129">"ANULAR"</string>
     <string name="snoozed_for_time" msgid="2390718332980204462">"Suspensa por <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
     <plurals name="snoozeHourOptions" formatted="false" msgid="2124335842674413030">
-      <item quantity="one">%d hora</item>
       <item quantity="other">%d horas</item>
+      <item quantity="one">%d hora</item>
     </plurals>
     <plurals name="snoozeMinuteOptions" formatted="false" msgid="4127251700591510196">
-      <item quantity="one">%d minuto</item>
       <item quantity="other">%d minutos</item>
+      <item quantity="one">%d minuto</item>
     </plurals>
     <string name="battery_panel_title" msgid="7944156115535366613">"Utiliz. da bateria"</string>
     <string name="battery_detail_charging_summary" msgid="1279095653533044008">"Poupança de bateria não disponível durante o carregamento"</string>
@@ -756,7 +756,8 @@
     <string name="instant_apps" msgid="6647570248119804907">"Aplicações instantâneas"</string>
     <string name="instant_apps_message" msgid="8116608994995104836">"As Aplicações instantâneas não requerem instalação."</string>
     <string name="app_info" msgid="6856026610594615344">"Informações da aplicação"</string>
-    <string name="go_to_web" msgid="1106022723459948514">"Aceder à Web"</string>
+    <!-- no translation found for go_to_web (2650669128861626071) -->
+    <skip />
     <string name="mobile_data" msgid="7094582042819250762">"Dados móveis"</string>
     <string name="wifi_is_off" msgid="1838559392210456893">"Wi-Fi desativado"</string>
     <string name="bt_is_off" msgid="2640685272289706392">"Bluetooth desativado"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 5b9ff78..95cd2d0 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -770,7 +770,8 @@
     <string name="instant_apps" msgid="6647570248119804907">"Додатки з миттєвим запуском"</string>
     <string name="instant_apps_message" msgid="8116608994995104836">"Додатки з миттєвим запуском не потрібно встановлювати."</string>
     <string name="app_info" msgid="6856026610594615344">"Про додаток"</string>
-    <string name="go_to_web" msgid="1106022723459948514">"Перейти на веб-сайт"</string>
+    <!-- no translation found for go_to_web (2650669128861626071) -->
+    <skip />
     <string name="mobile_data" msgid="7094582042819250762">"Мобільний трафік"</string>
     <string name="wifi_is_off" msgid="1838559392210456893">"Wi-Fi вимкнено"</string>
     <string name="bt_is_off" msgid="2640685272289706392">"Bluetooth вимкнено"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 8e065d1..5e7f9c6 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -316,6 +316,40 @@
     <!-- SystemUIFactory component -->
     <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
 
+    <!-- SystemUI Services: The classes of the stuff to start. -->
+    <string-array name="config_systemUIServiceComponents" translatable="false">
+        <item>com.android.systemui.Dependency</item>
+        <item>com.android.systemui.util.NotificationChannels</item>
+        <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
+        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
+        <item>com.android.systemui.recents.Recents</item>
+        <item>com.android.systemui.volume.VolumeUI</item>
+        <item>com.android.systemui.stackdivider.Divider</item>
+        <item>com.android.systemui.SystemBars</item>
+        <item>com.android.systemui.usb.StorageNotification</item>
+        <item>com.android.systemui.power.PowerUI</item>
+        <item>com.android.systemui.media.RingtonePlayer</item>
+        <item>com.android.systemui.keyboard.KeyboardUI</item>
+        <item>com.android.systemui.pip.PipUI</item>
+        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
+        <item>@string/config_systemUIVendorServiceComponent</item>
+        <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
+        <item>com.android.systemui.LatencyTester</item>
+        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
+        <item>com.android.systemui.RoundedCorners</item>
+        <item>com.android.systemui.EmulatedDisplayCutout</item>
+    </string-array>
+
+    <!-- SystemUI vender service, used in config_systemUIServiceComponents. -->
+    <string name="config_systemUIVendorServiceComponent" translatable="false">com.android.systemui.VendorServices</string>
+
+    <!-- SystemUI Services (per user): The classes of the stuff to start for each user. This is a subset of the config_systemUIServiceComponents -->
+    <string-array name="config_systemUIServiceComponentsPerUser" translatable="false">
+        <item>com.android.systemui.Dependency</item>
+        <item>com.android.systemui.util.NotificationChannels</item>
+        <item>com.android.systemui.recents.Recents</item>
+    </string-array>
+
     <!-- Nav bar button default ordering/layout -->
     <string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 659c5a8..62ab74d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1248,6 +1248,10 @@
     <string name="stream_tts" translatable="false">Transmitted Through Speaker</string> <!-- STREAM_TTS -->
     <string name="stream_accessibility">Accessibility</string> <!-- STREAM_ACCESSIBILITY -->
 
+    <string name="volume_ringer_status_normal">Ring</string>
+    <string name="volume_ringer_status_vibrate">Vibrate</string>
+    <string name="volume_ringer_status_silent">Mute</string>
+
     <string name="volume_stream_muted" translatable="false">%s silent</string>
     <string name="volume_stream_vibrate" translatable="false">%s vibrate</string>
     <string name="volume_stream_suppressed" translatable="false">%1$s silent — %2$s</string>
diff --git a/packages/SystemUI/shared/tests/Android.mk b/packages/SystemUI/shared/tests/Android.mk
index ce3b442..239a4e3 100644
--- a/packages/SystemUI/shared/tests/Android.mk
+++ b/packages/SystemUI/shared/tests/Android.mk
@@ -38,7 +38,6 @@
     mockito-target-minus-junit4 \
     SystemUI-proto \
     SystemUI-tags \
-    legacy-android-test \
     testables \
     truth-prebuilt \
 
@@ -50,4 +49,4 @@
 
 ifeq ($(EXCLUDE_SYSTEMUI_TESTS),)
     include $(BUILD_PACKAGE)
-endif
\ No newline at end of file
+endif
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 432b406..6e0b56e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -53,8 +53,13 @@
     private ProgressDialog mSimUnlockProgressDialog = null;
     private CheckSimPin mCheckSimPinThread;
 
+    // Below flag is set to true during power-up or when a new SIM card inserted on device.
+    // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
+    // be displayed to inform user about the number of remaining PIN attempts left.
+    private boolean mShowDefaultMessage = true;
+    private int mRemainingAttempts = -1;
     private AlertDialog mRemainingAttemptsDialog;
-    private int mSubId;
+    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private ImageView mSimImageView;
 
     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
@@ -91,34 +96,71 @@
     public void resetState() {
         super.resetState();
         if (DEBUG) Log.v(TAG, "Resetting state");
-        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
-        mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
-        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
-        if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
-            int count = TelephonyManager.getDefault().getSimCount();
-            Resources rez = getResources();
-            String msg;
-            int color = Color.WHITE;
-            if (count < 2) {
-                msg = rez.getString(R.string.kg_sim_pin_instructions);
-            } else {
-                SubscriptionInfo info = monitor.getSubscriptionInfoForSubId(mSubId);
-                CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
-                msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
-                if (info != null) {
-                    color = info.getIconTint();
-                }
-            }
-            if (isEsimLocked) {
-                msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
-            }
-            mSecurityMessageDisplay.setMessage(msg);
-            mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+        handleSubInfoChangeIfNeeded();
+        if (mShowDefaultMessage) {
+            showDefaultMessage();
         }
+        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
+
         KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
         esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
     }
 
+    private void showDefaultMessage() {
+        if (mRemainingAttempts >= 0) {
+            mSecurityMessageDisplay.setMessage(getPinPasswordErrorMessage(
+                    mRemainingAttempts, true));
+            return;
+        }
+
+        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
+        int count = TelephonyManager.getDefault().getSimCount();
+        Resources rez = getResources();
+        String msg;
+        int color = Color.WHITE;
+        if (count < 2) {
+            msg = rez.getString(R.string.kg_sim_pin_instructions);
+        } else {
+            SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext).
+                    getSubscriptionInfoForSubId(mSubId);
+            CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
+            msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
+            if (info != null) {
+                color = info.getIconTint();
+            }
+        }
+
+        if (isEsimLocked) {
+            msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
+        }
+
+        mSecurityMessageDisplay.setMessage(msg);
+        mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+
+        // Sending empty PIN here to query the number of remaining PIN attempts
+        new CheckSimPin("", mSubId) {
+            void onSimCheckResponse(final int result, final int attemptsRemaining) {
+                Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result" + result +
+                        " attemptsRemaining=" + attemptsRemaining);
+                if (attemptsRemaining >= 0) {
+                    mRemainingAttempts = attemptsRemaining;
+                    mSecurityMessageDisplay.setMessage(
+                            getPinPasswordErrorMessage(attemptsRemaining, true));
+                }
+            }
+        }.start();
+    }
+
+    private void handleSubInfoChangeIfNeeded() {
+        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        int subId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
+        if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
+            mSubId = subId;
+            mShowDefaultMessage = true;
+            mRemainingAttempts = -1;
+        }
+    }
+
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -131,17 +173,19 @@
         return 0;
     }
 
-    private String getPinPasswordErrorMessage(int attemptsRemaining) {
+    private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
         String displayMessage;
-
+        int msgId;
         if (attemptsRemaining == 0) {
             displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked);
         } else if (attemptsRemaining > 0) {
+            msgId = isDefault ? R.plurals.kg_password_default_pin_message :
+                     R.plurals.kg_password_wrong_pin_code;
             displayMessage = getContext().getResources()
-                    .getQuantityString(R.plurals.kg_password_wrong_pin_code, attemptsRemaining,
-                            attemptsRemaining);
+                    .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
         } else {
-            displayMessage = getContext().getString(R.string.kg_password_pin_failed);
+            msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed;
+            displayMessage = getContext().getString(msgId);
         }
         if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:"
                 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
@@ -252,7 +296,7 @@
     }
 
     private Dialog getSimRemainingAttemptsDialog(int remaining) {
-        String msg = getPinPasswordErrorMessage(remaining);
+        String msg = getPinPasswordErrorMessage(remaining, false);
         if (mRemainingAttemptsDialog == null) {
             Builder builder = new AlertDialog.Builder(mContext);
             builder.setMessage(msg);
@@ -288,6 +332,7 @@
                     post(new Runnable() {
                         @Override
                         public void run() {
+                            mRemainingAttempts = attemptsRemaining;
                             if (mSimUnlockProgressDialog != null) {
                                 mSimUnlockProgressDialog.hide();
                             }
@@ -296,8 +341,13 @@
                             if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
                                 KeyguardUpdateMonitor.getInstance(getContext())
                                         .reportSimUnlocked(mSubId);
-                                mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                                mRemainingAttempts = -1;
+                                mShowDefaultMessage = true;
+                                if (mCallback != null) {
+                                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                                }
                             } else {
+                                mShowDefaultMessage = false;
                                 if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) {
                                     if (attemptsRemaining <= 2) {
                                         // this is getting critical - show dialog
@@ -305,7 +355,7 @@
                                     } else {
                                         // show message
                                         mSecurityMessageDisplay.setMessage(
-                                                getPinPasswordErrorMessage(attemptsRemaining));
+                                                getPinPasswordErrorMessage(attemptsRemaining, false));
                                     }
                                 } else {
                                     // "PIN operation failed!" - no idea what this was and no way to
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 7f79008..876d170 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -52,11 +52,17 @@
 
     private ProgressDialog mSimUnlockProgressDialog = null;
     private CheckSimPuk mCheckSimPukThread;
+
+    // Below flag is set to true during power-up or when a new SIM card inserted on device.
+    // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would
+    // be displayed to inform user about the number of remaining PUK attempts left.
+    private boolean mShowDefaultMessage = true;
+    private int mRemainingAttempts = -1;
     private String mPukText;
     private String mPinText;
     private StateMachine mStateMachine = new StateMachine();
     private AlertDialog mRemainingAttemptsDialog;
-    private int mSubId;
+    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private ImageView mSimImageView;
 
     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
@@ -132,34 +138,17 @@
             }
         }
 
+
         void reset() {
             mPinText="";
             mPukText="";
             state = ENTER_PUK;
-            KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
-            mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED);
-            boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
-            if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
-                int count = TelephonyManager.getDefault().getSimCount();
-                Resources rez = getResources();
-                String msg;
-                int color = Color.WHITE;
-                if (count < 2) {
-                    msg = rez.getString(R.string.kg_puk_enter_puk_hint);
-                } else {
-                    SubscriptionInfo info = monitor.getSubscriptionInfoForSubId(mSubId);
-                    CharSequence displayName = info != null ? info.getDisplayName() : "";
-                    msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
-                    if (info != null) {
-                        color = info.getIconTint();
-                    }
-                }
-                if (isEsimLocked) {
-                    msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
-                }
-                mSecurityMessageDisplay.setMessage(msg);
-                mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+            handleSubInfoChangeIfNeeded();
+            if (mShowDefaultMessage) {
+                showDefaultMessage();
             }
+            boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
+
             KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
             esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
             mPasswordEntry.requestFocus();
@@ -168,23 +157,79 @@
 
     }
 
+    private void showDefaultMessage() {
+        if (mRemainingAttempts >= 0) {
+            mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage(
+                    mRemainingAttempts, true));
+            return;
+        }
+
+        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
+        int count = TelephonyManager.getDefault().getSimCount();
+        Resources rez = getResources();
+        String msg;
+        int color = Color.WHITE;
+        if (count < 2) {
+            msg = rez.getString(R.string.kg_puk_enter_puk_hint);
+        } else {
+            SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext).
+                    getSubscriptionInfoForSubId(mSubId);
+            CharSequence displayName = info != null ? info.getDisplayName() : "";
+            msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
+            if (info != null) {
+                color = info.getIconTint();
+            }
+        }
+        if (isEsimLocked) {
+            msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
+        }
+        mSecurityMessageDisplay.setMessage(msg);
+        mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+
+        // Sending empty PUK here to query the number of remaining PIN attempts
+        new CheckSimPuk("", "", mSubId) {
+            void onSimLockChangedResponse(final int result, final int attemptsRemaining) {
+                Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result" + result +
+                        " attemptsRemaining=" + attemptsRemaining);
+                if (attemptsRemaining >= 0) {
+                    mRemainingAttempts = attemptsRemaining;
+                    mSecurityMessageDisplay.setMessage(
+                            getPukPasswordErrorMessage(attemptsRemaining, true));
+                }
+            }
+        }.start();
+    }
+
+    private void handleSubInfoChangeIfNeeded() {
+        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        int subId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED);
+        if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
+            mSubId = subId;
+            mShowDefaultMessage = true;
+            mRemainingAttempts = -1;
+        }
+    }
+
     @Override
     protected int getPromtReasonStringRes(int reason) {
         // No message on SIM Puk
         return 0;
     }
 
-    private String getPukPasswordErrorMessage(int attemptsRemaining) {
+    private String getPukPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
         String displayMessage;
 
         if (attemptsRemaining == 0) {
             displayMessage = getContext().getString(R.string.kg_password_wrong_puk_code_dead);
         } else if (attemptsRemaining > 0) {
+            int msgId = isDefault ? R.plurals.kg_password_default_puk_message :
+                    R.plurals.kg_password_wrong_puk_code;
             displayMessage = getContext().getResources()
-                    .getQuantityString(R.plurals.kg_password_wrong_puk_code, attemptsRemaining,
-                            attemptsRemaining);
+                    .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
         } else {
-            displayMessage = getContext().getString(R.string.kg_password_puk_failed);
+            int msgId = isDefault ? R.string.kg_puk_enter_puk_hint :
+                    R.string.kg_password_puk_failed;
+            displayMessage = getContext().getString(msgId);
         }
         if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:"
                 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
@@ -303,7 +348,7 @@
     }
 
     private Dialog getPukRemainingAttemptsDialog(int remaining) {
-        String msg = getPukPasswordErrorMessage(remaining);
+        String msg = getPukPasswordErrorMessage(remaining, false);
         if (mRemainingAttemptsDialog == null) {
             AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
             builder.setMessage(msg);
@@ -359,16 +404,25 @@
                             if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
                                 KeyguardUpdateMonitor.getInstance(getContext())
                                         .reportSimUnlocked(mSubId);
-                                mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                                mRemainingAttempts = -1;
+                                mShowDefaultMessage = true;
+                                if (mCallback != null) {
+                                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                                }
                             } else {
+                                mShowDefaultMessage = false;
                                 if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) {
+                                    // show message
+                                    mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage(
+                                            attemptsRemaining, false));
                                     if (attemptsRemaining <= 2) {
                                         // this is getting critical - show dialog
                                         getPukRemainingAttemptsDialog(attemptsRemaining).show();
                                     } else {
                                         // show message
                                         mSecurityMessageDisplay.setMessage(
-                                                getPukPasswordErrorMessage(attemptsRemaining));
+                                                getPukPasswordErrorMessage(
+                                                attemptsRemaining, false));
                                     }
                                 } else {
                                     mSecurityMessageDisplay.setMessage(getContext().getString(
diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
new file mode 100644
index 0000000..edd1748
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
@@ -0,0 +1,140 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+
+/**
+ * Emulates a display cutout by drawing its shape in an overlay as supplied by
+ * {@link DisplayCutout}.
+ */
+public class EmulatedDisplayCutout extends SystemUI {
+    private View mOverlay;
+    private boolean mAttached;
+    private WindowManager mWindowManager;
+
+    @Override
+    public void start() {
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.EMULATE_DISPLAY_CUTOUT),
+                false, mObserver, UserHandle.USER_ALL);
+        mObserver.onChange(false);
+    }
+
+    private void setAttached(boolean attached) {
+        if (attached && !mAttached) {
+            if (mOverlay == null) {
+                mOverlay = new CutoutView(mContext);
+                mOverlay.setLayoutParams(getLayoutParams());
+            }
+            mWindowManager.addView(mOverlay, mOverlay.getLayoutParams());
+            mAttached = true;
+        } else if (!attached && mAttached) {
+            mWindowManager.removeView(mOverlay);
+            mAttached = false;
+        }
+    }
+
+    private WindowManager.LayoutParams getLayoutParams() {
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_SLIPPERY
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR,
+                PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
+                | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
+        lp.setTitle("EmulatedDisplayCutout");
+        lp.gravity = Gravity.TOP;
+        return lp;
+    }
+
+    private ContentObserver mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+        @Override
+        public void onChange(boolean selfChange) {
+            boolean emulateCutout = Settings.Global.getInt(
+                    mContext.getContentResolver(), Settings.Global.EMULATE_DISPLAY_CUTOUT,
+                    Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF)
+                    != Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF;
+            setAttached(emulateCutout);
+        }
+    };
+
+    private static class CutoutView extends View {
+        private Paint mPaint = new Paint();
+        private Path mPath = new Path();
+        private ArrayList<Point> mBoundingPolygon = new ArrayList<>();
+
+        CutoutView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+            insets.getDisplayCutout().getBoundingPolygon(mBoundingPolygon);
+            invalidate();
+            return insets.consumeCutout();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            if (!mBoundingPolygon.isEmpty()) {
+                mPaint.setColor(Color.DKGRAY);
+                mPaint.setStyle(Paint.Style.FILL);
+
+                mPath.reset();
+                for (int i = 0; i < mBoundingPolygon.size(); i++) {
+                    Point point = mBoundingPolygon.get(i);
+                    if (i == 0) {
+                        mPath.moveTo(point.x, point.y);
+                    } else {
+                        mPath.lineTo(point.x, point.y);
+                    }
+                }
+                mPath.close();
+                canvas.drawPath(mPath, mPaint);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 9adafda..3538327 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -37,9 +37,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.media.RingtonePlayer;
 import com.android.systemui.pip.PipUI;
-import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.OverlayPlugin;
-import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.power.PowerUI;
@@ -54,6 +52,7 @@
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.volume.VolumeUI;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -66,44 +65,9 @@
     private static final boolean DEBUG = false;
 
     /**
-     * The classes of the stuff to start.
-     */
-    private final Class<?>[] SERVICES = new Class[] {
-            Dependency.class,
-            NotificationChannels.class,
-            CommandQueue.CommandQueueStart.class,
-            KeyguardViewMediator.class,
-            Recents.class,
-            VolumeUI.class,
-            Divider.class,
-            SystemBars.class,
-            StorageNotification.class,
-            PowerUI.class,
-            RingtonePlayer.class,
-            KeyboardUI.class,
-            PipUI.class,
-            ShortcutKeyDispatcher.class,
-            VendorServices.class,
-            GarbageMonitor.Service.class,
-            LatencyTester.class,
-            GlobalActionsComponent.class,
-            RoundedCorners.class,
-    };
-
-    /**
-     * The classes of the stuff to start for each user.  This is a subset of the services listed
-     * above.
-     */
-    private final Class<?>[] SERVICES_PER_USER = new Class[] {
-            Dependency.class,
-            NotificationChannels.class,
-            Recents.class
-    };
-
-    /**
      * Hold a reference on the stuff we start.
      */
-    private final SystemUI[] mServices = new SystemUI[SERVICES.length];
+    private SystemUI[] mServices;
     private boolean mServicesStarted;
     private boolean mBootCompleted;
     private final Map<Class<?>, Object> mComponents = new HashMap<>();
@@ -149,7 +113,7 @@
             // been broadcasted on startup for the primary SystemUI process.  Instead, for
             // components which require the SystemUI component to be initialized per-user, we
             // start those components now for the current non-system user.
-            startServicesIfNeeded(SERVICES_PER_USER);
+            startSecondaryUserServicesIfNeeded();
         }
     }
 
@@ -161,7 +125,8 @@
      */
 
     public void startServicesIfNeeded() {
-        startServicesIfNeeded(SERVICES);
+        String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
+        startServicesIfNeeded(names);
     }
 
     /**
@@ -171,13 +136,16 @@
      * <p>This method must only be called from the main thread.</p>
      */
     void startSecondaryUserServicesIfNeeded() {
-        startServicesIfNeeded(SERVICES_PER_USER);
+        String[] names =
+                  getResources().getStringArray(R.array.config_systemUIServiceComponentsPerUser);
+        startServicesIfNeeded(names);
     }
 
-    private void startServicesIfNeeded(Class<?>[] services) {
+    private void startServicesIfNeeded(String[] services) {
         if (mServicesStarted) {
             return;
         }
+        mServices = new SystemUI[services.length];
 
         if (!mBootCompleted) {
             // check to see if maybe it was already completed long before we began
@@ -195,14 +163,16 @@
         log.traceBegin("StartServices");
         final int N = services.length;
         for (int i = 0; i < N; i++) {
-            Class<?> cl = services[i];
-            if (DEBUG) Log.d(TAG, "loading: " + cl);
-            log.traceBegin("StartServices" + cl.getSimpleName());
+            String clsName = services[i];
+            if (DEBUG) Log.d(TAG, "loading: " + clsName);
+            log.traceBegin("StartServices" + clsName);
             long ti = System.currentTimeMillis();
+            Class cls;
             try {
-
-                Object newService = SystemUIFactory.getInstance().createInstance(cl);
-                mServices[i] = (SystemUI) ((newService == null) ? cl.newInstance() : newService);
+                cls = Class.forName(clsName);
+                mServices[i] = (SystemUI) cls.newInstance();
+            } catch(ClassNotFoundException ex){
+                throw new RuntimeException(ex);
             } catch (IllegalAccessException ex) {
                 throw new RuntimeException(ex);
             } catch (InstantiationException ex) {
@@ -218,7 +188,7 @@
             // Warn if initialization of component takes too long
             ti = System.currentTimeMillis() - ti;
             if (ti > 1000) {
-                Log.w(TAG, "Initialization of " + cl.getName() + " took " + ti + " ms");
+                Log.w(TAG, "Initialization of " + cls.getName() + " took " + ti + " ms");
             }
             if (mBootCompleted) {
                 mServices[i].onBootCompleted();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index f28096f..45b11aa 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -109,10 +109,6 @@
         return new QSTileHost(context, statusBar, iconController);
     }
 
-    public <T> T createInstance(Class<T> classType) {
-        return null;
-    }
-
     public void injectDependencies(ArrayMap<Object, DependencyProvider> providers,
             Context context) {
         providers.put(NotificationLockscreenUserManager.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
new file mode 100644
index 0000000..6bcd174
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -0,0 +1,129 @@
+/*
+ * 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;
+
+import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
+import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_CHILD_NOTIFICATIONS;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
+
+/**
+ * This class handles listening to notification updates and passing them along to
+ * NotificationPresenter to be displayed to the user.
+ */
+public class NotificationListener extends NotificationListenerWithPlugins {
+    private static final String TAG = "NotificationListener";
+
+    private final NotificationPresenter mPresenter;
+    private final Context mContext;
+
+    public NotificationListener(NotificationPresenter presenter, Context context) {
+        mPresenter = presenter;
+        mContext = context;
+    }
+
+    @Override
+    public void onListenerConnected() {
+        if (DEBUG) Log.d(TAG, "onListenerConnected");
+        onPluginConnected();
+        final StatusBarNotification[] notifications = getActiveNotifications();
+        if (notifications == null) {
+            Log.w(TAG, "onListenerConnected unable to get active notifications.");
+            return;
+        }
+        final RankingMap currentRanking = getCurrentRanking();
+        mPresenter.getHandler().post(() -> {
+            for (StatusBarNotification sbn : notifications) {
+                mPresenter.addNotification(sbn, currentRanking);
+            }
+        });
+    }
+
+    @Override
+    public void onNotificationPosted(final StatusBarNotification sbn,
+            final RankingMap rankingMap) {
+        if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
+        if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
+            mPresenter.getHandler().post(() -> {
+                processForRemoteInput(sbn.getNotification(), mContext);
+                String key = sbn.getKey();
+                mPresenter.getKeysKeptForRemoteInput().remove(key);
+                boolean isUpdate = mPresenter.getNotificationData().get(key) != null;
+                // In case we don't allow child notifications, we ignore children of
+                // notifications that have a summary, since` we're not going to show them
+                // anyway. This is true also when the summary is canceled,
+                // because children are automatically canceled by NoMan in that case.
+                if (!ENABLE_CHILD_NOTIFICATIONS
+                        && mPresenter.getGroupManager().isChildInGroupWithSummary(sbn)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+                    }
+
+                    // Remove existing notification to avoid stale data.
+                    if (isUpdate) {
+                        mPresenter.removeNotification(key, rankingMap);
+                    } else {
+                        mPresenter.getNotificationData().updateRanking(rankingMap);
+                    }
+                    return;
+                }
+                if (isUpdate) {
+                    mPresenter.updateNotification(sbn, rankingMap);
+                } else {
+                    mPresenter.addNotification(sbn, rankingMap);
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn,
+            final RankingMap rankingMap) {
+        if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
+        if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
+            final String key = sbn.getKey();
+            mPresenter.getHandler().post(() -> mPresenter.removeNotification(key, rankingMap));
+        }
+    }
+
+    @Override
+    public void onNotificationRankingUpdate(final RankingMap rankingMap) {
+        if (DEBUG) Log.d(TAG, "onRankingUpdate");
+        if (rankingMap != null) {
+            RankingMap r = onPluginRankingUpdate(rankingMap);
+            mPresenter.getHandler().post(() -> mPresenter.updateNotificationRanking(r));
+        }
+    }
+
+    public void register() {
+        try {
+            registerAsSystemService(mContext,
+                    new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
+                    UserHandle.USER_ALL);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to register notification listener", e);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 8670887..4eca241 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -19,6 +19,8 @@
 import android.os.Handler;
 import android.service.notification.NotificationListenerService;
 
+import java.util.Set;
+
 /**
  * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods
  * for both querying the state of the system (some modularised piece of functionality may
@@ -26,7 +28,8 @@
  * for affecting the state of the system (e.g. starting an intent, given that the presenter may
  * want to perform some action before doing so).
  */
-public interface NotificationPresenter {
+public interface NotificationPresenter extends NotificationUpdateHandler,
+        NotificationData.Environment {
 
     /**
      * Returns true if the presenter is not visible. For example, it may not be necessary to do
@@ -66,12 +69,6 @@
      */
     void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
 
-    // TODO: Create NotificationUpdateHandler and move this method to there.
-    /**
-     * Removes a notification.
-     */
-    void removeNotification(String key, NotificationListenerService.RankingMap ranking);
-
     // TODO: Create NotificationEntryManager and move this method to there.
     /**
      * Gets the latest ranking map.
@@ -84,6 +81,14 @@
     void onWorkChallengeChanged();
 
     /**
+     * Notifications in this set are kept around when they were canceled in response to a remote
+     * input interaction. This allows us to show what you replied and allows you to continue typing
+     * into it.
+     */
+    // TODO: Create NotificationEntryManager and move this method to there.
+    Set<String> getKeysKeptForRemoteInput();
+
+    /**
      * Called when the current user changes.
      * @param newUserId new user id
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java
new file mode 100644
index 0000000..0044194
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+
+/**
+ * Interface for accepting notification updates from {@link NotificationListener}.
+ */
+public interface NotificationUpdateHandler {
+    /**
+     * Add a new notification and update the current notification ranking map.
+     *
+     * @param notification Notification to add
+     * @param ranking RankingMap to update with
+     */
+    void addNotification(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking);
+
+    /**
+     * Remove a notification and update the current notification ranking map.
+     *
+     * @param key Key identifying the notification to remove
+     * @param ranking RankingMap to update with
+     */
+    void removeNotification(String key, NotificationListenerService.RankingMap ranking);
+
+    /**
+     * Update a given notification and the current notification ranking map.
+     *
+     * @param notification Updated notification
+     * @param ranking RankingMap to update with
+     */
+    void updateNotification(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking);
+
+    /**
+     * Update with a new notification ranking map.
+     *
+     * @param ranking RankingMap to update with
+     */
+    void updateNotificationRanking(NotificationListenerService.RankingMap ranking);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index ff6c775..97e3d22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -21,17 +21,24 @@
 import com.android.systemui.statusbar.phone.StatusBarWindowManager;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 
+import android.app.Notification;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Keeps track of the currently active {@link RemoteInputView}s.
  */
 public class RemoteInputController {
+    private static final boolean ENABLE_REMOTE_INPUT =
+            SystemProperties.getBoolean("debug.enable_remote_input", true);
 
     private final ArrayList<Pair<WeakReference<NotificationData.Entry>, Object>> mOpen
             = new ArrayList<>();
@@ -45,6 +52,53 @@
     }
 
     /**
+     * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
+     * via first-class API.
+     *
+     * TODO: Remove once enough apps specify remote inputs on their own.
+     */
+    public static void processForRemoteInput(Notification n, Context context) {
+        if (!ENABLE_REMOTE_INPUT) {
+            return;
+        }
+
+        if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
+                (n.actions == null || n.actions.length == 0)) {
+            Notification.Action viableAction = null;
+            Notification.WearableExtender we = new Notification.WearableExtender(n);
+
+            List<Notification.Action> actions = we.getActions();
+            final int numActions = actions.size();
+
+            for (int i = 0; i < numActions; i++) {
+                Notification.Action action = actions.get(i);
+                if (action == null) {
+                    continue;
+                }
+                RemoteInput[] remoteInputs = action.getRemoteInputs();
+                if (remoteInputs == null) {
+                    continue;
+                }
+                for (RemoteInput ri : remoteInputs) {
+                    if (ri.getAllowFreeFormInput()) {
+                        viableAction = action;
+                        break;
+                    }
+                }
+                if (viableAction != null) {
+                    break;
+                }
+            }
+
+            if (viableAction != null) {
+                Notification.Builder rebuilder = Notification.Builder.recoverBuilder(context, n);
+                rebuilder.setActions(viableAction);
+                rebuilder.build(); // will rewrite n
+            }
+        }
+    }
+
+    /**
      * Adds a currently active remote input.
      *
      * @param entry the entry for which a remote input is now active.
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 c967423..80bab72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -200,6 +200,7 @@
 import com.android.systemui.statusbar.NotificationData.Entry;
 import com.android.systemui.statusbar.NotificationGutsManager;
 import com.android.systemui.statusbar.NotificationInfo;
+import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -249,6 +250,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.Stack;
 
 public class StatusBar extends SystemUI implements DemoMode,
@@ -816,14 +818,8 @@
         }
 
         // Set up the initial notification state.
-        try {
-            mNotificationListener.registerAsSystemService(mContext,
-                    new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
-                    UserHandle.USER_ALL);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to register notification listener", e);
-        }
-
+        mNotificationListener = new NotificationListener(this, mContext);
+        mNotificationListener.register();
 
         if (DEBUG) {
             Log.d(TAG, String.format(
@@ -1516,13 +1512,19 @@
         SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
     }
 
-    public void addNotification(StatusBarNotification notification, RankingMap ranking)
-            throws InflationException {
+    @Override
+    public void addNotification(StatusBarNotification notification, RankingMap ranking) {
         String key = notification.getKey();
         if (DEBUG) Log.d(TAG, "addNotification key=" + key);
 
         mNotificationData.updateRanking(ranking);
-        Entry shadeEntry = createNotificationViews(notification);
+        Entry shadeEntry = null;
+        try {
+            shadeEntry = createNotificationViews(notification);
+        } catch (InflationException e) {
+            handleInflationException(notification, e);
+            return;
+        }
         boolean isHeadsUped = shouldPeek(shadeEntry);
         if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
             if (shouldSuppressFullScreenIntent(key)) {
@@ -1536,11 +1538,11 @@
                             + key);
                 }
             } else {
-                // Stop screensaver if the notification has a full-screen intent.
+                // Stop screensaver if the notification has a fullscreen intent.
                 // (like an incoming phone call)
                 awakenDreams();
 
-                // not immersive & a full-screen alert should be shown
+                // not immersive & a fullscreen alert should be shown
                 if (DEBUG)
                     Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
                 try {
@@ -1617,7 +1619,8 @@
         }
     }
 
-    protected void updateNotificationRanking(RankingMap ranking) {
+    @Override
+    public void updateNotificationRanking(RankingMap ranking) {
         mNotificationData.updateRanking(ranking);
         updateNotifications();
     }
@@ -1670,7 +1673,7 @@
                     newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
             boolean updated = false;
             try {
-                updateNotification(newSbn, null);
+                updateNotificationInternal(newSbn, null);
                 updated = true;
             } catch (InflationException e) {
                 deferRemoval = false;
@@ -5705,90 +5708,7 @@
         }
     };
 
-    private final NotificationListenerWithPlugins mNotificationListener =
-            new NotificationListenerWithPlugins() {
-        @Override
-        public void onListenerConnected() {
-            if (DEBUG) Log.d(TAG, "onListenerConnected");
-            onPluginConnected();
-            final StatusBarNotification[] notifications = getActiveNotifications();
-            if (notifications == null) {
-                Log.w(TAG, "onListenerConnected unable to get active notifications.");
-                return;
-            }
-            final RankingMap currentRanking = getCurrentRanking();
-            mHandler.post(() -> {
-                for (StatusBarNotification sbn : notifications) {
-                    try {
-                        addNotification(sbn, currentRanking);
-                    } catch (InflationException e) {
-                        handleInflationException(sbn, e);
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void onNotificationPosted(final StatusBarNotification sbn,
-                final RankingMap rankingMap) {
-            if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
-            if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
-                mHandler.post(() -> {
-                    processForRemoteInput(sbn.getNotification());
-                    String key = sbn.getKey();
-                    mKeysKeptForRemoteInput.remove(key);
-                    boolean isUpdate = mNotificationData.get(key) != null;
-                    // In case we don't allow child notifications, we ignore children of
-                    // notifications that have a summary, since we're not going to show them
-                    // anyway. This is true also when the summary is canceled,
-                    // because children are automatically canceled by NoMan in that case.
-                    if (!ENABLE_CHILD_NOTIFICATIONS
-                            && mGroupManager.isChildInGroupWithSummary(sbn)) {
-                        if (DEBUG) {
-                            Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
-                        }
-
-                        // Remove existing notification to avoid stale data.
-                        if (isUpdate) {
-                            removeNotification(key, rankingMap);
-                        } else {
-                            mNotificationData.updateRanking(rankingMap);
-                        }
-                        return;
-                    }
-                    try {
-                        if (isUpdate) {
-                            updateNotification(sbn, rankingMap);
-                        } else {
-                            addNotification(sbn, rankingMap);
-                        }
-                    } catch (InflationException e) {
-                        handleInflationException(sbn, e);
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void onNotificationRemoved(StatusBarNotification sbn,
-                final RankingMap rankingMap) {
-            if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
-            if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
-                final String key = sbn.getKey();
-                mHandler.post(() -> removeNotification(key, rankingMap));
-            }
-        }
-
-        @Override
-        public void onNotificationRankingUpdate(final RankingMap rankingMap) {
-            if (DEBUG) Log.d(TAG, "onRankingUpdate");
-            if (rankingMap != null) {
-                RankingMap r = onPluginRankingUpdate(rankingMap);
-                mHandler.post(() -> updateNotificationRanking(r));
-            }
-        }
-
-    };
+    protected NotificationListener mNotificationListener;
 
     protected void notifyUserAboutHiddenNotifications() {
         if (0 != Settings.Secure.getInt(mContext.getContentResolver(),
@@ -6075,51 +5995,6 @@
         row.updateNotification(entry);
     }
 
-    /**
-     * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
-     * via first-class API.
-     *
-     * TODO: Remove once enough apps specify remote inputs on their own.
-     */
-    private void processForRemoteInput(Notification n) {
-        if (!ENABLE_REMOTE_INPUT) return;
-
-        if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
-                (n.actions == null || n.actions.length == 0)) {
-            Notification.Action viableAction = null;
-            Notification.WearableExtender we = new Notification.WearableExtender(n);
-
-            List<Notification.Action> actions = we.getActions();
-            final int numActions = actions.size();
-
-            for (int i = 0; i < numActions; i++) {
-                Notification.Action action = actions.get(i);
-                if (action == null) {
-                    continue;
-                }
-                RemoteInput[] remoteInputs = action.getRemoteInputs();
-                if (remoteInputs == null) {
-                    continue;
-                }
-                for (RemoteInput ri : remoteInputs) {
-                    if (ri.getAllowFreeFormInput()) {
-                        viableAction = action;
-                        break;
-                    }
-                }
-                if (viableAction != null) {
-                    break;
-                }
-            }
-
-            if (viableAction != null) {
-                Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n);
-                rebuilder.setActions(viableAction);
-                rebuilder.build(); // will rewrite n
-            }
-        }
-    }
-
     public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
         if (!isDeviceProvisioned()) return;
 
@@ -6508,8 +6383,9 @@
         mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount());
     }
 
-    public void updateNotification(StatusBarNotification notification, RankingMap ranking)
-            throws InflationException {
+    // TODO: Move this to NotificationEntryManager once it is created.
+    private void updateNotificationInternal(StatusBarNotification notification,
+            RankingMap ranking) throws InflationException {
         if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
 
         final String key = notification.getKey();
@@ -6559,6 +6435,15 @@
         setAreThereNotifications();
     }
 
+    @Override
+    public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
+        try {
+            updateNotificationInternal(notification, ranking);
+        } catch (InflationException e) {
+            handleInflationException(notification, e);
+        }
+    }
+
     protected void notifyHeadsUpGoingToSleep() {
         maybeEscalateHeadsUp();
     }
@@ -6755,6 +6640,11 @@
         return mLatestRankingMap;
     }
 
+    @Override
+    public Set<String> getKeysKeptForRemoteInput() {
+        return mKeysKeptForRemoteInput;
+    }
+
     private final NotificationInfo.CheckSaveListener mCheckSaveListener =
             (Runnable saveImportance, StatusBarNotification sbn) -> {
                 // If the user has security enabled, show challenge if the setting is changed.
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0d41e20..3e70980 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -30,7 +30,6 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.ColorStateList;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
@@ -67,8 +66,6 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
-import com.android.systemui.HardwareUiLayout;
-import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.plugins.VolumeDialogController;
@@ -97,11 +94,12 @@
     private final VolumeDialogController mController;
 
     private Window mWindow;
-    private HardwareUiLayout mHardwareLayout;
     private CustomDialog mDialog;
     private ViewGroup mDialogView;
     private ViewGroup mDialogRowsView;
     private ViewGroup mDialogContentView;
+    private ImageButton mRingerIcon;
+    private TextView mRingerStatus;
     private final List<VolumeRow> mRows = new ArrayList<>();
     private ConfigurableTexts mConfigurableTexts;
     private final SparseBooleanArray mDynamic = new SparseBooleanArray();
@@ -111,6 +109,7 @@
     private final Accessibility mAccessibility = new Accessibility();
     private final ColorStateList mActiveSliderTint;
     private final ColorStateList mInactiveSliderTint;
+    private VolumeUiLayout mHardwareLayout;
 
     private boolean mShowing;
     private boolean mShowA11yStream;
@@ -160,17 +159,34 @@
         mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
         mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
                 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
-        mWindow.addFlags(
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+        mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+        mWindow.setTitle(VolumeDialogImpl.class.getSimpleName());
         mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
         mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
 
         mDialog.setContentView(R.layout.volume_dialog);
-        mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
+        mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
+            @Override
+            public void onShow(DialogInterface dialog) {
+                mDialogView.setTranslationY(-mDialogView.getHeight());
+                mDialogView.setAlpha(0);
+                mDialogView.animate()
+                        .alpha(1)
+                        .translationY(0)
+                        .setDuration(300)
+                        .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
+                        .withEndAction(() -> {
+                            mWindow.getDecorView().requestAccessibilityFocus();
+                        })
+                        .start();
+            }
+        });
+        mDialogView = mDialog.findViewById(R.id.volume_dialog);
         mDialogView.setOnHoverListener(new View.OnHoverListener() {
             @Override
             public boolean onHover(View v, MotionEvent event) {
@@ -181,18 +197,20 @@
                 return true;
             }
         });
-        mHardwareLayout = HardwareUiLayout.get(mDialogView);
+        mHardwareLayout = VolumeUiLayout.get(mDialogView);
         mHardwareLayout.setOutsideTouchListener(view -> dismiss(DISMISS_REASON_TOUCH_OUTSIDE));
 
         mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content);
         mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows);
+        mRingerIcon = mDialogContentView.findViewById(R.id.ringer_icon);
+        mRingerStatus = mDialogContentView.findViewById(R.id.ringer_status);
 
         if (mRows.isEmpty()) {
             addRow(AudioManager.STREAM_MUSIC,
                     R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true);
             if (!AudioSystem.isSingleVolume(mContext)) {
                 addRow(AudioManager.STREAM_RING,
-                        R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
+                        R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, false);
                 addRow(AudioManager.STREAM_ALARM,
                         R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false);
                 addRow(AudioManager.STREAM_VOICE_CALL,
@@ -208,6 +226,7 @@
             addExistingRows();
         }
         updateRowsH(getActiveRow());
+        initRingerH();
     }
 
     private ColorStateList loadColorStateList(int colorResId) {
@@ -374,6 +393,30 @@
         }
     }
 
+    public void initRingerH() {
+        mRingerIcon.setOnClickListener(v -> {
+            Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, AudioManager.STREAM_RING,
+                    mRingerIcon.getTag());
+            final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
+            final boolean hasVibrator = mController.hasVibrator();
+            if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
+                if (hasVibrator) {
+                    mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
+                } else {
+                    final boolean wasZero = ss.level == 0;
+                    mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0);
+                }
+            } else {
+                mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
+                if (ss.level == 0) {
+                    mController.setStreamVolume(AudioManager.STREAM_RING, 1);
+                }
+            }
+            updateRingerH();
+        });
+        updateRingerH();
+    }
+
     public void show(int reason) {
         mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
     }
@@ -389,27 +432,12 @@
         rescheduleTimeoutH();
         if (mShowing) return;
         mShowing = true;
-        mHardwareLayout.setTranslationX(getAnimTranslation());
-        mHardwareLayout.setAlpha(0);
-        mHardwareLayout.animate()
-                .alpha(1)
-                .translationX(0)
-                .setDuration(300)
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .withEndAction(() -> {
-                    mDialog.show();
-                    mWindow.getDecorView().requestAccessibilityFocus();
-                })
-                .start();
+
+        mDialog.show();
         Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
         mController.notifyVisible(true);
     }
 
-    private float getAnimTranslation() {
-        return mContext.getResources().getDimension(
-                R.dimen.volume_dialog_panel_width) / 2;
-    }
-
     protected void rescheduleTimeoutH() {
         mHandler.removeMessages(H.DISMISS);
         final int timeout = computeTimeoutH();
@@ -423,7 +451,6 @@
         if (mAccessibility.mFeedbackEnabled) return 20000;
         if (mHovering) return 16000;
         if (mSafetyWarning != null) return 5000;
-        if (mActiveStream == AudioManager.STREAM_MUSIC) return 1500;
         return 3000;
     }
 
@@ -431,16 +458,22 @@
         mHandler.removeMessages(H.DISMISS);
         mHandler.removeMessages(H.SHOW);
         if (!mShowing) return;
+        mDialogView.animate().cancel();
         mShowing = false;
-        mHardwareLayout.setTranslationX(0);
-        mHardwareLayout.setAlpha(1);
-        mHardwareLayout.animate()
+
+        mDialogView.setTranslationY(0);
+        mDialogView.setAlpha(1);
+        mDialogView.animate()
                 .alpha(0)
-                .translationX(getAnimTranslation())
-                .setDuration(300)
-                .withEndAction(() -> mDialog.dismiss())
+                .translationY(-mDialogView.getHeight())
+                .setDuration(250)
                 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+                .withEndAction(() -> mHandler.postDelayed(() -> {
+                    if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
+                    mDialog.dismiss();
+                }, 50))
                 .start();
+
         if (mAccessibilityMgr.isEnabled()) {
             AccessibilityEvent event =
                     AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
@@ -493,6 +526,53 @@
         }
     }
 
+    protected void updateRingerH() {
+        if (mState != null) {
+            final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
+            switch (mState.ringerModeInternal) {
+                case AudioManager.RINGER_MODE_VIBRATE:
+                    mRingerStatus.setText(R.string.volume_ringer_status_vibrate);
+                    mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
+                    mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
+                    break;
+                case AudioManager.RINGER_MODE_SILENT:
+                    mRingerStatus.setText(R.string.volume_ringer_status_silent);
+                    mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+                    mRingerIcon.setContentDescription(mContext.getString(
+                            R.string.volume_stream_content_description_unmute,
+                            getStreamLabelH(ss)));
+                    mRingerIcon.setTag(Events.ICON_STATE_MUTE);
+                    break;
+                case AudioManager.RINGER_MODE_NORMAL:
+                default:
+                    boolean muted = (mAutomute && ss.level == 0) || ss.muted ? true : false;
+                    if (muted) {
+                        mRingerStatus.setText(R.string.volume_ringer_status_silent);
+                        mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+                        mRingerIcon.setContentDescription(mContext.getString(
+                                R.string.volume_stream_content_description_unmute,
+                                getStreamLabelH(ss)));
+                        mRingerIcon.setTag(Events.ICON_STATE_MUTE);
+                    } else {
+                        mRingerStatus.setText(R.string.volume_ringer_status_normal);
+                        mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
+                        if (mController.hasVibrator()) {
+                            mRingerIcon.setContentDescription(mContext.getString(
+                                    mShowA11yStream
+                                            ? R.string.volume_stream_content_description_vibrate_a11y
+                                            : R.string.volume_stream_content_description_vibrate,
+                                    getStreamLabelH(ss)));
+
+                        } else {
+                            mRingerIcon.setContentDescription(getStreamLabelH(ss));
+                        }
+                        mRingerIcon.setTag(Events.ICON_STATE_UNMUTE);
+                    }
+                    break;
+            }
+        }
+    }
+
     private void trimObsoleteH() {
         if (D.BUG) Log.d(TAG, "trimObsoleteH");
         for (int i = mRows.size() - 1; i >= 0; i--) {
@@ -529,6 +609,7 @@
         for (VolumeRow row : mRows) {
             updateVolumeRowH(row);
         }
+        updateRingerH();
     }
 
     private void updateVolumeRowH(VolumeRow row) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
new file mode 100644
index 0000000..49ac9b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
@@ -0,0 +1,66 @@
+/*
+ * 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.volume;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+
+public class VolumeUiLayout extends FrameLayout  {
+
+    public VolumeUiLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener);
+    }
+
+    @Override
+    public ViewOutlineProvider getOutlineProvider() {
+        return super.getOutlineProvider();
+    }
+
+    public void setOutsideTouchListener(OnClickListener onClickListener) {
+        requestLayout();
+        setOnClickListener(onClickListener);
+        setClickable(true);
+        setFocusable(true);
+    }
+
+    public static VolumeUiLayout get(View v) {
+        if (v instanceof VolumeUiLayout) return (VolumeUiLayout) v;
+        if (v.getParent() instanceof View) {
+            return get((View) v.getParent());
+        }
+        return null;
+    }
+
+    private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> {
+        inoutInfo.setTouchableInsets(
+                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+    };
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index b695919..9d44895 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -54,11 +54,15 @@
     mockito-target-minus-junit4 \
     SystemUI-proto \
     SystemUI-tags \
-    legacy-android-test \
     testables \
     truth-prebuilt \
 
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner \
+    telephony-common \
+    android.car \
+    android.test.base \
+
 
 LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui:com.android.keyguard
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
new file mode 100644
index 0000000..6ecfe3e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationListenerTest extends SysuiTestCase {
+    private static final String TEST_PACKAGE_NAME = "test";
+    private static final int TEST_UID = 0;
+
+    private NotificationPresenter mPresenter;
+    private Handler mHandler;
+    private NotificationListener mListener;
+    private StatusBarNotification mSbn;
+    private NotificationListenerService.RankingMap mRanking;
+    private Set<String> mKeysKeptForRemoteInput;
+    private NotificationData mNotificationData;
+
+    @Before
+    public void setUp() {
+        mHandler = new Handler(Looper.getMainLooper());
+        mPresenter = mock(NotificationPresenter.class);
+        mNotificationData = mock(NotificationData.class);
+        mRanking = mock(NotificationListenerService.RankingMap.class);
+        mKeysKeptForRemoteInput = new HashSet<>();
+
+        when(mPresenter.getHandler()).thenReturn(mHandler);
+        when(mPresenter.getNotificationData()).thenReturn(mNotificationData);
+        when(mPresenter.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput);
+
+        mListener = new NotificationListener(mPresenter, mContext);
+        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
+                new Notification(), UserHandle.CURRENT, null, 0);
+    }
+
+    @Test
+    public void testNotificationAddCallsAddNotification() {
+        mListener.onNotificationPosted(mSbn, mRanking);
+        waitForIdleSync(mHandler);
+        verify(mPresenter).addNotification(mSbn, mRanking);
+    }
+
+    @Test
+    public void testPostNotificationRemovesKeyKeptForRemoteInput() {
+        mKeysKeptForRemoteInput.add(mSbn.getKey());
+        mListener.onNotificationPosted(mSbn, mRanking);
+        waitForIdleSync(mHandler);
+        assertTrue(mKeysKeptForRemoteInput.isEmpty());
+    }
+
+    @Test
+    public void testNotificationUpdateCallsUpdateNotification() {
+        when(mNotificationData.get(mSbn.getKey())).thenReturn(new NotificationData.Entry(mSbn));
+        mListener.onNotificationPosted(mSbn, mRanking);
+        waitForIdleSync(mHandler);
+        verify(mPresenter).updateNotification(mSbn, mRanking);
+    }
+
+    @Test
+    public void testNotificationRemovalCallsRemoveNotification() {
+        mListener.onNotificationRemoved(mSbn, mRanking);
+        waitForIdleSync(mHandler);
+        verify(mPresenter).removeNotification(mSbn.getKey(), mRanking);
+    }
+
+    @Test
+    public void testRankingUpdateCallsNotificationRankingUpdate() {
+        mListener.onNotificationRankingUpdate(mRanking);
+        waitForIdleSync(mHandler);
+        // RankingMap may be modified by plugins.
+        verify(mPresenter).updateNotificationRanking(any());
+    }
+}
diff --git a/packages/WAPPushManager/tests/Android.mk b/packages/WAPPushManager/tests/Android.mk
index 1dea798..bfc85ab 100644
--- a/packages/WAPPushManager/tests/Android.mk
+++ b/packages/WAPPushManager/tests/Android.mk
@@ -18,8 +18,8 @@
 # We only want this apk build for tests.
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/sax/tests/saxtests/Android.mk b/sax/tests/saxtests/Android.mk
index d3fbd05..e0e490c 100644
--- a/sax/tests/saxtests/Android.mk
+++ b/sax/tests/saxtests/Android.mk
@@ -7,8 +7,8 @@
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 LOCAL_PACKAGE_NAME := FrameworksSaxTests
 
 include $(BUILD_PACKAGE)
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 2d2424f..9bfdd0c 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -433,7 +433,9 @@
                         || !otherStack.affectedBySplitScreenResize()) {
                     continue;
                 }
-                otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+                otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+                        false /* animate */, false /* showRecents */,
+                        false /* sendNonResizeableNotification */);
             }
         } finally {
             mSupervisor.mWindowManager.continueSurfaceLayout();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7b4703a..5d6cf74 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10570,7 +10570,8 @@
                 if (toTop) {
                     stack.moveToFront("setTaskWindowingModeSplitScreenPrimary", task);
                 }
-                stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, animate, showRecents);
+                stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, animate, showRecents,
+                        true /* sendNonResizeableNotification */);
                 return windowingMode != task.getWindowingMode();
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -20679,7 +20680,7 @@
                     if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
                             + app.processName + " new config " + configCopy);
                     mLifecycleManager.scheduleTransaction(app.thread,
-                            new ConfigurationChangeItem(configCopy));
+                            ConfigurationChangeItem.obtain(configCopy));
                 }
             } catch (Exception e) {
                 Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index a089e6c..60a6236 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -618,7 +618,7 @@
                             + ", displayId=" + displayId + ", config=" + config);
 
             service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
-                    new MoveToDisplayItem(displayId, config));
+                    MoveToDisplayItem.obtain(displayId, config));
         } catch (RemoteException e) {
             // If process died, whatever.
         }
@@ -636,7 +636,7 @@
                     + config);
 
             service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
-                    new ActivityConfigurationChangeItem(config));
+                    ActivityConfigurationChangeItem.obtain(config));
         } catch (RemoteException e) {
             // If process died, whatever.
         }
@@ -663,7 +663,7 @@
     private void scheduleMultiWindowModeChanged(Configuration overrideConfig) {
         try {
             service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
-                    new MultiWindowModeChangeItem(mLastReportedMultiWindowMode,
+                    MultiWindowModeChangeItem.obtain(mLastReportedMultiWindowMode,
                             overrideConfig));
         } catch (Exception e) {
             // If process died, I don't care.
@@ -691,7 +691,7 @@
     private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
         try {
             service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
-                    new PipModeChangeItem(mLastReportedPictureInPictureMode,
+                    PipModeChangeItem.obtain(mLastReportedPictureInPictureMode,
                             overrideConfig));
         } catch (Exception e) {
             // If process died, no one cares.
@@ -1380,7 +1380,7 @@
                 ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
                 ar.add(rintent);
                 service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
-                        new NewIntentItem(ar, state == PAUSED));
+                        NewIntentItem.obtain(ar, state == PAUSED));
                 unsent = false;
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
@@ -1603,7 +1603,7 @@
             sleeping = false;
             app.pendingUiClean = true;
             service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
-                    new WindowVisibilityItem(true /* showWindow */));
+                    WindowVisibilityItem.obtain(true /* showWindow */));
             // The activity may be waiting for stop, but that is no longer appropriate for it.
             mStackSupervisor.mStoppingActivities.remove(this);
             mStackSupervisor.mGoingToSleepActivities.remove(this);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index edf9813..cf40be5 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -483,10 +483,12 @@
 
     @Override
     public void setWindowingMode(int windowingMode) {
-        setWindowingMode(windowingMode, false /* animate */, true /* showRecents */);
+        setWindowingMode(windowingMode, false /* animate */, true /* showRecents */,
+                true /* sendNonResizeableNotification */);
     }
 
-    void setWindowingMode(int preferredWindowingMode, boolean animate, boolean showRecents) {
+    void setWindowingMode(int preferredWindowingMode, boolean animate, boolean showRecents,
+            boolean sendNonResizeableNotification) {
         final int currentMode = getWindowingMode();
         final ActivityDisplay display = getDisplay();
         final TaskRecord topTask = topTask();
@@ -505,7 +507,8 @@
         final boolean alreadyInSplitScreenMode = display.hasSplitScreenPrimaryStack();
 
         // Take any required action due to us not supporting the preferred windowing mode.
-        if (windowingMode != preferredWindowingMode && isActivityTypeStandardOrUndefined()) {
+        if (sendNonResizeableNotification
+                && windowingMode != preferredWindowingMode && isActivityTypeStandardOrUndefined()) {
             if (alreadyInSplitScreenMode
                     && (preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                     || preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
@@ -524,8 +527,9 @@
         final WindowManagerService wm = mService.mWindowManager;
         final ActivityRecord topActivity = getTopActivity();
 
-        if (windowingMode != WINDOWING_MODE_FULLSCREEN && topActivity != null
-                && topActivity.isNonResizableOrForcedResizable() && !topActivity.noDisplay) {
+        if (sendNonResizeableNotification && windowingMode != WINDOWING_MODE_FULLSCREEN
+                && topActivity != null && topActivity.isNonResizableOrForcedResizable()
+                && !topActivity.noDisplay) {
             // Inform the user that they are starting an app that may not work correctly in
             // multi-window mode.
             final String packageName = topActivity.appInfo.packageName;
@@ -1430,7 +1434,7 @@
                 mService.updateUsageStats(prev, false);
 
                 mService.mLifecycleManager.scheduleTransaction(prev.app.thread, prev.appToken,
-                        new PauseActivityItem(prev.finishing, userLeaving,
+                        PauseActivityItem.obtain(prev.finishing, userLeaving,
                                 prev.configChangeFlags, pauseImmediately));
             } catch (Exception e) {
                 // Ignore exception, if process died other code will cleanup.
@@ -2061,7 +2065,7 @@
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
                                 "Scheduling invisibility: " + r);
                         mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
-                                new WindowVisibilityItem(false /* showWindow */));
+                                WindowVisibilityItem.obtain(false /* showWindow */));
                     }
 
                     // Reset the flag indicating that an app can enter picture-in-picture once the
@@ -2589,13 +2593,13 @@
                             if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
                                     "Delivering results to " + next + ": " + a);
                             mService.mLifecycleManager.scheduleTransaction(next.app.thread,
-                                    next.appToken, new ActivityResultItem(a));
+                                    next.appToken, ActivityResultItem.obtain(a));
                         }
                     }
 
                     if (next.newIntents != null) {
                         mService.mLifecycleManager.scheduleTransaction(next.app.thread,
-                                next.appToken, new NewIntentItem(next.newIntents,
+                                next.appToken, NewIntentItem.obtain(next.newIntents,
                                         false /* andPause */));
                     }
 
@@ -2614,7 +2618,7 @@
                     next.app.forceProcessStateUpTo(mService.mTopProcessState);
                     next.clearOptionsLocked();
                     mService.mLifecycleManager.scheduleTransaction(next.app.thread, next.appToken,
-                            new ResumeActivityItem(next.app.repProcState,
+                            ResumeActivityItem.obtain(next.app.repProcState,
                                     mService.isNextTransitionForward()));
 
                     if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
@@ -3266,7 +3270,7 @@
                 list.add(new ResultInfo(resultWho, requestCode,
                         resultCode, data));
                 mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
-                        new ActivityResultItem(list));
+                        ActivityResultItem.obtain(list));
                 return;
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending result to " + r, e);
@@ -3395,7 +3399,7 @@
                 EventLogTags.writeAmStopActivity(
                         r.userId, System.identityHashCode(r), r.shortComponentName);
                 mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
-                        new StopActivityItem(r.visible, r.configChangeFlags));
+                        StopActivityItem.obtain(r.visible, r.configChangeFlags));
                 if (shouldSleepOrShutDownActivities()) {
                     r.setSleeping(true);
                 }
@@ -4201,7 +4205,7 @@
             try {
                 if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r);
                 mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
-                        new DestroyActivityItem(r.finishing, r.configChangeFlags));
+                        DestroyActivityItem.obtain(r.finishing, r.configChangeFlags));
             } catch (Exception e) {
                 // We can just ignore exceptions here...  if the process
                 // has crashed, our death notification will clean things
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 7561d0f..edaa511 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1239,9 +1239,16 @@
 
     ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags) {
         synchronized (mService) {
-            return mService.getPackageManagerInternalLocked().resolveIntent(intent, resolvedType,
-                    PackageManager.MATCH_INSTANT | PackageManager.MATCH_DEFAULT_ONLY | flags
-                    | ActivityManagerService.STOCK_PM_FLAGS, userId, true);
+            try {
+                Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent");
+                return mService.getPackageManagerInternalLocked().resolveIntent(
+                        intent, resolvedType, PackageManager.MATCH_INSTANT
+                                | PackageManager.MATCH_DEFAULT_ONLY | flags
+                                | ActivityManagerService.STOCK_PM_FLAGS, userId, true);
+
+            } finally {
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+            }
         }
     }
 
@@ -1400,9 +1407,9 @@
 
 
                 // Create activity launch transaction.
-                final ClientTransaction clientTransaction = new ClientTransaction(app.thread,
+                final ClientTransaction clientTransaction = ClientTransaction.obtain(app.thread,
                         r.appToken);
-                clientTransaction.addCallback(new LaunchActivityItem(new Intent(r.intent),
+                clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                         System.identityHashCode(r), r.info,
                         // TODO: Have this take the merged configuration instead of separate global
                         // and override configs.
@@ -1415,9 +1422,9 @@
                 // Set desired final state.
                 final ActivityLifecycleItem lifecycleItem;
                 if (andResume) {
-                    lifecycleItem = new ResumeActivityItem(mService.isNextTransitionForward());
+                    lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward());
                 } else {
-                    lifecycleItem = new PauseActivityItem();
+                    lifecycleItem = PauseActivityItem.obtain();
                 }
                 clientTransaction.setLifecycleStateRequest(lifecycleItem);
 
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index dda8e9c..10fb6e2 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -96,6 +96,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.voice.IVoiceInteractionSession;
diff --git a/services/core/java/com/android/server/am/ClientLifecycleManager.java b/services/core/java/com/android/server/am/ClientLifecycleManager.java
index c04d103..cc70f18 100644
--- a/services/core/java/com/android/server/am/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/am/ClientLifecycleManager.java
@@ -43,6 +43,7 @@
      */
     void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
         transaction.schedule();
+        transaction.recycle();
     }
 
     /**
@@ -100,7 +101,7 @@
      */
     private static ClientTransaction transactionWithState(@NonNull IApplicationThread client,
             @NonNull IBinder activityToken, @NonNull ActivityLifecycleItem stateRequest) {
-        final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+        final ClientTransaction clientTransaction = ClientTransaction.obtain(client, activityToken);
         clientTransaction.setLifecycleStateRequest(stateRequest);
         return clientTransaction;
     }
@@ -113,7 +114,7 @@
      */
     private static ClientTransaction transactionWithCallback(@NonNull IApplicationThread client,
             IBinder activityToken, @NonNull ClientTransactionItem callback) {
-        final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+        final ClientTransaction clientTransaction = ClientTransaction.obtain(client, activityToken);
         clientTransaction.addCallback(callback);
         return clientTransaction;
     }
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 9cd52d7..bdfd4bd 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -323,7 +323,7 @@
     private final BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            EndPoint target = new EndPoint(null, null, context.getUserId());
+            EndPoint target = new EndPoint(null, null, getSendingUserId());
             updateRunningAccounts(target /* sync targets for user */);
         }
     };
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8b2854c..e0f3ec7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -607,6 +607,12 @@
      */
     private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = true;
 
+    /**
+     * Permissions required in order to receive instant application lifecycle broadcasts.
+     */
+    private static final String[] INSTANT_APP_BROADCAST_PERMISSION =
+            new String[] { android.Manifest.permission.ACCESS_INSTANT_APPS };
+
     final ServiceThread mHandlerThread;
 
     final PackageHandler mHandler;
@@ -1967,16 +1973,20 @@
 
             // Determine the set of users who are adding this package for
             // the first time vs. those who are seeing an update.
-            int[] firstUsers = EMPTY_INT_ARRAY;
-            int[] updateUsers = EMPTY_INT_ARRAY;
+            int[] firstUserIds = EMPTY_INT_ARRAY;
+            int[] firstInstantUserIds = EMPTY_INT_ARRAY;
+            int[] updateUserIds = EMPTY_INT_ARRAY;
+            int[] instantUserIds = EMPTY_INT_ARRAY;
             final boolean allNewUsers = res.origUsers == null || res.origUsers.length == 0;
             final PackageSetting ps = (PackageSetting) res.pkg.mExtras;
             for (int newUser : res.newUsers) {
-                if (ps.getInstantApp(newUser)) {
-                    continue;
-                }
+                final boolean isInstantApp = ps.getInstantApp(newUser);
                 if (allNewUsers) {
-                    firstUsers = ArrayUtils.appendInt(firstUsers, newUser);
+                    if (isInstantApp) {
+                        firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser);
+                    } else {
+                        firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser);
+                    }
                     continue;
                 }
                 boolean isNew = true;
@@ -1987,9 +1997,17 @@
                     }
                 }
                 if (isNew) {
-                    firstUsers = ArrayUtils.appendInt(firstUsers, newUser);
+                    if (isInstantApp) {
+                        firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser);
+                    } else {
+                        firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser);
+                    }
                 } else {
-                    updateUsers = ArrayUtils.appendInt(updateUsers, newUser);
+                    if (isInstantApp) {
+                        instantUserIds = ArrayUtils.appendInt(instantUserIds, newUser);
+                    } else {
+                        updateUserIds = ArrayUtils.appendInt(updateUserIds, newUser);
+                    }
                 }
             }
 
@@ -2002,7 +2020,7 @@
                 int appId = UserHandle.getAppId(res.uid);
                 boolean isSystem = res.pkg.applicationInfo.isSystemApp();
                 sendPackageAddedForNewUsers(packageName, isSystem || virtualPreload,
-                        virtualPreload /*startReceiver*/, appId, firstUsers);
+                        virtualPreload /*startReceiver*/, appId, firstUserIds, firstInstantUserIds);
 
                 // Send added for users that don't see the package for the first time
                 Bundle extras = new Bundle(1);
@@ -2012,11 +2030,13 @@
                 }
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                         extras, 0 /*flags*/,
-                        null /*targetPackage*/, null /*finishedReceiver*/, updateUsers);
+                        null /*targetPackage*/, null /*finishedReceiver*/,
+                        updateUserIds, instantUserIds);
                 if (installerPackageName != null) {
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                             extras, 0 /*flags*/,
-                            installerPackageName, null /*finishedReceiver*/, updateUsers);
+                            installerPackageName, null /*finishedReceiver*/,
+                            updateUserIds, instantUserIds);
                 }
 
                 // Send replaced for users that don't see the package for the first time
@@ -2024,24 +2044,26 @@
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
                             packageName, extras, 0 /*flags*/,
                             null /*targetPackage*/, null /*finishedReceiver*/,
-                            updateUsers);
+                            updateUserIds, instantUserIds);
                     if (installerPackageName != null) {
                         sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
                                 extras, 0 /*flags*/,
-                                installerPackageName, null /*finishedReceiver*/, updateUsers);
+                                installerPackageName, null /*finishedReceiver*/,
+                                updateUserIds, instantUserIds);
                     }
                     sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
                             null /*package*/, null /*extras*/, 0 /*flags*/,
                             packageName /*targetPackage*/,
-                            null /*finishedReceiver*/, updateUsers);
+                            null /*finishedReceiver*/, updateUserIds, instantUserIds);
                 } else if (launchedForRestore && !isSystemApp(res.pkg)) {
                     // First-install and we did a restore, so we're responsible for the
                     // first-launch broadcast.
                     if (DEBUG_BACKUP) {
                         Slog.i(TAG, "Post-restore of " + packageName
-                                + " sending FIRST_LAUNCH in " + Arrays.toString(firstUsers));
+                                + " sending FIRST_LAUNCH in " + Arrays.toString(firstUserIds));
                     }
-                    sendFirstLaunchBroadcast(packageName, installerPackage, firstUsers);
+                    sendFirstLaunchBroadcast(packageName, installerPackage,
+                            firstUserIds, firstInstantUserIds);
                 }
 
                 // Send broadcast package appeared if forward locked/external for all users
@@ -2059,9 +2081,9 @@
             }
 
             // Work that needs to happen on first install within each user
-            if (firstUsers != null && firstUsers.length > 0) {
+            if (firstUserIds != null && firstUserIds.length > 0) {
                 synchronized (mPackages) {
-                    for (int userId : firstUsers) {
+                    for (int userId : firstUserIds) {
                         // If this app is a browser and it's newly-installed for some
                         // users, clear any default-browser state in those users. The
                         // app's nature doesn't depend on the user, so we can just check
@@ -2099,7 +2121,7 @@
             // should not change.
             // Don't notify the manager for ephemeral apps as they are not expected to
             // survive long enough to benefit of background optimizations.
-            for (int userId : firstUsers) {
+            for (int userId : firstUserIds) {
                 PackageInfo info = getPackageInfo(packageName, /*flags*/ 0, userId);
                 // There's a race currently where some install events may interleave with an uninstall.
                 // This can lead to package info being null (b/36642664).
@@ -9714,6 +9736,7 @@
         // them in the case where we're not upgrading or booting for the first time.
         String primaryCpuAbiFromSettings = null;
         String secondaryCpuAbiFromSettings = null;
+        boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
 
         // writer
         synchronized (mPackages) {
@@ -9791,11 +9814,14 @@
                 }
             }
 
-            if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) == 0) {
+            if (!needToDeriveAbi) {
                 PackageSetting foundPs = mSettings.getPackageLPr(pkg.packageName);
                 if (foundPs != null) {
                     primaryCpuAbiFromSettings = foundPs.primaryCpuAbiString;
                     secondaryCpuAbiFromSettings = foundPs.secondaryCpuAbiString;
+                } else {
+                    // when re-adding a system package failed after uninstalling updates.
+                    needToDeriveAbi = true;
                 }
             }
 
@@ -10011,7 +10037,7 @@
         final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
 
         if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
-            if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
+            if (needToDeriveAbi) {
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
                 final boolean extractNativeLibs = !pkg.isLibrary();
                 derivePackageAbi(pkg, cpuAbiOverride, extractNativeLibs, mAppLib32InstallDir);
@@ -12773,9 +12799,10 @@
         }
     };
 
+    @Override
     public void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
             final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
-            final int[] userIds) {
+            final int[] userIds, int[] instantUserIds) {
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -12788,33 +12815,11 @@
                     } else {
                         resolvedUserIds = userIds;
                     }
-                    for (int id : resolvedUserIds) {
-                        final Intent intent = new Intent(action,
-                                pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null);
-                        if (extras != null) {
-                            intent.putExtras(extras);
-                        }
-                        if (targetPkg != null) {
-                            intent.setPackage(targetPkg);
-                        }
-                        // Modify the UID when posting to other users
-                        int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-                        if (uid > 0 && UserHandle.getUserId(uid) != id) {
-                            uid = UserHandle.getUid(id, UserHandle.getAppId(uid));
-                            intent.putExtra(Intent.EXTRA_UID, uid);
-                        }
-                        intent.putExtra(Intent.EXTRA_USER_HANDLE, id);
-                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags);
-                        if (DEBUG_BROADCASTS) {
-                            RuntimeException here = new RuntimeException("here");
-                            here.fillInStackTrace();
-                            Slog.d(TAG, "Sending to user " + id + ": "
-                                    + intent.toShortString(false, true, false, false)
-                                    + " " + intent.getExtras(), here);
-                        }
-                        am.broadcastIntent(null, intent, null, finishedReceiver,
-                                0, null, null, null, android.app.AppOpsManager.OP_NONE,
-                                null, finishedReceiver != null, false, id);
+                    doSendBroadcast(am, action, pkg, extras, flags, targetPkg, finishedReceiver,
+                            resolvedUserIds, false);
+                    if (instantUserIds != null && instantUserIds != EMPTY_INT_ARRAY) {
+                        doSendBroadcast(am, action, pkg, extras, flags, targetPkg, finishedReceiver,
+                                instantUserIds, true);
                     }
                 } catch (RemoteException ex) {
                 }
@@ -12823,6 +12828,49 @@
     }
 
     /**
+     * Sends a broadcast for the given action.
+     * <p>If {@code isInstantApp} is {@code true}, then the broadcast is protected with
+     * the {@link android.Manifest.permission#ACCESS_INSTANT_APPS} permission. This allows
+     * the system and applications allowed to see instant applications to receive package
+     * lifecycle events for instant applications.
+     */
+    private void doSendBroadcast(IActivityManager am, String action, String pkg, Bundle extras,
+            int flags, String targetPkg, IIntentReceiver finishedReceiver,
+            int[] userIds, boolean isInstantApp)
+                    throws RemoteException {
+        for (int id : userIds) {
+            final Intent intent = new Intent(action,
+                    pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null);
+            final String[] requiredPermissions =
+                    isInstantApp ? INSTANT_APP_BROADCAST_PERMISSION : null;
+            if (extras != null) {
+                intent.putExtras(extras);
+            }
+            if (targetPkg != null) {
+                intent.setPackage(targetPkg);
+            }
+            // Modify the UID when posting to other users
+            int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+            if (uid > 0 && UserHandle.getUserId(uid) != id) {
+                uid = UserHandle.getUid(id, UserHandle.getAppId(uid));
+                intent.putExtra(Intent.EXTRA_UID, uid);
+            }
+            intent.putExtra(Intent.EXTRA_USER_HANDLE, id);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags);
+            if (DEBUG_BROADCASTS) {
+                RuntimeException here = new RuntimeException("here");
+                here.fillInStackTrace();
+                Slog.d(TAG, "Sending to user " + id + ": "
+                        + intent.toShortString(false, true, false, false)
+                        + " " + intent.getExtras(), here);
+            }
+            am.broadcastIntent(null, intent, null, finishedReceiver,
+                    0, null, null, requiredPermissions, android.app.AppOpsManager.OP_NONE,
+                    null, finishedReceiver != null, false, id);
+        }
+    }
+
+    /**
      * Check if the external storage media is available. This is true if there
      * is a mounted external storage medium or if the external storage is
      * emulated.
@@ -13071,8 +13119,11 @@
     private void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting,
             int userId) {
         final boolean isSystem = isSystemApp(pkgSetting) || isUpdatedSystemApp(pkgSetting);
+        final boolean isInstantApp = pkgSetting.getInstantApp(userId);
+        final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
+        final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
         sendPackageAddedForNewUsers(packageName, isSystem /*sendBootCompleted*/,
-                false /*startReceiver*/, pkgSetting.appId, userId);
+                false /*startReceiver*/, pkgSetting.appId, userIds, instantUserIds);
 
         // Send a session commit broadcast
         final PackageInstaller.SessionInfo info = new PackageInstaller.SessionInfo();
@@ -13081,18 +13132,21 @@
         sendSessionCommitBroadcast(info, userId);
     }
 
+    @Override
     public void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted,
-            boolean includeStopped, int appId, int... userIds) {
-        if (ArrayUtils.isEmpty(userIds)) {
+            boolean includeStopped, int appId, int[] userIds, int[] instantUserIds) {
+        if (ArrayUtils.isEmpty(userIds) && ArrayUtils.isEmpty(instantUserIds)) {
             return;
         }
         Bundle extras = new Bundle(1);
         // Set to UID of the first user, EXTRA_UID is automatically updated in sendPackageBroadcast
-        extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userIds[0], appId));
+        final int uid = UserHandle.getUid(
+                (ArrayUtils.isEmpty(userIds) ? instantUserIds[0] : userIds[0]), appId);
+        extras.putInt(Intent.EXTRA_UID, uid);
 
         sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
-                packageName, extras, 0, null, null, userIds);
-        if (sendBootCompleted) {
+                packageName, extras, 0, null, null, userIds, instantUserIds);
+        if (sendBootCompleted && !ArrayUtils.isEmpty(userIds)) {
             mHandler.post(() -> {
                         for (int userId : userIds) {
                             sendBootCompletedBroadcastToSystemApp(
@@ -13236,7 +13290,7 @@
                     suspended ? Intent.ACTION_PACKAGES_SUSPENDED
                             : Intent.ACTION_PACKAGES_UNSUSPENDED,
                     null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
-                    new int[] {userId});
+                    new int[] {userId}, null);
         }
     }
 
@@ -14124,7 +14178,8 @@
      * the first-launch broadcast will be sent implicitly on that basis in POST_INSTALL
      * handling.
      */
-    void notifyFirstLaunch(final String pkgName, final String installerPackage, final int userId) {
+    void notifyFirstLaunch(final String packageName, final String installerPackage,
+            final int userId) {
         // Serialize this with the rest of the install-process message chain.  In the
         // restore-at-install case, this Runnable will necessarily run before the
         // POST_INSTALL message is processed, so the contents of mRunningInstalls
@@ -14139,12 +14194,12 @@
                     if (data.res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
                         continue;
                     }
-                    if (pkgName.equals(data.res.pkg.applicationInfo.packageName)) {
+                    if (packageName.equals(data.res.pkg.applicationInfo.packageName)) {
                         // right package; but is it for the right user?
                         for (int uIndex = 0; uIndex < data.res.newUsers.length; uIndex++) {
                             if (userId == data.res.newUsers[uIndex]) {
                                 if (DEBUG_BACKUP) {
-                                    Slog.i(TAG, "Package " + pkgName
+                                    Slog.i(TAG, "Package " + packageName
                                             + " being restored so deferring FIRST_LAUNCH");
                                 }
                                 return;
@@ -14154,16 +14209,20 @@
                 }
                 // didn't find it, so not being restored
                 if (DEBUG_BACKUP) {
-                    Slog.i(TAG, "Package " + pkgName + " sending normal FIRST_LAUNCH");
+                    Slog.i(TAG, "Package " + packageName + " sending normal FIRST_LAUNCH");
                 }
-                sendFirstLaunchBroadcast(pkgName, installerPackage, new int[] {userId});
+                final boolean isInstantApp = isInstantApp(packageName, userId);
+                final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
+                final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
+                sendFirstLaunchBroadcast(packageName, installerPackage, userIds, instantUserIds);
             }
         });
     }
 
-    private void sendFirstLaunchBroadcast(String pkgName, String installerPkg, int[] userIds) {
+    private void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
+            int[] userIds, int[] instantUserIds) {
         sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
-                installerPkg, null, userIds);
+                installerPkg, null, userIds, instantUserIds);
     }
 
     private abstract class HandlerParams {
@@ -17310,6 +17369,7 @@
         int[] origUsers;
         int[] removedUsers = null;
         int[] broadcastUsers = null;
+        int[] instantUserIds = null;
         SparseArray<Integer> installReasons;
         boolean isRemovedPackageSystemUpdate = false;
         boolean isUpdate;
@@ -17355,7 +17415,7 @@
                 PackageInstalledInfo installedInfo = appearedChildPackages.valueAt(i);
                 packageSender.sendPackageAddedForNewUsers(installedInfo.name,
                     true /*sendBootCompleted*/, false /*startReceiver*/,
-                    UserHandle.getAppId(installedInfo.uid), installedInfo.newUsers);
+                    UserHandle.getAppId(installedInfo.uid), installedInfo.newUsers, null);
             }
         }
 
@@ -17364,18 +17424,18 @@
             extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid);
             extras.putBoolean(Intent.EXTRA_REPLACING, true);
             packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
-                removedPackage, extras, 0, null /*targetPackage*/, null, null);
+                removedPackage, extras, 0, null /*targetPackage*/, null, null, null);
             packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                removedPackage, extras, 0, null /*targetPackage*/, null, null);
+                removedPackage, extras, 0, null /*targetPackage*/, null, null, null);
             packageSender.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
-                null, null, 0, removedPackage, null, null);
+                null, null, 0, removedPackage, null, null, null);
             if (installerPackageName != null) {
                 packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                         removedPackage, extras, 0 /*flags*/,
-                        installerPackageName, null, null);
+                        installerPackageName, null, null, null);
                 packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
                         removedPackage, extras, 0 /*flags*/,
-                        installerPackageName, null, null);
+                        installerPackageName, null, null, null);
             }
         }
 
@@ -17396,23 +17456,24 @@
             extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers);
             if (removedPackage != null) {
                 packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
-                    removedPackage, extras, 0, null /*targetPackage*/, null, broadcastUsers);
+                    removedPackage, extras, 0, null /*targetPackage*/, null,
+                    broadcastUsers, instantUserIds);
                 if (installerPackageName != null) {
                     packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
                             removedPackage, extras, 0 /*flags*/,
-                            installerPackageName, null, broadcastUsers);
+                            installerPackageName, null, broadcastUsers, instantUserIds);
                 }
                 if (dataRemoved && !isRemovedPackageSystemUpdate) {
                     packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED,
                         removedPackage, extras,
                         Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
-                        null, null, broadcastUsers);
+                        null, null, broadcastUsers, instantUserIds);
                 }
             }
             if (removedAppId >= 0) {
                 packageSender.sendPackageBroadcast(Intent.ACTION_UID_REMOVED,
                     null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
-                    null, null, broadcastUsers);
+                    null, null, broadcastUsers, instantUserIds);
             }
         }
 
@@ -17424,12 +17485,14 @@
             }
 
             broadcastUsers = EMPTY_INT_ARRAY;
+            instantUserIds = EMPTY_INT_ARRAY;
             for (int i = userIds.length - 1; i >= 0; --i) {
                 final int userId = userIds[i];
                 if (deletedPackageSetting.getInstantApp(userId)) {
-                    continue;
+                    instantUserIds = ArrayUtils.appendInt(instantUserIds, userId);
+                } else {
+                    broadcastUsers = ArrayUtils.appendInt(broadcastUsers, userId);
                 }
-                broadcastUsers = ArrayUtils.appendInt(broadcastUsers, userId);
             }
         }
     }
@@ -19975,8 +20038,12 @@
         // little component state change.
         final int flags = !componentNames.contains(packageName)
                 ? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0;
+        final int userId = UserHandle.getUserId(packageUid);
+        final boolean isInstantApp = isInstantApp(packageName, userId);
+        final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
+        final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
         sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED,  packageName, extras, flags, null, null,
-                new int[] {UserHandle.getUserId(packageUid)});
+                userIds, instantUserIds);
     }
 
     @Override
@@ -21095,7 +21162,7 @@
             }
             String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
                     : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
-            sendPackageBroadcast(action, null, extras, 0, null, finishedReceiver, null);
+            sendPackageBroadcast(action, null, extras, 0, null, finishedReceiver, null, null);
         }
     }
 
@@ -23324,9 +23391,13 @@
 }
 
 interface PackageSender {
+    /**
+     * @param userIds User IDs where the action occurred on a full application
+     * @param instantUserIds User IDs where the action occurred on an instant application
+     */
     void sendPackageBroadcast(final String action, final String pkg,
         final Bundle extras, final int flags, final String targetPkg,
-        final IIntentReceiver finishedReceiver, final int[] userIds);
+        final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds);
     void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted,
-        boolean includeStopped, int appId, int... userIds);
+        boolean includeStopped, int appId, int[] userIds, int[] instantUserIds);
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 0907dd7..ee2f374 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2739,6 +2739,26 @@
         public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
             return ShortcutService.this.isRequestPinItemSupported(callingUserId, requestType);
         }
+
+        @Override
+        public boolean isForegroundDefaultLauncher(@NonNull String callingPackage, int callingUid) {
+            Preconditions.checkNotNull(callingPackage);
+
+            final int userId = UserHandle.getUserId(callingUid);
+            final ComponentName defaultLauncher = getDefaultLauncher(userId);
+            if (defaultLauncher == null) {
+                return false;
+            }
+            if (!callingPackage.equals(defaultLauncher.getPackageName())) {
+                return false;
+            }
+            synchronized (mLock) {
+                if (!isUidForegroundLocked(callingUid)) {
+                    return false;
+                }
+            }
+            return true;
+        }
     }
 
     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9a7e72a..61591bb 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -48,6 +48,7 @@
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
@@ -607,6 +608,8 @@
 
     PointerLocationView mPointerLocationView;
 
+    boolean mEmulateDisplayCutout = false;
+
     // During layout, the layer at which the doc window is placed.
     int mDockLayer;
     // During layout, this is the layer of the status bar.
@@ -965,6 +968,9 @@
             resolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.POLICY_CONTROL), false, this,
                     UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.EMULATE_DISPLAY_CUTOUT), false, this,
+                    UserHandle.USER_ALL);
             updateSettings();
         }
 
@@ -2396,6 +2402,10 @@
             if (mImmersiveModeConfirmation != null) {
                 mImmersiveModeConfirmation.loadSetting(mCurrentUserId);
             }
+            mEmulateDisplayCutout = Settings.Global.getInt(resolver,
+                    Settings.Global.EMULATE_DISPLAY_CUTOUT,
+                    Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF)
+                    != Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF;
         }
         synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
             PolicyControl.reloadFromSetting(mContext);
@@ -4436,7 +4446,7 @@
     /** {@inheritDoc} */
     @Override
     public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
-        displayFrames.onBeginLayout();
+        displayFrames.onBeginLayout(mEmulateDisplayCutout, mStatusBarHeight);
         // TODO(multi-display): This doesn't seem right...Maybe only apply to default display?
         mSystemGestures.screenWidth = displayFrames.mUnrestricted.width();
         mSystemGestures.screenHeight = displayFrames.mUnrestricted.height();
@@ -4506,6 +4516,14 @@
             }
         }
         layoutScreenDecorWindows(displayFrames, pf, df, dcf);
+
+        if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
+            // Make sure that the zone we're avoiding for the cutout is at least as tall as the
+            // status bar; otherwise fullscreen apps will end up cutting halfway into the status
+            // bar.
+            displayFrames.mDisplayCutoutSafe.top = Math.max(displayFrames.mDisplayCutoutSafe.top,
+                    displayFrames.mStable.top);
+        }
     }
 
     private void layoutScreenDecorWindows(DisplayFrames displayFrames, Rect pf, Rect df, Rect dcf) {
@@ -4641,11 +4659,15 @@
         final Rect dockFrame = displayFrames.mDock;
         mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation);
 
+        final Rect cutoutSafeUnrestricted = mTmpRect;
+        cutoutSafeUnrestricted.set(displayFrames.mUnrestricted);
+        cutoutSafeUnrestricted.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
+
         if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
             // It's a system nav bar or a portrait screen; nav bar goes on bottom.
-            final int top = displayFrames.mUnrestricted.bottom
+            final int top = cutoutSafeUnrestricted.bottom
                     - getNavigationBarHeight(rotation, uiMode);
-            mTmpNavigationFrame.set(0, top, displayWidth, displayFrames.mUnrestricted.bottom);
+            mTmpNavigationFrame.set(0, top, displayWidth, cutoutSafeUnrestricted.bottom);
             displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top;
             if (transientNavBarShowing) {
                 mNavigationBarController.setBarShowingLw(true);
@@ -4666,9 +4688,9 @@
             }
         } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
             // Landscape screen; nav bar goes to the right.
-            final int left = displayFrames.mUnrestricted.right
+            final int left = cutoutSafeUnrestricted.right
                     - getNavigationBarWidth(rotation, uiMode);
-            mTmpNavigationFrame.set(left, 0, displayFrames.mUnrestricted.right, displayHeight);
+            mTmpNavigationFrame.set(left, 0, cutoutSafeUnrestricted.right, displayHeight);
             displayFrames.mStable.right = displayFrames.mStableFullscreen.right = left;
             if (transientNavBarShowing) {
                 mNavigationBarController.setBarShowingLw(true);
@@ -4689,9 +4711,9 @@
             }
         } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
             // Seascape screen; nav bar goes to the left.
-            final int right = displayFrames.mUnrestricted.left
+            final int right = cutoutSafeUnrestricted.left
                     + getNavigationBarWidth(rotation, uiMode);
-            mTmpNavigationFrame.set(displayFrames.mUnrestricted.left, 0, right, displayHeight);
+            mTmpNavigationFrame.set(cutoutSafeUnrestricted.left, 0, right, displayHeight);
             displayFrames.mStable.left = displayFrames.mStableFullscreen.left = right;
             if (transientNavBarShowing) {
                 mNavigationBarController.setBarShowingLw(true);
@@ -4843,6 +4865,7 @@
 
         final int type = attrs.type;
         final int fl = PolicyControl.getWindowFlags(win, attrs);
+        final long fl2 = attrs.flags2;
         final int pfl = attrs.privateFlags;
         final int sim = attrs.softInputMode;
         final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null);
@@ -4863,6 +4886,14 @@
 
         final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
 
+        final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
+                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0
+                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0;
+
+        final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
+        final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
+        final boolean layoutInCutout = (fl2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0;
+
         sf.set(displayFrames.mStable);
 
         if (type == TYPE_INPUT_METHOD) {
@@ -4872,7 +4903,8 @@
             df.set(displayFrames.mDock);
             pf.set(displayFrames.mDock);
             // IM dock windows layout below the nav bar...
-            pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom;
+            pf.bottom = df.bottom = of.bottom = Math.min(displayFrames.mUnrestricted.bottom,
+                    displayFrames.mDisplayCutoutSafe.bottom);
             // ...with content insets above the nav bar
             cf.bottom = vf.bottom = displayFrames.mStable.bottom;
             if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) {
@@ -4943,8 +4975,7 @@
                 }
             }
 
-            if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR))
-                    == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
+            if (layoutInScreen && layoutInsetDecor) {
                 if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
                             + "): IN_SCREEN, INSET_DECOR");
                 // This is the case for a normal activity window: we want it to cover all of the
@@ -5021,6 +5052,9 @@
                         // moving from a window that is not hiding the status bar to one that is.
                         cf.set(displayFrames.mRestricted);
                     }
+                    if (requestedFullscreen && !layoutInCutout) {
+                        pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
+                    }
                     applyStableConstraints(sysUiFl, fl, cf, displayFrames);
                     if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
                         vf.set(displayFrames.mCurrent);
@@ -5028,7 +5062,7 @@
                         vf.set(cf);
                     }
                 }
-            } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl
+            } else if (layoutInScreen || (sysUiFl
                     & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                             | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
                 if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
@@ -5106,6 +5140,9 @@
                     of.set(displayFrames.mUnrestricted);
                     df.set(displayFrames.mUnrestricted);
                     pf.set(displayFrames.mUnrestricted);
+                    if (requestedFullscreen && !layoutInCutout) {
+                        pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
+                    }
                 } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
                     of.set(displayFrames.mRestricted);
                     df.set(displayFrames.mRestricted);
@@ -5176,9 +5213,27 @@
                         vf.set(cf);
                     }
                 }
+                pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
             }
         }
 
+        // Ensure that windows that did not request to be laid out in the cutout don't get laid
+        // out there.
+        if (!layoutInCutout) {
+            final Rect displayCutoutSafeExceptMaybeTop = mTmpRect;
+            displayCutoutSafeExceptMaybeTop.set(displayFrames.mDisplayCutoutSafe);
+            if (layoutInScreen && layoutInsetDecor) {
+                // At the top we have the status bar, so apps that are
+                // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR already expect that there's an inset
+                // there and we don't need to exclude the window from that area.
+                displayCutoutSafeExceptMaybeTop.top = Integer.MIN_VALUE;
+            }
+            pf.intersectUnchecked(displayCutoutSafeExceptMaybeTop);
+        }
+
+        // Content should never appear in the cutout.
+        cf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
+
         // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
         // Also, we don't allow windows in multi-window mode to extend out of the screen.
         if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ca8ffca..91cad46 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -135,6 +135,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.util.DisplayMetrics;
 import android.util.MutableBoolean;
 import android.util.Slog;
@@ -178,20 +179,20 @@
 
     /** The containers below are the only child containers the display can have. */
     // Contains all window containers that are related to apps (Activities)
-    private final TaskStackContainers mTaskStackContainers = new TaskStackContainers();
+    private final TaskStackContainers mTaskStackContainers = new TaskStackContainers(mService);
     // Contains all non-app window containers that should be displayed above the app containers
     // (e.g. Status bar)
     private final AboveAppWindowContainers mAboveAppWindowsContainers =
-            new AboveAppWindowContainers("mAboveAppWindowsContainers");
+            new AboveAppWindowContainers("mAboveAppWindowsContainers", mService);
     // Contains all non-app window containers that should be displayed below the app containers
     // (e.g. Wallpaper).
     private final NonAppWindowContainers mBelowAppWindowsContainers =
-            new NonAppWindowContainers("mBelowAppWindowsContainers");
+            new NonAppWindowContainers("mBelowAppWindowsContainers", mService);
     // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
     // on the IME target. We mainly have this container grouping so we can keep track of all the IME
     // window containers together and move them in-sync if/when needed.
     private final NonAppWindowContainers mImeWindowsContainers =
-            new NonAppWindowContainers("mImeWindowsContainers");
+            new NonAppWindowContainers("mImeWindowsContainers", mService);
 
     private WindowState mTmpWindow;
     private WindowState mTmpWindow2;
@@ -317,8 +318,6 @@
     /** Used for handing back size of display */
     private final Rect mTmpBounds = new Rect();
 
-    WindowManagerService mService;
-
     /** Remove this display when animation on it has completed. */
     private boolean mDeferredRemoval;
 
@@ -765,6 +764,7 @@
      */
     DisplayContent(Display display, WindowManagerService service,
             WallpaperController wallpaperController) {
+        super(service);
         if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
             throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
                     + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
@@ -777,7 +777,6 @@
         display.getDisplayInfo(mDisplayInfo);
         display.getMetrics(mDisplayMetrics);
         isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
-        mService = service;
         mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo);
         initializeDisplayBaseInfo();
         mDividerControllerLocked = new DockedStackDividerController(service, this);
@@ -2339,6 +2338,7 @@
 
     /** Updates the layer assignment of windows on this display. */
     void assignWindowLayers(boolean setLayoutNeeded) {
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "assignWindowLayers");
         assignChildLayers(getPendingTransaction());
         if (setLayoutNeeded) {
             setLayoutNeeded();
@@ -2349,6 +2349,7 @@
         // prepareSurfaces. This allows us to synchronize Z-ordering changes with
         // the hiding and showing of surfaces.
         scheduleAnimation();
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     // TODO: This should probably be called any time a visual change is made to the hierarchy like
@@ -3182,6 +3183,10 @@
      */
     static class DisplayChildWindowContainer<E extends WindowContainer> extends WindowContainer<E> {
 
+        DisplayChildWindowContainer(WindowManagerService service) {
+            super(service);
+        }
+
         @Override
         boolean fillsParent() {
             return true;
@@ -3209,6 +3214,10 @@
         private TaskStack mPinnedStack = null;
         private TaskStack mSplitScreenPrimaryStack = null;
 
+        TaskStackContainers(WindowManagerService service) {
+            super(service);
+        }
+
         /**
          * Returns the topmost stack on the display that is compatible with the input windowing mode
          * and activity type. Null is no compatible stack on the display.
@@ -3516,35 +3525,37 @@
 
         @Override
         void assignChildLayers(SurfaceControl.Transaction t) {
-            final int NORMAL_STACK_STATE = 0;
-            final int BOOSTED_STATE = 1;
-            final int ALWAYS_ON_TOP_STATE = 2;
+            int layer = 0;
 
             // We allow stacks to change visual order from the AM specified order due to
             // Z-boosting during animations. However we must take care to ensure TaskStacks
             // which are marked as alwaysOnTop remain that way.
-            int layer = 0;
-            for (int state = 0; state <= ALWAYS_ON_TOP_STATE; state++) {
-                for (int i = 0; i < mChildren.size(); i++) {
-                    final TaskStack s = mChildren.get(i);
-                    layer++;
-                    if (state == NORMAL_STACK_STATE) {
-                        s.assignLayer(t, layer);
-                    } else if (state == BOOSTED_STATE && s.needsZBoost()) {
-                        s.assignLayer(t, layer);
-                    } else if (state == ALWAYS_ON_TOP_STATE &&
-                            s.isAlwaysOnTop()) {
-                        s.assignLayer(t, layer);
-                    }
-                    s.assignChildLayers(t);
+            for (int i = 0; i < mChildren.size(); i++) {
+                final TaskStack s = mChildren.get(i);
+                s.assignChildLayers();
+                if (!s.needsZBoost() && !s.isAlwaysOnTop()) {
+                    s.assignLayer(t, layer++);
                 }
-                // The appropriate place for App-Transitions to occur is right
-                // above all other animations but still below things in the Picture-and-Picture
-                // windowing mode.
-                if (state == BOOSTED_STATE && mAnimationLayer != null) {
-                    t.setLayer(mAnimationLayer, layer + 1);
+            }
+            for (int i = 0; i < mChildren.size(); i++) {
+                final TaskStack s = mChildren.get(i);
+                if (s.needsZBoost() && !s.isAlwaysOnTop()) {
+                    s.assignLayer(t, layer++);
                 }
             }
+            for (int i = 0; i < mChildren.size(); i++) {
+                final TaskStack s = mChildren.get(i);
+                if (s.isAlwaysOnTop()) {
+                    s.assignLayer(t, layer++);
+                }
+            }
+
+            // The appropriate place for App-Transitions to occur is right
+            // above all other animations but still below things in the Picture-and-Picture
+            // windowing mode.
+            if (mAnimationLayer != null) {
+                t.setLayer(mAnimationLayer, layer++);
+            }
         }
 
         @Override
@@ -3560,8 +3571,8 @@
     }
 
     private final class AboveAppWindowContainers extends NonAppWindowContainers {
-        AboveAppWindowContainers(String name) {
-            super(name);
+        AboveAppWindowContainers(String name, WindowManagerService service) {
+            super(name, service);
         }
 
         void assignChildLayers(SurfaceControl.Transaction t, WindowContainer imeContainer) {
@@ -3577,14 +3588,12 @@
 
                 if (needAssignIme && layer >= mService.mPolicy.getWindowLayerFromTypeLw(
                                 TYPE_INPUT_METHOD_DIALOG, true)) {
-                    t.setRelativeLayer(imeContainer.getSurfaceControl(),
-                            wt.getSurfaceControl(), -1);
+                    imeContainer.assignRelativeLayer(t, wt.getSurfaceControl(), -1);
                     needAssignIme = false;
                 }
             }
             if (needAssignIme) {
-                t.setRelativeLayer(imeContainer.getSurfaceControl(),
-                        getSurfaceControl(), Integer.MAX_VALUE);
+                imeContainer.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE);
             }
         }
     }
@@ -3618,7 +3627,8 @@
         };
 
         private final String mName;
-        NonAppWindowContainers(String name) {
+        NonAppWindowContainers(String name, WindowManagerService service) {
+            super(service);
             mName = name;
         }
 
@@ -3712,8 +3722,6 @@
 
     @Override
     void assignChildLayers(SurfaceControl.Transaction t) {
-        t.setLayer(mOverlayLayer, 1)
-                .setLayer(mWindowingLayer, 0);
 
         // These are layers as children of "mWindowingLayer"
         mBelowAppWindowsContainers.assignLayer(t, 0);
@@ -3737,8 +3745,7 @@
         // place it in the AboveAppWindowContainers.
         if (imeTarget != null && !imeTarget.inSplitScreenWindowingMode()
                 && (imeTarget.getSurfaceControl() != null)) {
-            t.setRelativeLayer(mImeWindowsContainers.getSurfaceControl(),
-                    imeTarget.getSurfaceControl(),
+            mImeWindowsContainers.assignRelativeLayer(t, imeTarget.getSurfaceControl(),
                     // TODO: We need to use an extra level on the app surface to ensure
                     // this is always above SurfaceView but always below attached window.
                     1);
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 209ce3f..0155712 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -22,12 +22,16 @@
 import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS;
 
 import android.annotation.NonNull;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
+import java.util.Arrays;
 
 /**
  * Container class for all the display frames that affect how we do window layout on a display.
@@ -124,7 +128,7 @@
                 info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom);
     }
 
-    public void onBeginLayout() {
+    public void onBeginLayout(boolean emulateDisplayCutout, int statusBarHeight) {
         switch (mRotation) {
             case ROTATION_90:
                 mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top;
@@ -165,12 +169,64 @@
         mDisplayCutout = DisplayCutout.NO_CUTOUT;
         mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE,
                 Integer.MAX_VALUE, Integer.MAX_VALUE);
+        if (emulateDisplayCutout) {
+            setEmulatedDisplayCutout((int) (statusBarHeight * 0.8));
+        }
     }
 
     public int getInputMethodWindowVisibleHeight() {
         return mDock.bottom - mCurrent.bottom;
     }
 
+    private void setEmulatedDisplayCutout(int height) {
+        final boolean swappedDimensions = mRotation == ROTATION_90 || mRotation == ROTATION_270;
+
+        final int screenWidth = swappedDimensions ? mDisplayHeight : mDisplayWidth;
+        final int screenHeight = swappedDimensions ? mDisplayWidth : mDisplayHeight;
+
+        final int widthTop = (int) (screenWidth * 0.3);
+        final int widthBottom = widthTop - height;
+
+        switch (mRotation) {
+            case ROTATION_90:
+                mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
+                        new Point(0, (screenWidth - widthTop) / 2),
+                        new Point(height, (screenWidth - widthBottom) / 2),
+                        new Point(height, (screenWidth + widthBottom) / 2),
+                        new Point(0, (screenWidth + widthTop) / 2)
+                )).calculateRelativeTo(mUnrestricted);
+                mDisplayCutoutSafe.left = height;
+                break;
+            case ROTATION_180:
+                mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
+                        new Point((screenWidth - widthTop) / 2, screenHeight),
+                        new Point((screenWidth - widthBottom) / 2, screenHeight - height),
+                        new Point((screenWidth + widthBottom) / 2, screenHeight - height),
+                        new Point((screenWidth + widthTop) / 2, screenHeight)
+                )).calculateRelativeTo(mUnrestricted);
+                mDisplayCutoutSafe.bottom = screenHeight - height;
+                break;
+            case ROTATION_270:
+                mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
+                        new Point(screenHeight, (screenWidth - widthTop) / 2),
+                        new Point(screenHeight - height, (screenWidth - widthBottom) / 2),
+                        new Point(screenHeight - height, (screenWidth + widthBottom) / 2),
+                        new Point(screenHeight, (screenWidth + widthTop) / 2)
+                )).calculateRelativeTo(mUnrestricted);
+                mDisplayCutoutSafe.right = screenHeight - height;
+                break;
+            default:
+                mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
+                        new Point((screenWidth - widthTop) / 2, 0),
+                        new Point((screenWidth - widthBottom) / 2, height),
+                        new Point((screenWidth + widthBottom) / 2, height),
+                        new Point((screenWidth + widthTop) / 2, 0)
+                )).calculateRelativeTo(mUnrestricted);
+                mDisplayCutoutSafe.top = height;
+                break;
+        }
+    }
+
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         mStable.writeToProto(proto, STABLE_BOUNDS);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 4008811..b08eb18 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -101,8 +101,6 @@
     private static final int SET_SCREEN_BRIGHTNESS_OVERRIDE = 1;
     private static final int SET_USER_ACTIVITY_TIMEOUT = 2;
 
-    WindowManagerService mService;
-
     private boolean mWallpaperForceHidingChanged = false;
     private Object mLastWindowFreezeSource = null;
     private Session mHoldScreen = null;
@@ -160,7 +158,7 @@
     };
 
     RootWindowContainer(WindowManagerService service) {
-        mService = service;
+        super(service);
         mHandler = new MyHandler(service.mH.getLooper());
         mWallpaperController = new WallpaperController(mService);
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8aa129a..6ea8a47 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -59,7 +59,6 @@
     final int mTaskId;
     final int mUserId;
     private boolean mDeferRemoval = false;
-    final WindowManagerService mService;
 
     final Rect mPreparedFrozenBounds = new Rect();
     final Configuration mPreparedFrozenMergedConfig = new Configuration();
@@ -102,10 +101,10 @@
     Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode,
             boolean supportsPictureInPicture, TaskDescription taskDescription,
             TaskWindowContainerController controller) {
+        super(service);
         mTaskId = taskId;
         mStack = stack;
         mUserId = userId;
-        mService = service;
         mResizeMode = resizeMode;
         mSupportsPictureInPicture = supportsPictureInPicture;
         setController(controller);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 94fbd0e..259f8df 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -197,7 +197,7 @@
         }
         try {
             final int res = session.addToDisplay(window, window.mSeq, layoutParams,
-                    View.VISIBLE, token.getDisplayContent().getDisplayId(), tmpRect, tmpRect,
+                    View.GONE, token.getDisplayContent().getDisplayId(), tmpRect, tmpRect,
                     tmpRect, tmpCutout, null);
             if (res < 0) {
                 Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 4a3a3fc..832d395 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -75,9 +75,6 @@
     /** Unique identifier */
     final int mStackId;
 
-    /** The service */
-    private final WindowManagerService mService;
-
     /** The display this stack sits under. */
     // TODO: Track parent marks like this in WindowContainer.
     private DisplayContent mDisplayContent;
@@ -151,7 +148,7 @@
     final Rect mTmpDimBoundsRect = new Rect();
 
     TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
-        mService = service;
+        super(service);
         mStackId = stackId;
         setController(controller);
         mDockedStackMinimizeThickness = service.mContext.getResources().getDimensionPixelSize(
diff --git a/services/core/java/com/android/server/wm/TransactionFactory.java b/services/core/java/com/android/server/wm/TransactionFactory.java
new file mode 100644
index 0000000..067f083
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TransactionFactory.java
@@ -0,0 +1,27 @@
+/*
+ * 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 android.view.SurfaceControl.Transaction;
+
+/**
+ * Helper class to inject custom transaction objects into window manager.
+ */
+interface TransactionFactory {
+    Transaction make();
+};
+
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6467582..d6329bf 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -21,6 +21,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static com.android.server.wm.proto.WindowContainerProto.CONFIGURATION_CONTAINER;
 import static com.android.server.wm.proto.WindowContainerProto.ORIENTATION;
+import static com.android.server.wm.proto.WindowContainerProto.VISIBLE;
 import static android.view.SurfaceControl.Transaction;
 
 import android.annotation.CallSuper;
@@ -71,18 +72,25 @@
      WindowContainerController mController;
 
     protected SurfaceControl mSurfaceControl;
+    private int mLastLayer = 0;
+    private SurfaceControl mLastRelativeToLayer = null;
 
     /**
      * Applied as part of the animation pass in "prepareSurfaces".
      */
-    private Transaction mPendingTransaction = new Transaction();
+    private final Transaction mPendingTransaction;
+    protected final WindowManagerService mService;
+
+    WindowContainer(WindowManagerService service) {
+        mService = service;
+        mPendingTransaction = service.mTransactionFactory.make();
+    }
 
     @Override
     final protected WindowContainer getParent() {
         return mParent;
     }
 
-
     @Override
     protected int getChildCount() {
         return mChildren.size();
@@ -755,34 +763,46 @@
     }
 
     void assignLayer(Transaction t, int layer) {
-        if (mSurfaceControl != null) {
+        final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null;
+        if (mSurfaceControl != null && changed) {
             t.setLayer(mSurfaceControl, layer);
+            mLastLayer = layer;
+            mLastRelativeToLayer = null;
+        }
+    }
+
+    void assignRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
+        final boolean changed = layer != mLastLayer || mLastRelativeToLayer != relativeTo;
+        if (mSurfaceControl != null && changed) {
+            t.setRelativeLayer(mSurfaceControl, relativeTo, layer);
+            mLastLayer = layer;
+            mLastRelativeToLayer = relativeTo;
         }
     }
 
     void assignChildLayers(Transaction t) {
         int layer = 0;
-        boolean boosting = false;
 
         // We use two passes as a way to promote children which
         // need Z-boosting to the end of the list.
-        for (int i = 0; i < 2; i++ ) {
-            for (int j = 0; j < mChildren.size(); ++j) {
-                final WindowContainer wc = mChildren.get(j);
-                if (wc.needsZBoost() && !boosting) {
-                    continue;
-                }
-                wc.assignLayer(t, layer);
-                wc.assignChildLayers(t);
-
-                layer++;
+        for (int j = 0; j < mChildren.size(); ++j) {
+            final WindowContainer wc = mChildren.get(j);
+            wc.assignChildLayers(t);
+            if (!wc.needsZBoost()) {
+                wc.assignLayer(t, layer++);
             }
-            boosting = true;
+        }
+        for (int j = 0; j < mChildren.size(); ++j) {
+            final WindowContainer wc = mChildren.get(j);
+            if (wc.needsZBoost()) {
+                wc.assignLayer(t, layer++);
+            }
         }
     }
 
     void assignChildLayers() {
         assignChildLayers(getPendingTransaction());
+        scheduleAnimation();
     }
 
     boolean needsZBoost() {
@@ -809,6 +829,7 @@
         final long token = proto.start(fieldId);
         super.writeToProto(proto, CONFIGURATION_CONTAINER, trim);
         proto.write(ORIENTATION, mOrientation);
+        proto.write(VISIBLE, isVisible());
         proto.end(token);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3ad4df7..e598224 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -215,6 +215,7 @@
 import android.view.PointerIcon;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.SurfaceControl.Builder;
 import android.view.SurfaceSession;
 import android.view.View;
 import android.view.WindowContentFrameStats;
@@ -806,12 +807,8 @@
     static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
             new WindowManagerThreadPriorityBooster();
 
-    class DefaultSurfaceBuilderFactory implements SurfaceBuilderFactory {
-        public SurfaceControl.Builder make(SurfaceSession s) {
-            return new SurfaceControl.Builder(s);
-        }
-    };
-    SurfaceBuilderFactory mSurfaceBuilderFactory = new DefaultSurfaceBuilderFactory();
+    SurfaceBuilderFactory mSurfaceBuilderFactory = SurfaceControl.Builder::new;
+    TransactionFactory mTransactionFactory = SurfaceControl.Transaction::new;
 
     static void boostPriorityForLockedSection() {
         sThreadPriorityBooster.boost();
@@ -1502,7 +1499,7 @@
 
             // Don't do layout here, the window must call
             // relayout to be displayed, so we'll do it there.
-            displayContent.assignWindowLayers(false /* setLayoutNeeded */);
+            win.getParent().assignChildLayers();
 
             if (focusChanged) {
                 mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
@@ -1972,6 +1969,13 @@
                         + " newVis=" + viewVisibility, stack);
             }
 
+            win.setDisplayLayoutNeeded();
+            win.mGivenInsetsPending = (flags & WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
+
+            // We may be deferring layout passes at the moment, but since the client is interested
+            // in the new out values right now we need to force a layout.
+            mWindowPlacerLocked.performSurfacePlacement(true /* force */);
+
             // We should only relayout if the view is visible, it is a starting window, or the
             // associated appToken is not hidden.
             final boolean shouldRelayout = viewVisibility == View.VISIBLE &&
@@ -1981,15 +1985,6 @@
             if (shouldRelayout) {
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
 
-                // We are about to create a surface, but we didn't run a layout yet. So better run
-                // a layout now that we already know the right size, as a resize call will make the
-                // surface transaction blocking until next vsync and slow us down.
-                // TODO: Ideally we'd create the surface after running layout a bit further down,
-                // but moving this seems to be too risky at this point in the release.
-                if (win.mLayoutSeq == -1) {
-                    win.setDisplayLayoutNeeded();
-                    mWindowPlacerLocked.performSurfacePlacement(true);
-                }
                 result = win.relayoutVisibleWindow(result, attrChanges, oldVisibility);
 
                 try {
@@ -2091,16 +2086,11 @@
                 mUnknownAppVisibilityController.notifyRelayouted(win.mAppToken);
             }
 
-            win.setDisplayLayoutNeeded();
-            win.mGivenInsetsPending = (flags & WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                     "relayoutWindow: updateOrientationFromAppTokens");
             configChanged = updateOrientationFromAppTokensLocked(false, displayId);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
-            // We may be deferring layout passes at the moment, but since the client is interested
-            // in the new out values right now we need to force a layout.
-            mWindowPlacerLocked.performSurfacePlacement(true /* force */);
             if (toBeDisplayed && win.mIsWallpaper) {
                 DisplayInfo displayInfo = win.getDisplayContent().getDisplayInfo();
                 dc.mWallpaperController.updateWallpaperOffset(
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d23a6c7..e38605d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -186,7 +186,6 @@
     // to capture touch events in that area.
     static final int RESIZE_HANDLE_WIDTH_IN_DP = 30;
 
-    final WindowManagerService mService;
     final WindowManagerPolicy mPolicy;
     final Context mContext;
     final Session mSession;
@@ -627,7 +626,7 @@
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
-        mService = service;
+        super(service);
         mSession = s;
         mClient = c;
         mAppOp = appOp;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index a3d4b71..5bcf59c 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -46,9 +46,6 @@
 class WindowToken extends WindowContainer<WindowState> {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowToken" : TAG_WM;
 
-    // The window manager!
-    protected final WindowManagerService mService;
-
     // The actual token.
     final IBinder token;
 
@@ -107,7 +104,7 @@
 
     WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
             DisplayContent dc, boolean ownerCanManageAppTokens) {
-        mService = service;
+        super(service);
         token = _token;
         windowType = type;
         mPersistOnEmpty = persistOnEmpty;
diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/services/net/java/android/net/ip/ConnectivityPacketTracker.java
index 1925c39..6cf4fa9a 100644
--- a/services/net/java/android/net/ip/ConnectivityPacketTracker.java
+++ b/services/net/java/android/net/ip/ConnectivityPacketTracker.java
@@ -19,7 +19,7 @@
 import static android.system.OsConstants.*;
 
 import android.net.NetworkUtils;
-import android.net.util.BlockingSocketReader;
+import android.net.util.PacketReader;
 import android.net.util.ConnectivityPacketSummary;
 import android.os.Handler;
 import android.system.ErrnoException;
@@ -65,7 +65,7 @@
 
     private final String mTag;
     private final LocalLog mLog;
-    private final BlockingSocketReader mPacketListener;
+    private final PacketReader mPacketListener;
     private boolean mRunning;
     private String mDisplayName;
 
@@ -101,7 +101,7 @@
         mDisplayName = null;
     }
 
-    private final class PacketListener extends BlockingSocketReader {
+    private final class PacketListener extends PacketReader {
         private final int mIfIndex;
         private final byte mHwAddr[];
 
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 70983c8..b305b33 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -163,10 +163,10 @@
     // TODO: Find an lighter weight approach.
     private class LoggingCallbackWrapper extends Callback {
         private static final String PREFIX = "INVOKE ";
-        private Callback mCallback;
+        private final Callback mCallback;
 
         public LoggingCallbackWrapper(Callback callback) {
-            mCallback = callback;
+            mCallback = (callback != null) ? callback : new Callback();
         }
 
         private void log(String msg) {
@@ -815,6 +815,15 @@
         pw.println(Objects.toString(provisioningConfig, "N/A"));
         pw.decreaseIndent();
 
+        final IpReachabilityMonitor iprm = mIpReachabilityMonitor;
+        if (iprm != null) {
+            pw.println();
+            pw.println(mTag + " current IpReachabilityMonitor state:");
+            pw.increaseIndent();
+            iprm.dump(pw);
+            pw.decreaseIndent();
+        }
+
         pw.println();
         pw.println(mTag + " StateMachine dump:");
         pw.increaseIndent();
@@ -1237,6 +1246,7 @@
             mIpReachabilityMonitor = new IpReachabilityMonitor(
                     mContext,
                     mInterfaceName,
+                    getHandler(),
                     mLog,
                     new IpReachabilityMonitor.Callback() {
                         @Override
@@ -1273,6 +1283,7 @@
             stopAllIP();
 
             resetLinkProperties();
+            mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties));
             if (mStartTimeMillis > 0) {
                 recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
                 mStartTimeMillis = 0;
diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/services/net/java/android/net/ip/IpNeighborMonitor.java
new file mode 100644
index 0000000..6807334
--- /dev/null
+++ b/services/net/java/android/net/ip/IpNeighborMonitor.java
@@ -0,0 +1,236 @@
+/*
+ * 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 android.net.ip;
+
+import android.net.netlink.NetlinkConstants;
+import android.net.netlink.NetlinkErrorMessage;
+import android.net.netlink.NetlinkMessage;
+import android.net.netlink.NetlinkSocket;
+import android.net.netlink.RtNetlinkNeighborMessage;
+import android.net.netlink.StructNdMsg;
+import android.net.netlink.StructNlMsgHdr;
+import android.net.util.PacketReader;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.NetlinkSocketAddress;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.internal.util.BitUtils;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.StringJoiner;
+
+
+/**
+ * IpNeighborMonitor.
+ *
+ * Monitors the kernel rtnetlink neighbor notifications and presents to callers
+ * NeighborEvents describing each event. Callers can provide a consumer instance
+ * to both filter (e.g. by interface index and IP address) and handle the
+ * generated NeighborEvents.
+ *
+ * @hide
+ */
+public class IpNeighborMonitor extends PacketReader {
+    private static final String TAG = IpNeighborMonitor.class.getSimpleName();
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+
+    /**
+     * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
+     * for the given IP address on the specified interface index.
+     *
+     * @return 0 if the request was successfully passed to the kernel; otherwise return
+     *         a non-zero error code.
+     */
+    public static int startKernelNeighborProbe(int ifIndex, InetAddress ip) {
+        final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
+        if (DBG) { Log.d(TAG, msgSnippet); }
+
+        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
+                1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
+
+        try {
+            NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Error " + msgSnippet + ": " + e);
+            return -e.errno;
+        }
+
+        return 0;
+    }
+
+    public static class NeighborEvent {
+        final long elapsedMs;
+        final short msgType;
+        final int ifindex;
+        final InetAddress ip;
+        final short nudState;
+        final byte[] linkLayerAddr;
+
+        public NeighborEvent(long elapsedMs, short msgType, int ifindex, InetAddress ip,
+                short nudState, byte[] linkLayerAddr) {
+            this.elapsedMs = elapsedMs;
+            this.msgType = msgType;
+            this.ifindex = ifindex;
+            this.ip = ip;
+            this.nudState = nudState;
+            this.linkLayerAddr = linkLayerAddr;
+        }
+
+        boolean isConnected() {
+            return (msgType != NetlinkConstants.RTM_DELNEIGH) &&
+                   StructNdMsg.isNudStateConnected(nudState);
+        }
+
+        boolean isValid() {
+            return (msgType != NetlinkConstants.RTM_DELNEIGH) &&
+                   StructNdMsg.isNudStateValid(nudState);
+        }
+
+        @Override
+        public String toString() {
+            final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
+            return j.add("@" + elapsedMs)
+                    .add(NetlinkConstants.stringForNlMsgType(msgType))
+                    .add("if=" + ifindex)
+                    .add(ip.getHostAddress())
+                    .add(StructNdMsg.stringForNudState(nudState))
+                    .add("[" + NetlinkConstants.hexify(linkLayerAddr) + "]")
+                    .toString();
+        }
+    }
+
+    public interface NeighborEventConsumer {
+        // Every neighbor event received on the netlink socket is passed in
+        // here. Subclasses should filter for events of interest.
+        public void accept(NeighborEvent event);
+    }
+
+    private final SharedLog mLog;
+    private final NeighborEventConsumer mConsumer;
+
+    public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) {
+        super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
+        mLog = log.forSubComponent(TAG);
+        mConsumer = (cb != null) ? cb : (event) -> { /* discard */ };
+    }
+
+    @Override
+    protected FileDescriptor createFd() {
+        FileDescriptor fd = null;
+
+        try {
+            fd = NetlinkSocket.forProto(OsConstants.NETLINK_ROUTE);
+            Os.bind(fd, (SocketAddress)(new NetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH)));
+            Os.connect(fd, (SocketAddress)(new NetlinkSocketAddress(0, 0)));
+
+            if (VDBG) {
+                final NetlinkSocketAddress nlAddr = (NetlinkSocketAddress) Os.getsockname(fd);
+                Log.d(TAG, "bound to sockaddr_nl{"
+                        + BitUtils.uint32(nlAddr.getPortId()) + ", "
+                        + nlAddr.getGroupsMask()
+                        + "}");
+            }
+        } catch (ErrnoException|SocketException e) {
+            logError("Failed to create rtnetlink socket", e);
+            IoUtils.closeQuietly(fd);
+            return null;
+        }
+
+        return fd;
+    }
+
+    @Override
+    protected void handlePacket(byte[] recvbuf, int length) {
+        final long whenMs = SystemClock.elapsedRealtime();
+
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        parseNetlinkMessageBuffer(byteBuffer, whenMs);
+    }
+
+    private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
+        while (byteBuffer.remaining() > 0) {
+            final int position = byteBuffer.position();
+            final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
+            if (nlMsg == null || nlMsg.getHeader() == null) {
+                byteBuffer.position(position);
+                mLog.e("unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer));
+                break;
+            }
+
+            final int srcPortId = nlMsg.getHeader().nlmsg_pid;
+            if (srcPortId !=  0) {
+                mLog.e("non-kernel source portId: " + BitUtils.uint32(srcPortId));
+                break;
+            }
+
+            if (nlMsg instanceof NetlinkErrorMessage) {
+                mLog.e("netlink error: " + nlMsg);
+                continue;
+            } else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
+                mLog.i("non-rtnetlink neighbor msg: " + nlMsg);
+                continue;
+            }
+
+            evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
+        }
+    }
+
+    private void evaluateRtNetlinkNeighborMessage(
+            RtNetlinkNeighborMessage neighMsg, long whenMs) {
+        final short msgType = neighMsg.getHeader().nlmsg_type;
+        final StructNdMsg ndMsg = neighMsg.getNdHeader();
+        if (ndMsg == null) {
+            mLog.e("RtNetlinkNeighborMessage without ND message header!");
+            return;
+        }
+
+        final int ifindex = ndMsg.ndm_ifindex;
+        final InetAddress destination = neighMsg.getDestination();
+        final short nudState =
+                (msgType == NetlinkConstants.RTM_DELNEIGH)
+                ? StructNdMsg.NUD_NONE
+                : ndMsg.ndm_state;
+
+        final NeighborEvent event = new NeighborEvent(
+                whenMs, msgType, ifindex, destination, nudState, neighMsg.getLinkLayerAddress());
+
+        if (VDBG) {
+            Log.d(TAG, neighMsg.toString());
+        }
+        if (DBG) {
+            Log.d(TAG, event.toString());
+        }
+
+        mConsumer.accept(event);
+    }
+}
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 714b35a..b31ffbb 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -22,30 +22,27 @@
 import android.net.LinkProperties.ProvisioningChange;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
+import android.net.ip.IpNeighborMonitor.NeighborEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpReachabilityEvent;
-import android.net.netlink.NetlinkConstants;
-import android.net.netlink.NetlinkErrorMessage;
-import android.net.netlink.NetlinkMessage;
-import android.net.netlink.NetlinkSocket;
-import android.net.netlink.RtNetlinkNeighborMessage;
 import android.net.netlink.StructNdMsg;
-import android.net.netlink.StructNdaCacheInfo;
-import android.net.netlink.StructNlMsgHdr;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.SharedLog;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
 import android.system.ErrnoException;
-import android.system.NetlinkSocketAddress;
 import android.system.OsConstants;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.DumpUtils.Dump;
 
 import java.io.InterruptedIOException;
+import java.io.PrintWriter;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -134,6 +131,8 @@
  *          state it may be best for the link to disconnect completely and
  *          reconnect afresh.
  *
+ * Accessing an instance of this class from multiple threads is NOT safe.
+ *
  * @hide
  */
 public class IpReachabilityMonitor {
@@ -169,64 +168,33 @@
         }
     }
 
-    private final Object mLock = new Object();
     private final String mInterfaceName;
     private final int mInterfaceIndex;
+    private final IpNeighborMonitor mIpNeighborMonitor;
     private final SharedLog mLog;
     private final Callback mCallback;
     private final Dependencies mDependencies;
     private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
-    private final NetlinkSocketObserver mNetlinkSocketObserver;
-    private final Thread mObserverThread;
     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-    @GuardedBy("mLock")
     private LinkProperties mLinkProperties = new LinkProperties();
-    // TODO: consider a map to a private NeighborState class holding more
-    // information than a single NUD state entry.
-    @GuardedBy("mLock")
-    private Map<InetAddress, Short> mIpWatchList = new HashMap<>();
-    @GuardedBy("mLock")
-    private int mIpWatchListVersion;
-    private volatile boolean mRunning;
+    private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
     // Time in milliseconds of the last forced probe request.
     private volatile long mLastProbeTimeMs;
 
-    /**
-     * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
-     * for the given IP address on the specified interface index.
-     *
-     * @return 0 if the request was successfully passed to the kernel; otherwise return
-     *         a non-zero error code.
-     */
-    private static int probeNeighbor(int ifIndex, InetAddress ip) {
-        final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
-        if (DBG) { Log.d(TAG, msgSnippet); }
-
-        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
-                1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
-
-        try {
-            NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
-        } catch (ErrnoException e) {
-            Log.e(TAG, "Error " + msgSnippet + ": " + e);
-            return -e.errno;
-        }
-
-        return 0;
+    public IpReachabilityMonitor(
+            Context context, String ifName, Handler h, SharedLog log, Callback callback) {
+        this(context, ifName, h, log, callback, null);
     }
 
-    public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback) {
-        this(context, ifName, log, callback, null);
-    }
-
-    public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback,
+    public IpReachabilityMonitor(
+            Context context, String ifName, Handler h, SharedLog log, Callback callback,
             MultinetworkPolicyTracker tracker) {
-        this(ifName, getInterfaceIndex(ifName), log, callback, tracker,
+        this(ifName, getInterfaceIndex(ifName), h, log, callback, tracker,
                 Dependencies.makeDefault(context, ifName));
     }
 
     @VisibleForTesting
-    IpReachabilityMonitor(String ifName, int ifIndex, SharedLog log, Callback callback,
+    IpReachabilityMonitor(String ifName, int ifIndex, Handler h, SharedLog log, Callback callback,
             MultinetworkPolicyTracker tracker, Dependencies dependencies) {
         mInterfaceName = ifName;
         mLog = log.forSubComponent(TAG);
@@ -234,47 +202,56 @@
         mMultinetworkPolicyTracker = tracker;
         mInterfaceIndex = ifIndex;
         mDependencies = dependencies;
-        mNetlinkSocketObserver = new NetlinkSocketObserver();
-        mObserverThread = new Thread(mNetlinkSocketObserver);
-        mObserverThread.start();
+
+        mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
+                (NeighborEvent event) -> {
+                    if (mInterfaceIndex != event.ifindex) return;
+                    if (!mNeighborWatchList.containsKey(event.ip)) return;
+
+                    final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);
+
+                    // TODO: Consider what to do with other states that are not within
+                    // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
+                    if (event.nudState == StructNdMsg.NUD_FAILED) {
+                        mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
+                        handleNeighborLost(event);
+                    }
+                });
+        mIpNeighborMonitor.start();
     }
 
     public void stop() {
-        mRunning = false;
+        mIpNeighborMonitor.stop();
         clearLinkProperties();
-        mNetlinkSocketObserver.clearNetlinkSocket();
     }
 
-    // TODO: add a public dump() method that can be called during a bug report.
+    public void dump(PrintWriter pw) {
+        DumpUtils.dumpAsync(
+                mIpNeighborMonitor.getHandler(),
+                new Dump() {
+                    @Override
+                    public void dump(PrintWriter pw, String prefix) {
+                        pw.println(describeWatchList("\n"));
+                    }
+                },
+                pw, "", 1000);
+    }
 
-    private String describeWatchList() {
-        final String delimiter = ", ";
-        StringBuilder sb = new StringBuilder();
-        synchronized (mLock) {
-            sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}, ");
-            sb.append("v{" + mIpWatchListVersion + "}, ");
-            sb.append("ntable=[");
-            boolean firstTime = true;
-            for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
-                if (firstTime) {
-                    firstTime = false;
-                } else {
-                    sb.append(delimiter);
-                }
-                sb.append(entry.getKey().getHostAddress() + "/" +
-                        StructNdMsg.stringForNudState(entry.getValue()));
-            }
-            sb.append("]");
+    private String describeWatchList() { return describeWatchList(" "); }
+
+    private String describeWatchList(String sep) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}," + sep);
+        sb.append("ntable=[" + sep);
+        String delimiter = "";
+        for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
+            sb.append(delimiter).append(entry.getKey().getHostAddress() + "/" + entry.getValue());
+            delimiter = "," + sep;
         }
+        sb.append("]");
         return sb.toString();
     }
 
-    private boolean isWatching(InetAddress ip) {
-        synchronized (mLock) {
-            return mRunning && mIpWatchList.containsKey(ip);
-        }
-    }
-
     private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
         for (RouteInfo route : routes) {
             if (!route.hasGateway() && route.matches(ip)) {
@@ -284,13 +261,6 @@
         return false;
     }
 
-    private short getNeighborStateLocked(InetAddress ip) {
-        if (mIpWatchList.containsKey(ip)) {
-            return mIpWatchList.get(ip);
-        }
-        return StructNdMsg.NUD_NONE;
-    }
-
     public void updateLinkProperties(LinkProperties lp) {
         if (!mInterfaceName.equals(lp.getInterfaceName())) {
             // TODO: figure out whether / how to cope with interface changes.
@@ -299,70 +269,63 @@
             return;
         }
 
-        synchronized (mLock) {
-            mLinkProperties = new LinkProperties(lp);
-            Map<InetAddress, Short> newIpWatchList = new HashMap<>();
+        mLinkProperties = new LinkProperties(lp);
+        Map<InetAddress, NeighborEvent> newNeighborWatchList = new HashMap<>();
 
-            final List<RouteInfo> routes = mLinkProperties.getRoutes();
-            for (RouteInfo route : routes) {
-                if (route.hasGateway()) {
-                    InetAddress gw = route.getGateway();
-                    if (isOnLink(routes, gw)) {
-                        newIpWatchList.put(gw, getNeighborStateLocked(gw));
-                    }
+        final List<RouteInfo> routes = mLinkProperties.getRoutes();
+        for (RouteInfo route : routes) {
+            if (route.hasGateway()) {
+                InetAddress gw = route.getGateway();
+                if (isOnLink(routes, gw)) {
+                    newNeighborWatchList.put(gw, mNeighborWatchList.getOrDefault(gw, null));
                 }
             }
-
-            for (InetAddress nameserver : lp.getDnsServers()) {
-                if (isOnLink(routes, nameserver)) {
-                    newIpWatchList.put(nameserver, getNeighborStateLocked(nameserver));
-                }
-            }
-
-            mIpWatchList = newIpWatchList;
-            mIpWatchListVersion++;
         }
+
+        for (InetAddress dns : lp.getDnsServers()) {
+            if (isOnLink(routes, dns)) {
+                newNeighborWatchList.put(dns, mNeighborWatchList.getOrDefault(dns, null));
+            }
+        }
+
+        mNeighborWatchList = newNeighborWatchList;
         if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
     }
 
     public void clearLinkProperties() {
-        synchronized (mLock) {
-            mLinkProperties.clear();
-            mIpWatchList.clear();
-            mIpWatchListVersion++;
-        }
+        mLinkProperties.clear();
+        mNeighborWatchList.clear();
         if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
     }
 
-    private void handleNeighborLost(String msg) {
+    private void handleNeighborLost(NeighborEvent event) {
+        final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
+
         InetAddress ip = null;
-        final ProvisioningChange delta;
-        synchronized (mLock) {
-            LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
+        for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
+            // TODO: Consider using NeighborEvent#isValid() here; it's more
+            // strict but may interact badly if other entries are somehow in
+            // NUD_INCOMPLETE (say, during network attach).
+            if (entry.getValue().nudState != StructNdMsg.NUD_FAILED) continue;
 
-            for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
-                if (entry.getValue() != StructNdMsg.NUD_FAILED) {
-                    continue;
-                }
-
-                ip = entry.getKey();
-                for (RouteInfo route : mLinkProperties.getRoutes()) {
-                    if (ip.equals(route.getGateway())) {
-                        whatIfLp.removeRoute(route);
-                    }
-                }
-
-                if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
-                    // We should do this unconditionally, but alas we cannot: b/31827713.
-                    whatIfLp.removeDnsServer(ip);
+            ip = entry.getKey();
+            for (RouteInfo route : mLinkProperties.getRoutes()) {
+                if (ip.equals(route.getGateway())) {
+                    whatIfLp.removeRoute(route);
                 }
             }
 
-            delta = LinkProperties.compareProvisioning(mLinkProperties, whatIfLp);
+            if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
+                // We should do this unconditionally, but alas we cannot: b/31827713.
+                whatIfLp.removeDnsServer(ip);
+            }
         }
 
+        final ProvisioningChange delta = LinkProperties.compareProvisioning(
+                mLinkProperties, whatIfLp);
+
         if (delta == ProvisioningChange.LOST_PROVISIONING) {
-            final String logMsg = "FAILURE: LOST_PROVISIONING, " + msg;
+            final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
             Log.w(TAG, logMsg);
             if (mCallback != null) {
                 // TODO: remove |ip| when the callback signature no longer has
@@ -378,12 +341,9 @@
     }
 
     public void probeAll() {
-        final List<InetAddress> ipProbeList;
-        synchronized (mLock) {
-            ipProbeList = new ArrayList<>(mIpWatchList.keySet());
-        }
+        final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
 
-        if (!ipProbeList.isEmpty() && mRunning) {
+        if (!ipProbeList.isEmpty()) {
             // Keep the CPU awake long enough to allow all ARP/ND
             // probes a reasonable chance at success. See b/23197666.
             //
@@ -394,13 +354,10 @@
         }
 
         for (InetAddress target : ipProbeList) {
-            if (!mRunning) {
-                break;
-            }
-            final int returnValue = probeNeighbor(mInterfaceIndex, target);
+            final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceIndex, target);
             mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
-                     target.getHostAddress(), returnValue));
-            logEvent(IpReachabilityEvent.PROBE, returnValue);
+                     target.getHostAddress(), rval));
+            logEvent(IpReachabilityEvent.PROBE, rval);
         }
         mLastProbeTimeMs = SystemClock.elapsedRealtime();
     }
@@ -446,153 +403,4 @@
         int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
         mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
     }
-
-    // TODO: simplify the number of objects by making this extend Thread.
-    private final class NetlinkSocketObserver implements Runnable {
-        private NetlinkSocket mSocket;
-
-        @Override
-        public void run() {
-            if (VDBG) { Log.d(TAG, "Starting observing thread."); }
-            mRunning = true;
-
-            try {
-                setupNetlinkSocket();
-            } catch (ErrnoException | SocketException e) {
-                Log.e(TAG, "Failed to suitably initialize a netlink socket", e);
-                mRunning = false;
-            }
-
-            while (mRunning) {
-                final ByteBuffer byteBuffer;
-                try {
-                    byteBuffer = recvKernelReply();
-                } catch (ErrnoException e) {
-                    if (mRunning) { Log.w(TAG, "ErrnoException: ", e); }
-                    break;
-                }
-                final long whenMs = SystemClock.elapsedRealtime();
-                if (byteBuffer == null) {
-                    continue;
-                }
-                parseNetlinkMessageBuffer(byteBuffer, whenMs);
-            }
-
-            clearNetlinkSocket();
-
-            mRunning = false; // Not a no-op when ErrnoException happened.
-            if (VDBG) { Log.d(TAG, "Finishing observing thread."); }
-        }
-
-        private void clearNetlinkSocket() {
-            if (mSocket != null) {
-                mSocket.close();
-            }
-        }
-
-            // TODO: Refactor the main loop to recreate the socket upon recoverable errors.
-        private void setupNetlinkSocket() throws ErrnoException, SocketException {
-            clearNetlinkSocket();
-            mSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
-
-            final NetlinkSocketAddress listenAddr = new NetlinkSocketAddress(
-                    0, OsConstants.RTMGRP_NEIGH);
-            mSocket.bind(listenAddr);
-
-            if (VDBG) {
-                final NetlinkSocketAddress nlAddr = mSocket.getLocalAddress();
-                Log.d(TAG, "bound to sockaddr_nl{"
-                        + ((long) (nlAddr.getPortId() & 0xffffffff)) + ", "
-                        + nlAddr.getGroupsMask()
-                        + "}");
-            }
-        }
-
-        private ByteBuffer recvKernelReply() throws ErrnoException {
-            try {
-                return mSocket.recvMessage(0);
-            } catch (InterruptedIOException e) {
-                // Interruption or other error, e.g. another thread closed our file descriptor.
-            } catch (ErrnoException e) {
-                if (e.errno != OsConstants.EAGAIN) {
-                    throw e;
-                }
-            }
-            return null;
-        }
-
-        private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
-            while (byteBuffer.remaining() > 0) {
-                final int position = byteBuffer.position();
-                final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
-                if (nlMsg == null || nlMsg.getHeader() == null) {
-                    byteBuffer.position(position);
-                    Log.e(TAG, "unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer));
-                    break;
-                }
-
-                final int srcPortId = nlMsg.getHeader().nlmsg_pid;
-                if (srcPortId !=  0) {
-                    Log.e(TAG, "non-kernel source portId: " + ((long) (srcPortId & 0xffffffff)));
-                    break;
-                }
-
-                if (nlMsg instanceof NetlinkErrorMessage) {
-                    Log.e(TAG, "netlink error: " + nlMsg);
-                    continue;
-                } else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
-                    if (DBG) {
-                        Log.d(TAG, "non-rtnetlink neighbor msg: " + nlMsg);
-                    }
-                    continue;
-                }
-
-                evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
-            }
-        }
-
-        private void evaluateRtNetlinkNeighborMessage(
-                RtNetlinkNeighborMessage neighMsg, long whenMs) {
-            final StructNdMsg ndMsg = neighMsg.getNdHeader();
-            if (ndMsg == null || ndMsg.ndm_ifindex != mInterfaceIndex) {
-                return;
-            }
-
-            final InetAddress destination = neighMsg.getDestination();
-            if (!isWatching(destination)) {
-                return;
-            }
-
-            final short msgType = neighMsg.getHeader().nlmsg_type;
-            final short nudState = ndMsg.ndm_state;
-            final String eventMsg = "NeighborEvent{"
-                    + "elapsedMs=" + whenMs + ", "
-                    + destination.getHostAddress() + ", "
-                    + "[" + NetlinkConstants.hexify(neighMsg.getLinkLayerAddress()) + "], "
-                    + NetlinkConstants.stringForNlMsgType(msgType) + ", "
-                    + StructNdMsg.stringForNudState(nudState)
-                    + "}";
-
-            if (VDBG) {
-                Log.d(TAG, neighMsg.toString());
-            } else if (DBG) {
-                Log.d(TAG, eventMsg);
-            }
-
-            synchronized (mLock) {
-                if (mIpWatchList.containsKey(destination)) {
-                    final short value =
-                            (msgType == NetlinkConstants.RTM_DELNEIGH)
-                            ? StructNdMsg.NUD_NONE
-                            : nudState;
-                    mIpWatchList.put(destination, value);
-                }
-            }
-
-            if (nudState == StructNdMsg.NUD_FAILED) {
-                Log.w(TAG, "ALERT: " + eventMsg);
-                handleNeighborLost(eventMsg);
-            }
-        }
-    }
 }
diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java
index f5f211d..5af3c29 100644
--- a/services/net/java/android/net/netlink/NetlinkSocket.java
+++ b/services/net/java/android/net/netlink/NetlinkSocket.java
@@ -16,16 +16,24 @@
 
 package android.net.netlink;
 
+import static android.system.OsConstants.AF_NETLINK;
+import static android.system.OsConstants.EIO;
+import static android.system.OsConstants.EPROTO;
+import static android.system.OsConstants.ETIMEDOUT;
+import static android.system.OsConstants.SO_RCVBUF;
+import static android.system.OsConstants.SO_RCVTIMEO;
+import static android.system.OsConstants.SO_SNDTIMEO;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOL_SOCKET;
+
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
 import android.system.Os;
-import android.system.OsConstants;
 import android.system.StructTimeval;
 import android.util.Log;
 import libcore.io.IoUtils;
 import libcore.io.Libcore;
 
-import java.io.Closeable;
 import java.io.FileDescriptor;
 import java.io.InterruptedIOException;
 import java.net.SocketAddress;
@@ -37,28 +45,27 @@
 /**
  * NetlinkSocket
  *
- * A small wrapper class to assist with AF_NETLINK socket operations.
+ * A small static class to assist with AF_NETLINK socket operations.
  *
  * @hide
  */
-public class NetlinkSocket implements Closeable {
+public class NetlinkSocket {
     private static final String TAG = "NetlinkSocket";
-    private static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
-    private static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
 
-    final private FileDescriptor mDescriptor;
-    private NetlinkSocketAddress mAddr;
-    private long mLastRecvTimeoutMs;
-    private long mLastSendTimeoutMs;
+    public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
+    public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
 
     public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
         final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
+        final long IO_TIMEOUT = 300L;
 
-        try (NetlinkSocket nlSocket = new NetlinkSocket(nlProto)) {
-            final long IO_TIMEOUT = 300L;
-            nlSocket.connectToKernel();
-            nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
-            final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
+        FileDescriptor fd;
+
+        try {
+            fd = forProto(nlProto);
+            connectToKernel(fd);
+            sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT);
+            final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT);
             // recvMessage() guaranteed to not return null if it did not throw.
             final NetlinkMessage response = NetlinkMessage.parse(bytes);
             if (response != null && response instanceof NetlinkErrorMessage &&
@@ -81,61 +88,30 @@
                     errmsg = response.toString();
                 }
                 Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
-                throw new ErrnoException(errmsg, OsConstants.EPROTO);
+                throw new ErrnoException(errmsg, EPROTO);
             }
         } catch (InterruptedIOException e) {
             Log.e(TAG, errPrefix, e);
-            throw new ErrnoException(errPrefix, OsConstants.ETIMEDOUT, e);
+            throw new ErrnoException(errPrefix, ETIMEDOUT, e);
         } catch (SocketException e) {
             Log.e(TAG, errPrefix, e);
-            throw new ErrnoException(errPrefix, OsConstants.EIO, e);
+            throw new ErrnoException(errPrefix, EIO, e);
         }
+
+        IoUtils.closeQuietly(fd);
     }
 
-    public NetlinkSocket(int nlProto) throws ErrnoException {
-        mDescriptor = Os.socket(
-                OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto);
-
-        Os.setsockoptInt(
-                mDescriptor, OsConstants.SOL_SOCKET,
-                OsConstants.SO_RCVBUF, SOCKET_RECV_BUFSIZE);
+    public static FileDescriptor forProto(int nlProto) throws ErrnoException {
+        final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
+        Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
+        return fd;
     }
 
-    public NetlinkSocketAddress getLocalAddress() throws ErrnoException {
-        return (NetlinkSocketAddress) Os.getsockname(mDescriptor);
+    public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
+        Os.connect(fd, (SocketAddress) (new NetlinkSocketAddress(0, 0)));
     }
 
-    public void bind(NetlinkSocketAddress localAddr) throws ErrnoException, SocketException {
-        Os.bind(mDescriptor, (SocketAddress)localAddr);
-    }
-
-    public void connectTo(NetlinkSocketAddress peerAddr)
-            throws ErrnoException, SocketException {
-        Os.connect(mDescriptor, (SocketAddress) peerAddr);
-    }
-
-    public void connectToKernel() throws ErrnoException, SocketException {
-        connectTo(new NetlinkSocketAddress(0, 0));
-    }
-
-    /**
-     * Wait indefinitely (or until underlying socket error) for a
-     * netlink message of at most DEFAULT_RECV_BUFSIZE size.
-     */
-    public ByteBuffer recvMessage()
-            throws ErrnoException, InterruptedIOException {
-        return recvMessage(DEFAULT_RECV_BUFSIZE, 0);
-    }
-
-    /**
-     * Wait up to |timeoutMs| (or until underlying socket error) for a
-     * netlink message of at most DEFAULT_RECV_BUFSIZE size.
-     */
-    public ByteBuffer recvMessage(long timeoutMs) throws ErrnoException, InterruptedIOException {
-        return recvMessage(DEFAULT_RECV_BUFSIZE, timeoutMs);
-    }
-
-    private void checkTimeout(long timeoutMs) {
+    private static void checkTimeout(long timeoutMs) {
         if (timeoutMs < 0) {
             throw new IllegalArgumentException("Negative timeouts not permitted");
         }
@@ -147,21 +123,14 @@
      *
      * Multi-threaded calls with different timeouts will cause unexpected results.
      */
-    public ByteBuffer recvMessage(int bufsize, long timeoutMs)
+    public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
             throws ErrnoException, IllegalArgumentException, InterruptedIOException {
         checkTimeout(timeoutMs);
 
-        synchronized (mDescriptor) {
-            if (mLastRecvTimeoutMs != timeoutMs) {
-                Os.setsockoptTimeval(mDescriptor,
-                        OsConstants.SOL_SOCKET, OsConstants.SO_RCVTIMEO,
-                        StructTimeval.fromMillis(timeoutMs));
-                mLastRecvTimeoutMs = timeoutMs;
-            }
-        }
+        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
 
         ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
-        int length = Os.read(mDescriptor, byteBuffer);
+        int length = Os.read(fd, byteBuffer);
         if (length == bufsize) {
             Log.w(TAG, "maximum read");
         }
@@ -172,39 +141,16 @@
     }
 
     /**
-     * Send a message to a peer to which this socket has previously connected.
-     *
-     * This blocks until completion or an error occurs.
-     */
-    public boolean sendMessage(byte[] bytes, int offset, int count)
-            throws ErrnoException, InterruptedIOException {
-        return sendMessage(bytes, offset, count, 0);
-    }
-
-    /**
      * Send a message to a peer to which this socket has previously connected,
      * waiting at most |timeoutMs| milliseconds for the send to complete.
      *
      * Multi-threaded calls with different timeouts will cause unexpected results.
      */
-    public boolean sendMessage(byte[] bytes, int offset, int count, long timeoutMs)
+    public static int sendMessage(
+            FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
             throws ErrnoException, IllegalArgumentException, InterruptedIOException {
         checkTimeout(timeoutMs);
-
-        synchronized (mDescriptor) {
-            if (mLastSendTimeoutMs != timeoutMs) {
-                Os.setsockoptTimeval(mDescriptor,
-                        OsConstants.SOL_SOCKET, OsConstants.SO_SNDTIMEO,
-                        StructTimeval.fromMillis(timeoutMs));
-                mLastSendTimeoutMs = timeoutMs;
-            }
-        }
-
-        return (count == Os.write(mDescriptor, bytes, offset, count));
-    }
-
-    @Override
-    public void close() {
-        IoUtils.closeQuietly(mDescriptor);
+        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
+        return Os.write(fd, bytes, offset, count);
     }
 }
diff --git a/services/net/java/android/net/netlink/StructNdMsg.java b/services/net/java/android/net/netlink/StructNdMsg.java
index b68ec0b..e34ec39 100644
--- a/services/net/java/android/net/netlink/StructNdMsg.java
+++ b/services/net/java/android/net/netlink/StructNdMsg.java
@@ -63,6 +63,11 @@
         return ((nudState & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)) != 0);
     }
 
+    public static boolean isNudStateValid(short nudState) {
+        return (isNudStateConnected(nudState) ||
+                ((nudState & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0));
+    }
+
     // Neighbor Cache Entry Flags
     public static byte NTF_USE       = (byte) 0x01;
     public static byte NTF_SELF      = (byte) 0x02;
@@ -143,7 +148,7 @@
     }
 
     public boolean nudValid() {
-        return (nudConnected() || ((ndm_state & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0));
+        return isNudStateValid(ndm_state);
     }
 
     @Override
diff --git a/services/net/java/android/net/util/BlockingSocketReader.java b/services/net/java/android/net/util/PacketReader.java
similarity index 97%
rename from services/net/java/android/net/util/BlockingSocketReader.java
rename to services/net/java/android/net/util/PacketReader.java
index 99bf469..10da2a5 100644
--- a/services/net/java/android/net/util/BlockingSocketReader.java
+++ b/services/net/java/android/net/util/PacketReader.java
@@ -67,7 +67,7 @@
  *
  * @hide
  */
-public abstract class BlockingSocketReader {
+public abstract class PacketReader {
     private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
     private static final int UNREGISTER_THIS_FD = 0;
 
@@ -83,11 +83,11 @@
         IoUtils.closeQuietly(fd);
     }
 
-    protected BlockingSocketReader(Handler h) {
+    protected PacketReader(Handler h) {
         this(h, DEFAULT_RECV_BUF_SIZE);
     }
 
-    protected BlockingSocketReader(Handler h, int recvbufsize) {
+    protected PacketReader(Handler h, int recvbufsize) {
         mHandler = h;
         mQueue = mHandler.getLooper().getQueue();
         mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)];
@@ -115,6 +115,8 @@
         }
     }
 
+    public Handler getHandler() { return mHandler; }
+
     public final int recvBufSize() { return mPacket.length; }
 
     public final long numPacketsReceived() { return mPacketsReceived; }
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 926a911..8cf575e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -2121,22 +2121,30 @@
                 PACKAGE_FALLBACK_LAUNCHER_PRIORITY);
     }
 
-    protected void makeCallerForeground() {
+    protected void makeUidForeground(int uid) {
         try {
             mService.mUidObserver.onUidStateChanged(
-                    mInjectedCallingUid, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
+                    uid, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    protected void makeCallerForeground() {
+        makeUidForeground(mInjectedCallingUid);
+    }
+
+    protected void makeUidBackground(int uid) {
+        try {
+            mService.mUidObserver.onUidStateChanged(
+                    uid, ActivityManager.PROCESS_STATE_TOP_SLEEPING, 0);
         } catch (RemoteException e) {
             e.rethrowAsRuntimeException();
         }
     }
 
     protected void makeCallerBackground() {
-        try {
-            mService.mUidObserver.onUidStateChanged(
-                    mInjectedCallingUid, ActivityManager.PROCESS_STATE_TOP_SLEEPING, 0);
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
-        }
+        makeUidBackground(mInjectedCallingUid);
     }
 
     protected void publishManifestShortcutsAsCaller(int resId) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index beffcee..e12a8da 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -43,11 +43,13 @@
       class PackageSenderImpl implements PackageSender {
         public void sendPackageBroadcast(final String action, final String pkg,
             final Bundle extras, final int flags, final String targetPkg,
-            final IIntentReceiver finishedReceiver, final int[] userIds) {
+            final IIntentReceiver finishedReceiver, final int[] userIds,
+            int[] instantUserIds) {
         }
 
         public void sendPackageAddedForNewUsers(String packageName,
-            boolean sendBootComplete, boolean includeStopped, int appId, int... userIds) {
+            boolean sendBootComplete, boolean includeStopped, int appId,
+            int[] userIds, int[] instantUserIds) {
         }
       }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index f92b575..ee33e79 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -7912,4 +7912,35 @@
                     .forAllShortcuts(si -> assertTrue(si.isReturnedByServer()));
         });
     }
+
+    public void testIsForegroundDefaultLauncher_true() {
+        final ComponentName defaultLauncher = new ComponentName("default", "launcher");
+        final int uid = 1024;
+
+        setDefaultLauncher(UserHandle.USER_SYSTEM, defaultLauncher);
+        makeUidForeground(uid);
+
+        assertTrue(mInternal.isForegroundDefaultLauncher("default", uid));
+    }
+
+
+    public void testIsForegroundDefaultLauncher_defaultButNotForeground() {
+        final ComponentName defaultLauncher = new ComponentName("default", "launcher");
+        final int uid = 1024;
+
+        setDefaultLauncher(UserHandle.USER_SYSTEM, defaultLauncher);
+        makeUidBackground(uid);
+
+        assertFalse(mInternal.isForegroundDefaultLauncher("default", uid));
+    }
+
+    public void testIsForegroundDefaultLauncher_foregroundButNotDefault() {
+        final ComponentName defaultLauncher = new ComponentName("default", "launcher");
+        final int uid = 1024;
+
+        setDefaultLauncher(UserHandle.USER_SYSTEM, defaultLauncher);
+        makeUidForeground(uid);
+
+        assertFalse(mInternal.isForegroundDefaultLauncher("another", uid));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
index 0941d4f..9a6da0e 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
@@ -16,7 +16,12 @@
 
 package com.android.server.policy;
 
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
@@ -29,6 +34,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.view.Surface;
 import android.view.WindowManager;
 
 import org.junit.Before;
@@ -105,4 +111,123 @@
         assertEquals(0, mAppWindow.attrs.systemUiVisibility);
         assertEquals(0, mAppWindow.attrs.subtreeSystemUiVisibility);
     }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout() {
+        addDisplayCutout();
+
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_fullscreen() {
+        addDisplayCutout();
+
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() {
+        addDisplayCutout();
+
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+    }
+
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_landscape() {
+        addDisplayCutout();
+        setRotation(ROTATION_90);
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
+        assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.contentFrame,
+                DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_seascape() {
+        addDisplayCutout();
+        setRotation(ROTATION_270);
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetBy(mAppWindow.parentFrame, 0, 0, DISPLAY_CUTOUT_HEIGHT, 0);
+        assertInsetBy(mAppWindow.stableFrame, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0);
+        assertInsetBy(mAppWindow.contentFrame,
+                NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0);
+        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_fullscreen_landscape() {
+        addDisplayCutout();
+        setRotation(ROTATION_90);
+
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
+        assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.contentFrame,
+                DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_fullscreenInCutout_landscape() {
+        addDisplayCutout();
+        setRotation(ROTATION_90);
+
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetBy(mAppWindow.parentFrame, 0, 0, 0, 0);
+        assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.contentFrame,
+                DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
+    }
+
 }
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
index 9c55707..e7e9abad 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
@@ -17,6 +17,9 @@
 package com.android.server.policy;
 
 import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -54,6 +57,7 @@
 
     static final int STATUS_BAR_HEIGHT = 10;
     static final int NAV_BAR_HEIGHT = 15;
+    static final int DISPLAY_CUTOUT_HEIGHT = 8;
 
     TestablePhoneWindowManager mPolicy;
     TestContextWrapper mContext;
@@ -76,10 +80,17 @@
 
         mPolicy = TestablePhoneWindowManager.create(mContext);
 
+        setRotation(ROTATION_0);
+    }
+
+    public void setRotation(int rotation) {
         DisplayInfo info = new DisplayInfo();
-        info.logicalWidth = DISPLAY_WIDTH;
-        info.logicalHeight = DISPLAY_HEIGHT;
-        info.rotation = ROTATION_0;
+
+        final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
+        info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+        info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+        info.rotation = rotation;
+
         mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info);
     }
 
@@ -95,7 +106,7 @@
 
     public void addNavigationBar() {
         mNavigationBar = new FakeWindowState();
-        mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, STATUS_BAR_HEIGHT,
+        mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, NAV_BAR_HEIGHT,
                 TYPE_NAVIGATION_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT);
         mNavigationBar.attrs.gravity = Gravity.BOTTOM;
 
@@ -104,11 +115,16 @@
         mPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT;
     }
 
+    public void addDisplayCutout() {
+        mPolicy.mEmulateDisplayCutout = true;
+    }
+
     /** Asserts that {@code actual} is inset by the given amounts from the full display rect. */
     public void assertInsetBy(Rect actual, int expectedInsetLeft, int expectedInsetTop,
             int expectedInsetRight, int expectedInsetBottom) {
         assertEquals(new Rect(expectedInsetLeft, expectedInsetTop,
-                DISPLAY_WIDTH - expectedInsetRight, DISPLAY_HEIGHT - expectedInsetBottom), actual);
+                mFrames.mDisplayWidth - expectedInsetRight,
+                mFrames.mDisplayHeight - expectedInsetBottom), actual);
     }
 
     /**
@@ -181,6 +197,11 @@
                 policy[0].mAccessibilityManager = new AccessibilityManager(context,
                         mock(IAccessibilityManager.class), UserHandle.USER_CURRENT);
                 policy[0].mSystemGestures = mock(SystemGesturesPointerEventListener.class);
+                policy[0].mNavigationBarCanMove = true;
+                policy[0].mPortraitRotation = ROTATION_0;
+                policy[0].mLandscapeRotation = ROTATION_90;
+                policy[0].mUpsideDownRotation = ROTATION_180;
+                policy[0].mSeascapeRotation = ROTATION_270;
                 policy[0].onConfigurationChanged();
             });
             return policy[0];
diff --git a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
index f069d49..4dd51eb 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
@@ -48,6 +48,10 @@
         final SurfaceControl mControl = mock(SurfaceControl.class);
         final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class);
 
+        TestWindowContainer() {
+            super(sWm);
+        }
+
         @Override
         SurfaceControl getSurfaceControl() {
             return mControl;
@@ -65,6 +69,10 @@
         final SurfaceControl mHostControl = mock(SurfaceControl.class);
         final SurfaceControl.Transaction mHostTransaction = mock(SurfaceControl.Transaction.class);
 
+        MockSurfaceBuildingContainer() {
+            super(sWm);
+        }
+
         class MockSurfaceBuilder extends SurfaceControl.Builder {
             MockSurfaceBuilder(SurfaceSession ss) {
                 super(ss);
@@ -78,6 +86,7 @@
             }
         }
 
+
         @Override
         SurfaceControl.Builder makeChildSurface(WindowContainer child) {
             return new MockSurfaceBuilder(mSession);
diff --git a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
index 5da3224..ab0a2bd 100644
--- a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
@@ -34,7 +34,7 @@
  * Test class for {@link StackWindowController}.
  *
  * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.wm.StackWindowControllerTests
+ *  atest FrameworksServicesTests:StackWindowControllerTests
  */
 @SmallTest
 @Presubmit
@@ -61,7 +61,6 @@
     }
 
     @Test
-    @Ignore("b/65379195")
     public void testRemoveContainer_deferRemoval() throws Exception {
         final StackWindowController stackController =
                 createStackControllerOnDisplay(mDisplayContent);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerControllerTests.java
index 8df7568..bab2170 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerControllerTests.java
@@ -44,7 +44,7 @@
     @Test
     public void testCreation() throws Exception {
         final WindowContainerController controller = new WindowContainerController(null, sWm);
-        final WindowContainer container = new WindowContainer();
+        final WindowContainer container = new WindowContainer(sWm);
 
         container.setController(controller);
         assertEquals(controller, container.getController());
@@ -54,7 +54,7 @@
     @Test
     public void testSetContainer() throws Exception {
         final WindowContainerController controller = new WindowContainerController(null, sWm);
-        final WindowContainer container = new WindowContainer();
+        final WindowContainer container = new WindowContainer(sWm);
 
         controller.setContainer(container);
         assertEquals(controller.mContainer, container);
@@ -62,7 +62,7 @@
         // Assert we can't change the container to another one once set
         boolean gotException = false;
         try {
-            controller.setContainer(new WindowContainer());
+            controller.setContainer(new WindowContainer(sWm));
         } catch (IllegalArgumentException e) {
             gotException = true;
         }
@@ -76,7 +76,7 @@
     @Test
     public void testRemoveContainer() throws Exception {
         final WindowContainerController controller = new WindowContainerController(null, sWm);
-        final WindowContainer container = new WindowContainer();
+        final WindowContainer container = new WindowContainer(sWm);
 
         controller.setContainer(container);
         assertEquals(controller.mContainer, container);
@@ -88,7 +88,7 @@
     @Test
     public void testOnOverrideConfigurationChanged() throws Exception {
         final WindowContainerController controller = new WindowContainerController(null, sWm);
-        final WindowContainer container = new WindowContainer();
+        final WindowContainer container = new WindowContainer(sWm);
 
         controller.setContainer(container);
         assertEquals(controller.mContainer, container);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 16b6ca6..5cb9467 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -193,7 +193,7 @@
 
     @Test
     public void testRemoveImmediately_WithController() throws Exception {
-        final WindowContainer container = new WindowContainer();
+        final WindowContainer container = new WindowContainer(sWm);
         final WindowContainerController controller = new WindowContainerController(null, sWm);
 
         container.setController(controller);
@@ -208,7 +208,7 @@
     @Test
     public void testSetController() throws Exception {
         final WindowContainerController controller = new WindowContainerController(null, sWm);
-        final WindowContainer container = new WindowContainer();
+        final WindowContainer container = new WindowContainer(sWm);
 
         container.setController(controller);
         assertEquals(controller, container.getController());
@@ -587,6 +587,7 @@
 
         TestWindowContainer(int layer, boolean isAnimating, boolean isVisible,
             Integer orientation) {
+            super(sWm);
             mLayer = layer;
             mIsAnimating = isAnimating;
             mIsVisible = isVisible;
diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
index e5cbdba..6468763 100644
--- a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
@@ -16,23 +16,6 @@
 
 package com.android.server.wm;
 
-import java.util.HashMap;
-import java.util.LinkedList;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.util.Log;
-
-import android.platform.test.annotations.Presubmit;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -42,10 +25,23 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.LinkedList;
 
 /**
  * Tests for the {@link WindowLayersController} class.
@@ -79,7 +75,7 @@
         }
 
         int getLayer(SurfaceControl sc) {
-            return mLayersForControl.get(sc);
+            return mLayersForControl.getOrDefault(sc, 0);
         }
 
         SurfaceControl getRelativeLayer(SurfaceControl sc) {
@@ -125,6 +121,7 @@
         // would miss construction of the top-level layers.
         mTransaction = new LayerRecordingTransaction();
         sWm.mSurfaceBuilderFactory = new HierarchyRecordingBuilderFactory();
+        sWm.mTransactionFactory = () -> mTransaction;
     }
 
     @After
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
index 4eb4220..5ebfd48 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
@@ -23,6 +23,7 @@
 
 import android.service.notification.ScheduleCalendar;
 import android.service.notification.ZenModeConfig;
+import android.support.test.filters.FlakyTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -257,6 +258,7 @@
     }
 
     @Test
+    @FlakyTest
     public void testIsInSchedule_inScheduleOvernight() {
         Calendar cal = new GregorianCalendar();
         cal.set(Calendar.HOUR_OF_DAY, 23);
@@ -273,6 +275,7 @@
     }
 
     @Test
+    @FlakyTest
     public void testIsInSchedule_inScheduleSingleDay() {
         Calendar cal = new GregorianCalendar();
         cal.set(Calendar.HOUR_OF_DAY, 14);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index e55e073..9705469 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -503,7 +503,7 @@
                     mCal.getTimeInMillis());
 
             mCal.setTimeInMillis(currentTimeMillis);
-            mCal.addDays(-7);
+            mCal.addDays(-10);
             pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY],
                     mCal.getTimeInMillis());
 
@@ -803,4 +803,4 @@
         }
         directory.delete();
     }
-}
\ No newline at end of file
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 51c805d..cd3fdee 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -66,6 +66,7 @@
     private SoundTriggerDbHelper mDbHelper;
     private SoundTriggerHelper mSoundTriggerHelper;
     private final TreeMap<UUID, SoundModel> mLoadedModels;
+    private Object mCallbacksLock;
     private final TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback> mIntentCallbacks;
     private PowerManager.WakeLock mWakelock;
 
@@ -75,6 +76,7 @@
         mServiceStub = new SoundTriggerServiceStub();
         mLocalSoundTriggerService = new LocalSoundTriggerService(context);
         mLoadedModels = new TreeMap<UUID, SoundModel>();
+        mCallbacksLock = new Object();
         mIntentCallbacks = new TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback>();
         mLock = new Object();
     }
@@ -211,7 +213,9 @@
                 // don't know if the other model is loaded.
                 if (oldModel != null && !oldModel.equals(soundModel)) {
                     mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
-                    mIntentCallbacks.remove(soundModel.uuid);
+                    synchronized (mCallbacksLock) {
+                        mIntentCallbacks.remove(soundModel.uuid);
+                    }
                 }
                 mLoadedModels.put(soundModel.uuid, soundModel);
             }
@@ -240,7 +244,9 @@
                 // don't know if the other model is loaded.
                 if (oldModel != null && !oldModel.equals(soundModel)) {
                     mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
-                    mIntentCallbacks.remove(soundModel.uuid);
+                    synchronized (mCallbacksLock) {
+                        mIntentCallbacks.remove(soundModel.uuid);
+                    }
                 }
                 mLoadedModels.put(soundModel.uuid, soundModel);
             }
@@ -262,8 +268,10 @@
                     Slog.e(TAG, soundModelId + " is not loaded");
                     return STATUS_ERROR;
                 }
-                LocalSoundTriggerRecognitionStatusCallback callback = mIntentCallbacks.get(
-                        soundModelId.getUuid());
+                LocalSoundTriggerRecognitionStatusCallback callback = null;
+                synchronized (mCallbacksLock) {
+                    callback = mIntentCallbacks.get(soundModelId.getUuid());
+                }
                 if (callback != null) {
                     Slog.e(TAG, soundModelId + " is already running");
                     return STATUS_ERROR;
@@ -291,7 +299,9 @@
                     Slog.e(TAG, "Failed to start model: " + ret);
                     return ret;
                 }
-                mIntentCallbacks.put(soundModelId.getUuid(), callback);
+                synchronized (mCallbacksLock) {
+                    mIntentCallbacks.put(soundModelId.getUuid(), callback);
+                }
             }
             return STATUS_OK;
         }
@@ -310,8 +320,10 @@
                     Slog.e(TAG, soundModelId + " is not loaded");
                     return STATUS_ERROR;
                 }
-                LocalSoundTriggerRecognitionStatusCallback callback = mIntentCallbacks.get(
-                        soundModelId.getUuid());
+                LocalSoundTriggerRecognitionStatusCallback callback = null;
+                synchronized (mCallbacksLock) {
+                     callback = mIntentCallbacks.get(soundModelId.getUuid());
+                }
                 if (callback == null) {
                     Slog.e(TAG, soundModelId + " is not running");
                     return STATUS_ERROR;
@@ -334,7 +346,9 @@
                     Slog.e(TAG, "Failed to stop model: " + ret);
                     return ret;
                 }
-                mIntentCallbacks.remove(soundModelId.getUuid());
+                synchronized (mCallbacksLock) {
+                    mIntentCallbacks.remove(soundModelId.getUuid());
+                }
             }
             return STATUS_OK;
         }
@@ -379,14 +393,14 @@
         public boolean isRecognitionActive(ParcelUuid parcelUuid) {
             enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
             if (!isInitialized()) return false;
-            synchronized (mLock) {
+            synchronized (mCallbacksLock) {
                 LocalSoundTriggerRecognitionStatusCallback callback =
                         mIntentCallbacks.get(parcelUuid.getUuid());
                 if (callback == null) {
                     return false;
                 }
-                return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
             }
+            return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
         }
     }
 
@@ -513,7 +527,7 @@
 
         private void removeCallback(boolean releaseWakeLock) {
             mCallbackIntent = null;
-            synchronized (mLock) {
+            synchronized (mCallbacksLock) {
                 mIntentCallbacks.remove(mUuid);
                 if (releaseWakeLock) {
                     mWakelock.release();
@@ -523,7 +537,7 @@
     }
 
     private void grabWakeLock() {
-        synchronized (mLock) {
+        synchronized (mCallbacksLock) {
             if (mWakelock == null) {
                 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
                 mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
@@ -537,7 +551,7 @@
         public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
                 String resultData, Bundle resultExtras) {
             // We're only ever invoked when the callback is done, so release the lock.
-            synchronized (mLock) {
+            synchronized (mCallbacksLock) {
                 mWakelock.release();
             }
         }
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index c3a2ceb..56e1e64 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -280,6 +280,36 @@
      * {@hide}
      */
     public static final int NORMAL_UNSPECIFIED = 65;
+
+    /**
+     * Stk Call Control modified DIAL request to video DIAL request.
+     * {@hide}
+     */
+    public static final int DIAL_MODIFIED_TO_DIAL_VIDEO = 66;
+
+    /**
+     * Stk Call Control modified Video DIAL request to SS request.
+     * {@hide}
+     */
+    public static final int DIAL_VIDEO_MODIFIED_TO_SS = 67;
+
+    /**
+     * Stk Call Control modified Video DIAL request to USSD request.
+     * {@hide}
+     */
+    public static final int DIAL_VIDEO_MODIFIED_TO_USSD = 68;
+
+    /**
+     * Stk Call Control modified Video DIAL request to DIAL request.
+     * {@hide}
+     */
+    public static final int DIAL_VIDEO_MODIFIED_TO_DIAL = 69;
+
+    /**
+     * Stk Call Control modified Video DIAL request to Video DIAL request.
+     * {@hide}
+     */
+    public static final int DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO = 70;
     //*********************************************************************************************
     // When adding a disconnect type:
     // 1) Update toString() with the newly added disconnect type.
@@ -382,6 +412,16 @@
             return "DIAL_MODIFIED_TO_SS";
         case DIAL_MODIFIED_TO_DIAL:
             return "DIAL_MODIFIED_TO_DIAL";
+        case DIAL_MODIFIED_TO_DIAL_VIDEO:
+            return "DIAL_MODIFIED_TO_DIAL_VIDEO";
+        case DIAL_VIDEO_MODIFIED_TO_SS:
+            return "DIAL_VIDEO_MODIFIED_TO_SS";
+        case DIAL_VIDEO_MODIFIED_TO_USSD:
+            return "DIAL_VIDEO_MODIFIED_TO_USSD";
+        case DIAL_VIDEO_MODIFIED_TO_DIAL:
+            return "DIAL_VIDEO_MODIFIED_TO_DIAL";
+        case DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO:
+            return "DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO";
         case ERROR_UNSPECIFIED:
             return "ERROR_UNSPECIFIED";
         case OUTGOING_FAILURE:
diff --git a/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java
index b371efb..daa74c8 100644
--- a/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java
@@ -21,6 +21,7 @@
 
 import com.android.ims.ImsCallForwardInfo;
 import com.android.ims.ImsReasonInfo;
+import com.android.ims.ImsSsData;
 import com.android.ims.ImsSsInfo;
 import com.android.ims.internal.IImsUt;
 import com.android.ims.internal.IImsUtListener;
@@ -85,4 +86,10 @@
     public void utConfigurationCallWaitingQueried(IImsUt ut, int id, ImsSsInfo[] cwInfo)
             throws RemoteException {
     }
+
+    /**
+     * Notifies client when Supplementary Service indication is received
+     */
+    @Override
+    public void onSupplementaryServiceIndication(ImsSsData ssData) {}
 }
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java
index cdfc1fd..6ad54c1 100644
--- a/telephony/java/com/android/ims/ImsReasonInfo.java
+++ b/telephony/java/com/android/ims/ImsReasonInfo.java
@@ -111,6 +111,16 @@
     // and this capability is not supported by the network.
     public static final int CODE_IMEI_NOT_ACCEPTED = 243;
 
+    //STK CC errors
+    public static final int CODE_DIAL_MODIFIED_TO_USSD = 244;
+    public static final int CODE_DIAL_MODIFIED_TO_SS = 245;
+    public static final int CODE_DIAL_MODIFIED_TO_DIAL = 246;
+    public static final int CODE_DIAL_MODIFIED_TO_DIAL_VIDEO = 247;
+    public static final int CODE_DIAL_VIDEO_MODIFIED_TO_DIAL = 248;
+    public static final int CODE_DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO = 249;
+    public static final int CODE_DIAL_VIDEO_MODIFIED_TO_SS = 250;
+    public static final int CODE_DIAL_VIDEO_MODIFIED_TO_USSD = 251;
+
     /**
      * STATUSCODE (SIP response code) (IMS -> Telephony)
      */
@@ -217,6 +227,11 @@
     public static final int CODE_UT_OPERATION_NOT_ALLOWED = 803;
     public static final int CODE_UT_NETWORK_ERROR = 804;
     public static final int CODE_UT_CB_PASSWORD_MISMATCH = 821;
+    //STK CC errors
+    public static final int CODE_UT_SS_MODIFIED_TO_DIAL = 822;
+    public static final int CODE_UT_SS_MODIFIED_TO_USSD = 823;
+    public static final int CODE_UT_SS_MODIFIED_TO_SS = 824;
+    public static final int CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO = 825;
 
     /**
      * ECBM
diff --git a/telephony/java/com/android/ims/ImsSsData.aidl b/telephony/java/com/android/ims/ImsSsData.aidl
new file mode 100644
index 0000000..33f8306
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsSsData.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.ims;
+
+parcelable ImsSsData;
diff --git a/telephony/java/com/android/ims/ImsSsData.java b/telephony/java/com/android/ims/ImsSsData.java
new file mode 100644
index 0000000..7336c13
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsSsData.java
@@ -0,0 +1,189 @@
+/*
+ * 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.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+
+/**
+ * Provided STK Call Control Suplementary Service information
+ *
+ * {@hide}
+ */
+public class ImsSsData implements Parcelable {
+
+    //ServiceType
+    public static final int SS_CFU = 0;
+    public static final int SS_CF_BUSY = 1;
+    public static final int SS_CF_NO_REPLY = 2;
+    public static final int SS_CF_NOT_REACHABLE = 3;
+    public static final int SS_CF_ALL = 4;
+    public static final int SS_CF_ALL_CONDITIONAL = 5;
+    public static final int SS_CFUT = 6;
+    public static final int SS_CLIP = 7;
+    public static final int SS_CLIR = 8;
+    public static final int SS_COLP = 9;
+    public static final int SS_COLR = 10;
+    public static final int SS_CNAP = 11;
+    public static final int SS_WAIT = 12;
+    public static final int SS_BAOC = 13;
+    public static final int SS_BAOIC = 14;
+    public static final int SS_BAOIC_EXC_HOME = 15;
+    public static final int SS_BAIC = 16;
+    public static final int SS_BAIC_ROAMING = 17;
+    public static final int SS_ALL_BARRING = 18;
+    public static final int SS_OUTGOING_BARRING = 19;
+    public static final int SS_INCOMING_BARRING = 20;
+    public static final int SS_INCOMING_BARRING_DN = 21;
+    public static final int SS_INCOMING_BARRING_ANONYMOUS = 22;
+
+    //SSRequestType
+    public static final int SS_ACTIVATION = 0;
+    public static final int SS_DEACTIVATION = 1;
+    public static final int SS_INTERROGATION = 2;
+    public static final int SS_REGISTRATION = 3;
+    public static final int SS_ERASURE = 4;
+
+    //TeleserviceType
+    public static final int SS_ALL_TELE_AND_BEARER_SERVICES = 0;
+    public static final int SS_ALL_TELESEVICES = 1;
+    public static final int SS_TELEPHONY = 2;
+    public static final int SS_ALL_DATA_TELESERVICES = 3;
+    public static final int SS_SMS_SERVICES = 4;
+    public static final int SS_ALL_TELESERVICES_EXCEPT_SMS = 5;
+
+    // Refer to ServiceType
+    public int serviceType;
+    // Refere to SSRequestType
+    public int requestType;
+    // Refer to TeleserviceType
+    public int teleserviceType;
+    // Service Class
+    public int serviceClass;
+    // Error information
+    public int result;
+
+    public int[] ssInfo; /* Valid for all supplementary services.
+                             This field will be empty for RequestType SS_INTERROGATION
+                             and ServiceType SS_CF_*, SS_INCOMING_BARRING_DN,
+                             SS_INCOMING_BARRING_ANONYMOUS.*/
+
+    public ImsCallForwardInfo[] cfInfo; /* Valid only for supplementary services
+                                            ServiceType SS_CF_* and RequestType SS_INTERROGATION */
+
+    public ImsSsInfo[] imsSsInfo;   /* Valid only for ServiceType SS_INCOMING_BARRING_DN and
+                                        ServiceType SS_INCOMING_BARRING_ANONYMOUS */
+
+    public ImsSsData() {}
+
+    public ImsSsData(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public static final Creator<ImsSsData> CREATOR = new Creator<ImsSsData>() {
+        @Override
+        public ImsSsData createFromParcel(Parcel in) {
+            return new ImsSsData(in);
+        }
+
+        @Override
+        public ImsSsData[] newArray(int size) {
+            return new ImsSsData[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(serviceType);
+        out.writeInt(requestType);
+        out.writeInt(teleserviceType);
+        out.writeInt(serviceClass);
+        out.writeInt(result);
+        out.writeIntArray(ssInfo);
+        out.writeParcelableArray(cfInfo, 0);
+    }
+
+    private void readFromParcel(Parcel in) {
+        serviceType = in.readInt();
+        requestType = in.readInt();
+        teleserviceType = in.readInt();
+        serviceClass = in.readInt();
+        result = in.readInt();
+        ssInfo = in.createIntArray();
+        cfInfo = (ImsCallForwardInfo[])in.readParcelableArray(this.getClass().getClassLoader());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public boolean isTypeCF() {
+        return (serviceType == SS_CFU || serviceType == SS_CF_BUSY ||
+              serviceType == SS_CF_NO_REPLY || serviceType == SS_CF_NOT_REACHABLE ||
+              serviceType == SS_CF_ALL || serviceType == SS_CF_ALL_CONDITIONAL);
+    }
+
+    public boolean isTypeUnConditional() {
+        return (serviceType == SS_CFU || serviceType == SS_CF_ALL);
+    }
+
+    public boolean isTypeCW() {
+        return (serviceType == SS_WAIT);
+    }
+
+    public boolean isTypeClip() {
+        return (serviceType == SS_CLIP);
+    }
+
+    public boolean isTypeColr() {
+        return (serviceType == SS_COLR);
+    }
+
+    public boolean isTypeColp() {
+        return (serviceType == SS_COLP);
+    }
+
+    public boolean isTypeClir() {
+        return (serviceType == SS_CLIR);
+    }
+
+    public boolean isTypeIcb() {
+        return (serviceType == SS_INCOMING_BARRING_DN ||
+                serviceType == SS_INCOMING_BARRING_ANONYMOUS);
+    }
+
+    public boolean isTypeBarring() {
+        return (serviceType == SS_BAOC || serviceType == SS_BAOIC ||
+              serviceType == SS_BAOIC_EXC_HOME || serviceType == SS_BAIC ||
+              serviceType == SS_BAIC_ROAMING || serviceType == SS_ALL_BARRING ||
+              serviceType == SS_OUTGOING_BARRING || serviceType == SS_INCOMING_BARRING);
+    }
+
+    public boolean isTypeInterrogation() {
+        return (requestType == SS_INTERROGATION);
+    }
+
+    public String toString() {
+        return "[ImsSsData] " + "ServiceType: " + serviceType
+            + " RequestType: " + requestType
+            + " TeleserviceType: " + teleserviceType
+            + " ServiceClass: " + serviceClass
+            + " Result: " + result;
+    }
+}
diff --git a/telephony/java/com/android/ims/ImsUtInterface.java b/telephony/java/com/android/ims/ImsUtInterface.java
index 250371f..14c184a 100644
--- a/telephony/java/com/android/ims/ImsUtInterface.java
+++ b/telephony/java/com/android/ims/ImsUtInterface.java
@@ -16,6 +16,7 @@
 
 package com.android.ims;
 
+import android.os.Handler;
 import android.os.Message;
 
 /**
@@ -188,4 +189,18 @@
      * Updates the configuration of the COLP supplementary service.
      */
     public void updateCOLP(boolean enable, Message result);
+
+    /**
+     * Register for UNSOL_ON_SS indications.
+     * @param handler the {@link Handler} that is notified when there is an ss indication.
+     * @param event  Supplimentary service indication event.
+     * @param Object user object.
+     */
+    public void registerForSuppServiceIndication(Handler handler, int event, Object object);
+
+    /**
+     * Deregister for UNSOL_ON_SS indications.
+     * @param handler the {@link Handler} that is notified when there is an ss indication.
+     */
+    public void unregisterForSuppServiceIndication(Handler handler);
 }
diff --git a/telephony/java/com/android/ims/internal/IImsUtListener.aidl b/telephony/java/com/android/ims/internal/IImsUtListener.aidl
index 300273a..1bc0369 100644
--- a/telephony/java/com/android/ims/internal/IImsUtListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsUtListener.aidl
@@ -19,6 +19,7 @@
 import android.os.Bundle;
 
 import com.android.ims.ImsCallForwardInfo;
+import com.android.ims.ImsSsData;
 import com.android.ims.ImsSsInfo;
 import com.android.ims.internal.IImsUt;
 import com.android.ims.ImsReasonInfo;
@@ -56,4 +57,11 @@
      */
     void utConfigurationCallWaitingQueried(in IImsUt ut,
             int id, in ImsSsInfo[] cwInfo);
+
+    /**
+     * Notifies client when Supplementary Service indication is received
+     *
+     * @param ssData Details of SS request and response information
+     */
+    void onSupplementaryServiceIndication(in ImsSsData ssData);
 }
diff --git a/tests/BrowserPowerTest/Android.mk b/tests/BrowserPowerTest/Android.mk
index 59bc729..5765575 100644
--- a/tests/BrowserPowerTest/Android.mk
+++ b/tests/BrowserPowerTest/Android.mk
@@ -18,8 +18,8 @@
 # We only want this apk build for tests.
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/CanvasCompare/Android.mk b/tests/CanvasCompare/Android.mk
index 90de503..b071ec4 100644
--- a/tests/CanvasCompare/Android.mk
+++ b/tests/CanvasCompare/Android.mk
@@ -23,7 +23,7 @@
 
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 include $(BUILD_PACKAGE)
diff --git a/tests/CoreTests/android/Android.mk b/tests/CoreTests/android/Android.mk
index c9f1161..56d7918 100644
--- a/tests/CoreTests/android/Android.mk
+++ b/tests/CoreTests/android/Android.mk
@@ -6,8 +6,14 @@
 LOCAL_SRC_FILES := \
 	$(call all-subdir-java-files)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle conscrypt org.apache.http.legacy
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner \
+    bouncycastle \
+    conscrypt \
+    org.apache.http.legacy \
+    android.test.base \
+
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 LOCAL_PACKAGE_NAME := CoreTests
 
diff --git a/tests/DataIdleTest/Android.mk b/tests/DataIdleTest/Android.mk
index 4e15729..85f7edf 100644
--- a/tests/DataIdleTest/Android.mk
+++ b/tests/DataIdleTest/Android.mk
@@ -20,8 +20,8 @@
 LOCAL_MODULE_TAGS := tests
 
 LOCAL_PACKAGE_NAME := DataIdleTest
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 # We need to sign it to get access to the network usage history.
diff --git a/tests/FrameworkPerf/Android.mk b/tests/FrameworkPerf/Android.mk
index d2ec753..1873cc1 100644
--- a/tests/FrameworkPerf/Android.mk
+++ b/tests/FrameworkPerf/Android.mk
@@ -7,8 +7,8 @@
 
 LOCAL_PACKAGE_NAME := FrameworkPerf
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 LOCAL_AAPT_FLAGS = -c 120dpi,240dpi,160dpi,161dpi,320dpi,nodpi
 
diff --git a/tests/HierarchyViewerTest/Android.mk b/tests/HierarchyViewerTest/Android.mk
index f8c8656..8550d70 100644
--- a/tests/HierarchyViewerTest/Android.mk
+++ b/tests/HierarchyViewerTest/Android.mk
@@ -7,7 +7,7 @@
 
 LOCAL_PACKAGE_NAME := HierarchyViewerTest
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 include $(BUILD_PACKAGE)
diff --git a/tests/ImfTest/tests/Android.mk b/tests/ImfTest/tests/Android.mk
index 6042471..a0df959 100644
--- a/tests/ImfTest/tests/Android.mk
+++ b/tests/ImfTest/tests/Android.mk
@@ -7,8 +7,8 @@
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 LOCAL_PACKAGE_NAME := ImfTestTests
 
diff --git a/tests/Internal/Android.mk b/tests/Internal/Android.mk
index fc001e9..73181ec 100644
--- a/tests/Internal/Android.mk
+++ b/tests/Internal/Android.mk
@@ -11,7 +11,6 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_STATIC_JAVA_LIBRARIES := junit \
-    legacy-android-test \
     android-support-test \
     mockito-target-minus-junit4
 
diff --git a/tests/MemoryUsage/Android.mk b/tests/MemoryUsage/Android.mk
index 578e628..e186e9f 100644
--- a/tests/MemoryUsage/Android.mk
+++ b/tests/MemoryUsage/Android.mk
@@ -9,8 +9,8 @@
 LOCAL_PACKAGE_NAME := MemoryUsage
 
 LOCAL_CERTIFICATE := platform
-LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 include $(BUILD_PACKAGE)
 
diff --git a/tests/NetworkSecurityConfigTest/Android.mk b/tests/NetworkSecurityConfigTest/Android.mk
index dd9ff11..6fb6025 100644
--- a/tests/NetworkSecurityConfigTest/Android.mk
+++ b/tests/NetworkSecurityConfigTest/Android.mk
@@ -5,8 +5,13 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_CERTIFICATE := platform
 
-LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle conscrypt
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner \
+    bouncycastle \
+    conscrypt \
+    android.test.base \
+
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/SoundTriggerTests/Android.mk b/tests/SoundTriggerTests/Android.mk
index 359484e..030d5f4 100644
--- a/tests/SoundTriggerTests/Android.mk
+++ b/tests/SoundTriggerTests/Android.mk
@@ -27,8 +27,8 @@
   LOCAL_SRC_FILES := src/android/hardware/soundtrigger/SoundTriggerTest.java
 endif
 
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target legacy-android-test
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
 
 LOCAL_PACKAGE_NAME := SoundTriggerTests
 
diff --git a/tests/SurfaceComposition/Android.mk b/tests/SurfaceComposition/Android.mk
index d97c3f4..1962791 100644
--- a/tests/SurfaceComposition/Android.mk
+++ b/tests/SurfaceComposition/Android.mk
@@ -27,7 +27,9 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+LOCAL_STATIC_JAVA_LIBRARIES := junit
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 LOCAL_PACKAGE_NAME := SurfaceComposition
 
diff --git a/tests/TtsTests/Android.mk b/tests/TtsTests/Android.mk
index 3c3cd77..2fa1950 100644
--- a/tests/TtsTests/Android.mk
+++ b/tests/TtsTests/Android.mk
@@ -20,8 +20,8 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target legacy-android-test
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
 
 LOCAL_PACKAGE_NAME := TtsTests
 
diff --git a/tests/WindowAnimationJank/Android.mk b/tests/WindowAnimationJank/Android.mk
index f356afb..8aac8a1 100644
--- a/tests/WindowAnimationJank/Android.mk
+++ b/tests/WindowAnimationJank/Android.mk
@@ -27,7 +27,6 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ub-uiautomator \
     ub-janktesthelper \
-    legacy-android-test \
     junit
 
 LOCAL_SDK_VERSION := current
diff --git a/tests/net/java/android/net/ip/IpManagerTest.java b/tests/net/java/android/net/ip/IpManagerTest.java
index 22d88fb..ebf121a 100644
--- a/tests/net/java/android/net/ip/IpManagerTest.java
+++ b/tests/net/java/android/net/ip/IpManagerTest.java
@@ -69,6 +69,8 @@
 
 /**
  * Tests for IpManager.
+ *
+ * TODO: Rename to IpClientTest.
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -111,6 +113,10 @@
         verify(mNMService, times(1)).registerObserver(arg.capture());
         mObserver = arg.getValue();
         reset(mNMService);
+        final LinkProperties emptyLp = new LinkProperties();
+        emptyLp.setInterfaceName(ifname);
+        verify(mCb, timeout(100)).onLinkPropertiesChange(eq(emptyLp));
+        reset(mCb);
         return ipm;
     }
 
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
index f849689..54776db 100644
--- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
+++ b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
@@ -18,10 +18,12 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.when;
 
 import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.Looper;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -42,14 +44,18 @@
     @Mock IpReachabilityMonitor.Callback mCallback;
     @Mock IpReachabilityMonitor.Dependencies mDependencies;
     @Mock SharedLog mLog;
+    Handler mHandler;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mLog.forSubComponent(anyString())).thenReturn(mLog);
+        mHandler = new Handler(Looper.getMainLooper());
     }
 
     IpReachabilityMonitor makeMonitor() {
-        return new IpReachabilityMonitor("fake0", 1, mLog, mCallback, null, mDependencies);
+        return new IpReachabilityMonitor(
+                "fake0", 1, mHandler, mLog, mCallback, null, mDependencies);
     }
 
     @Test
diff --git a/tests/net/java/android/net/netlink/NetlinkSocketTest.java b/tests/net/java/android/net/netlink/NetlinkSocketTest.java
index bd36bac8..11be40b 100644
--- a/tests/net/java/android/net/netlink/NetlinkSocketTest.java
+++ b/tests/net/java/android/net/netlink/NetlinkSocketTest.java
@@ -16,6 +16,8 @@
 
 package android.net.netlink;
 
+import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
+import static android.system.OsConstants.NETLINK_ROUTE;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -28,10 +30,12 @@
 import android.support.test.filters.SmallTest;
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
-import android.system.OsConstants;
+import android.system.Os;
 import android.util.Log;
+import libcore.io.IoUtils;
 
 import java.io.InterruptedIOException;
+import java.io.FileDescriptor;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 
@@ -46,29 +50,28 @@
 
     @Test
     public void testBasicWorkingGetNeighborsQuery() throws Exception {
-        NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
-        assertNotNull(s);
+        final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE);
+        assertNotNull(fd);
 
-        s.connectToKernel();
+        NetlinkSocket.connectToKernel(fd);
 
-        NetlinkSocketAddress localAddr = s.getLocalAddress();
+        final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
         assertNotNull(localAddr);
         assertEquals(0, localAddr.getGroupsMask());
         assertTrue(0 != localAddr.getPortId());
 
         final int TEST_SEQNO = 5;
-        final byte[] request = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
-        assertNotNull(request);
+        final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
+        assertNotNull(req);
 
         final long TIMEOUT = 500;
-        assertTrue(s.sendMessage(request, 0, request.length, TIMEOUT));
+        assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, TIMEOUT));
 
         int neighMessageCount = 0;
         int doneMessageCount = 0;
 
         while (doneMessageCount == 0) {
-            ByteBuffer response = null;
-            response = s.recvMessage(TIMEOUT);
+            ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT);
             assertNotNull(response);
             assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
             assertEquals(0, response.position());
@@ -100,30 +103,6 @@
         // TODO: make sure this test passes sanely in airplane mode.
         assertTrue(neighMessageCount > 0);
 
-        s.close();
-    }
-
-    @Test
-    public void testRepeatedCloseCallsAreQuiet() throws Exception {
-        // Create a working NetlinkSocket.
-        NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
-        assertNotNull(s);
-        s.connectToKernel();
-        NetlinkSocketAddress localAddr = s.getLocalAddress();
-        assertNotNull(localAddr);
-        assertEquals(0, localAddr.getGroupsMask());
-        assertTrue(0 != localAddr.getPortId());
-        // Close once.
-        s.close();
-        // Test that it is closed.
-        boolean expectedErrorSeen = false;
-        try {
-            localAddr = s.getLocalAddress();
-        } catch (ErrnoException e) {
-            expectedErrorSeen = true;
-        }
-        assertTrue(expectedErrorSeen);
-        // Close once more.
-        s.close();
+        IoUtils.closeQuietly(fd);
     }
 }
diff --git a/tests/net/java/android/net/util/BlockingSocketReaderTest.java b/tests/net/java/android/net/util/PacketReaderTest.java
similarity index 91%
rename from tests/net/java/android/net/util/BlockingSocketReaderTest.java
rename to tests/net/java/android/net/util/PacketReaderTest.java
index 29dfa4c..dced743 100644
--- a/tests/net/java/android/net/util/BlockingSocketReaderTest.java
+++ b/tests/net/java/android/net/util/PacketReaderTest.java
@@ -16,7 +16,7 @@
 
 package android.net.util;
 
-import static android.net.util.BlockingSocketReader.DEFAULT_RECV_BUF_SIZE;
+import static android.net.util.PacketReader.DEFAULT_RECV_BUF_SIZE;
 import static android.system.OsConstants.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -53,13 +53,13 @@
 import libcore.io.IoBridge;
 
 /**
- * Tests for BlockingSocketReader.
+ * Tests for PacketReader.
  *
  * @hide
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class BlockingSocketReaderTest {
+public class PacketReaderTest {
     static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress();
     static final StructTimeval TIMEO = StructTimeval.fromMillis(500);
 
@@ -69,9 +69,9 @@
     protected byte[] mLastRecvBuf;
     protected boolean mStopped;
     protected HandlerThread mHandlerThread;
-    protected BlockingSocketReader mReceiver;
+    protected PacketReader mReceiver;
 
-    class UdpLoopbackReader extends BlockingSocketReader {
+    class UdpLoopbackReader extends PacketReader {
         public UdpLoopbackReader(Handler h) {
             super(h);
         }
@@ -121,7 +121,7 @@
         mLastRecvBuf = null;
         mStopped = false;
 
-        mHandlerThread = new HandlerThread(BlockingSocketReaderTest.class.getSimpleName());
+        mHandlerThread = new HandlerThread(PacketReaderTest.class.getSimpleName());
         mHandlerThread.start();
     }
 
@@ -188,8 +188,8 @@
         mReceiver = null;
     }
 
-    class NullBlockingSocketReader extends BlockingSocketReader {
-        public NullBlockingSocketReader(Handler h, int recvbufsize) {
+    class NullPacketReader extends PacketReader {
+        public NullPacketReader(Handler h, int recvbufsize) {
             super(h, recvbufsize);
         }
 
@@ -202,7 +202,7 @@
         final Handler h = mHandlerThread.getThreadHandler();
 
         for (int i : new int[]{-1, 0, 1, DEFAULT_RECV_BUF_SIZE-1}) {
-            final BlockingSocketReader b = new NullBlockingSocketReader(h, i);
+            final PacketReader b = new NullPacketReader(h, i);
             assertEquals(DEFAULT_RECV_BUF_SIZE, b.recvBufSize());
         }
     }
diff --git a/tests/permission/Android.mk b/tests/permission/Android.mk
index 54688c8..7f81d9a3 100644
--- a/tests/permission/Android.mk
+++ b/tests/permission/Android.mk
@@ -7,8 +7,8 @@
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.test.base
+LOCAL_STATIC_JAVA_LIBRARIES := junit
 LOCAL_PACKAGE_NAME := FrameworkPermissionTests
 
 include $(BUILD_PACKAGE)
diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java
index 498d517..ffe7219 100644
--- a/tests/testables/src/android/testing/TestableContext.java
+++ b/tests/testables/src/android/testing/TestableContext.java
@@ -25,6 +25,7 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.UserHandle;
@@ -69,6 +70,7 @@
     private LeakCheck.Tracker mService;
     private LeakCheck.Tracker mComponent;
     private TestableResources mTestableResources;
+    private TestablePermissions mTestablePermissions;
 
     public TestableContext(Context base) {
         this(base, null);
@@ -302,6 +304,159 @@
         super.unregisterComponentCallbacks(callback);
     }
 
+    public TestablePermissions getTestablePermissions() {
+        if (mTestablePermissions == null) {
+            mTestablePermissions = new TestablePermissions();
+        }
+        return mTestablePermissions;
+    }
+
+    @Override
+    public int checkCallingOrSelfPermission(String permission) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
+            return mTestablePermissions.check(permission);
+        }
+        return super.checkCallingOrSelfPermission(permission);
+    }
+
+    @Override
+    public int checkCallingPermission(String permission) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
+            return mTestablePermissions.check(permission);
+        }
+        return super.checkCallingPermission(permission);
+    }
+
+    @Override
+    public int checkPermission(String permission, int pid, int uid) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
+            return mTestablePermissions.check(permission);
+        }
+        return super.checkPermission(permission, pid, uid);
+    }
+
+    @Override
+    public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
+            return mTestablePermissions.check(permission);
+        }
+        return super.checkPermission(permission, pid, uid, callerToken);
+    }
+
+    @Override
+    public int checkSelfPermission(String permission) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
+            return mTestablePermissions.check(permission);
+        }
+        return super.checkSelfPermission(permission);
+    }
+
+    @Override
+    public void enforceCallingOrSelfPermission(String permission, String message) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
+            mTestablePermissions.enforce(permission);
+        } else {
+            super.enforceCallingOrSelfPermission(permission, message);
+        }
+    }
+
+    @Override
+    public void enforceCallingPermission(String permission, String message) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
+            mTestablePermissions.enforce(permission);
+        } else {
+            super.enforceCallingPermission(permission, message);
+        }
+    }
+
+    @Override
+    public void enforcePermission(String permission, int pid, int uid, String message) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
+            mTestablePermissions.enforce(permission);
+        } else {
+            super.enforcePermission(permission, pid, uid, message);
+        }
+    }
+
+    @Override
+    public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
+            return mTestablePermissions.check(uri, modeFlags);
+        }
+        return super.checkCallingOrSelfUriPermission(uri, modeFlags);
+    }
+
+    @Override
+    public int checkCallingUriPermission(Uri uri, int modeFlags) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
+            return mTestablePermissions.check(uri, modeFlags);
+        }
+        return super.checkCallingUriPermission(uri, modeFlags);
+    }
+
+    @Override
+    public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
+            mTestablePermissions.enforce(uri, modeFlags);
+        } else {
+            super.enforceCallingOrSelfUriPermission(uri, modeFlags, message);
+        }
+    }
+
+    @Override
+    public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
+            return mTestablePermissions.check(uri, modeFlags);
+        }
+        return super.checkUriPermission(uri, pid, uid, modeFlags);
+    }
+
+    @Override
+    public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
+            return mTestablePermissions.check(uri, modeFlags);
+        }
+        return super.checkUriPermission(uri, pid, uid, modeFlags, callerToken);
+    }
+
+    @Override
+    public int checkUriPermission(Uri uri, String readPermission, String writePermission, int pid,
+            int uid, int modeFlags) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
+            return mTestablePermissions.check(uri, modeFlags);
+        }
+        return super.checkUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags);
+    }
+
+    @Override
+    public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
+            mTestablePermissions.enforce(uri, modeFlags);
+        } else {
+            super.enforceCallingUriPermission(uri, modeFlags, message);
+        }
+    }
+
+    @Override
+    public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
+            mTestablePermissions.enforce(uri, modeFlags);
+        } else {
+            super.enforceUriPermission(uri, pid, uid, modeFlags, message);
+        }
+    }
+
+    @Override
+    public void enforceUriPermission(Uri uri, String readPermission, String writePermission,
+            int pid, int uid, int modeFlags, String message) {
+        if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
+            mTestablePermissions.enforce(uri, modeFlags);
+        } else {
+            super.enforceUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags,
+                    message);
+        }
+    }
+
     @Override
     public Statement apply(Statement base, Description description) {
         return new TestWatcher() {
diff --git a/tests/testables/src/android/testing/TestablePermissions.java b/tests/testables/src/android/testing/TestablePermissions.java
new file mode 100644
index 0000000..4f009e4
--- /dev/null
+++ b/tests/testables/src/android/testing/TestablePermissions.java
@@ -0,0 +1,80 @@
+/*
+ * 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 android.testing;
+
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.util.ArrayMap;
+
+/**
+ * Simple class for simulating basic permission states for tests.
+ *
+ * All enforce* and check* calls on TestableContext are considered the same
+ * and routed through the same check here. If more fine-grained control is
+ * required, then either a sub-class or spy on TestableContext is recommended.
+ */
+public class TestablePermissions {
+
+    private final ArrayMap<String, Integer> mPermissions = new ArrayMap<>();
+    private final ArrayMap<Uri, Integer> mUris = new ArrayMap<>();
+
+    /**
+     * Sets the return value for checkPermission* calls on TestableContext
+     * for a specific permission value. For all enforcePermission* calls
+     * they will throw a security exception if value != PERMISSION_GRANTED.
+     */
+    public void setPermission(String permission, int value) {
+        mPermissions.put(permission, value);
+    }
+
+    /**
+     * Sets the return value for checkUriPermission* calls on TestableContext
+     * for a specific permission value. For all enforceUriPermission* calls
+     * they will throw a security exception if value != PERMISSION_GRANTED.
+     */
+    public void setPermission(Uri uri, int value) {
+        // TODO: Support modeFlags
+        mUris.put(uri, value);
+    }
+
+    boolean wantsCall(String permission) {
+        return mPermissions.containsKey(permission);
+    }
+
+    boolean wantsCall(Uri uri) {
+        return mUris.containsKey(uri);
+    }
+
+    int check(String permission) {
+        return mPermissions.get(permission);
+    }
+
+    int check(Uri uri, int modeFlags) {
+        // TODO: Support modeFlags
+        return mUris.get(uri);
+    }
+
+    public void enforce(String permission) {
+        if (check(permission) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException();
+        }
+    }
+
+    public void enforce(Uri uri, int modeFlags) {
+        if (check(uri, modeFlags) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException();
+        }
+    }
+}
diff --git a/tests/testables/tests/Android.mk b/tests/testables/tests/Android.mk
index 16fe535..6e20d797 100644
--- a/tests/testables/tests/Android.mk
+++ b/tests/testables/tests/Android.mk
@@ -28,10 +28,9 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     mockito-target-minus-junit4 \
-    legacy-android-test \
 	testables
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
 
 LOCAL_CERTIFICATE := platform
 
diff --git a/tests/testables/tests/src/android/testing/TestablePermissionsTest.java b/tests/testables/tests/src/android/testing/TestablePermissionsTest.java
new file mode 100644
index 0000000..c56146e
--- /dev/null
+++ b/tests/testables/tests/src/android/testing/TestablePermissionsTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.testing;
+
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest.permission;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.testing.TestableLooper.RunWithLooper;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class TestablePermissionsTest {
+
+    private static final Uri URI_1 = Uri.parse("content://my.authority/path1");
+    private static final Uri URI_2 = Uri.parse("content://my.authority/path2");
+
+    @Rule
+    public TestableContext mContext = new TestableContext(InstrumentationRegistry.getContext());
+
+    @Test
+    public void testCheck() {
+        mContext.getTestablePermissions().setPermission(permission.INTERACT_ACROSS_USERS,
+                PERMISSION_GRANTED);
+        mContext.getTestablePermissions().setPermission(permission.INTERACT_ACROSS_USERS_FULL,
+                PERMISSION_DENIED);
+        assertEquals(PERMISSION_GRANTED,
+                mContext.checkPermission(permission.INTERACT_ACROSS_USERS, 0, 0));
+        assertEquals(PERMISSION_DENIED,
+                mContext.checkPermission(permission.INTERACT_ACROSS_USERS_FULL, 0, 0));
+    }
+
+    @Test
+    public void testCheckUri() {
+        mContext.getTestablePermissions().setPermission(URI_1, PERMISSION_GRANTED);
+        mContext.getTestablePermissions().setPermission(URI_2, PERMISSION_DENIED);
+
+        assertEquals(PERMISSION_GRANTED, mContext.checkUriPermission(URI_1, 0, 0, 0));
+        assertEquals(PERMISSION_DENIED, mContext.checkUriPermission(URI_2, 0, 0, 0));
+    }
+
+    @Test
+    public void testEnforceNoException() {
+        mContext.getTestablePermissions().setPermission(permission.INTERACT_ACROSS_USERS,
+                PERMISSION_GRANTED);
+        mContext.enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS, "");
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testEnforceWithException() {
+        mContext.getTestablePermissions().setPermission(permission.INTERACT_ACROSS_USERS,
+                PERMISSION_DENIED);
+        mContext.enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS, "");
+    }
+
+    @Test
+    public void testEnforceUriNoException() {
+        mContext.getTestablePermissions().setPermission(URI_1, PERMISSION_GRANTED);
+        mContext.enforceUriPermission(URI_1, 0, 0, 0, "");
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testEnforceUriWithException() {
+        mContext.getTestablePermissions().setPermission(URI_1, PERMISSION_DENIED);
+        mContext.enforceUriPermission(URI_1, 0, 0, 0, "");
+    }
+
+}
\ No newline at end of file