Merge "Add SliceSpec to Slice API"
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 45ae56f..1b2f9da 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -21,6 +21,7 @@
     src/statsd_config.proto \
     src/atoms_copy.proto \
     src/anomaly/AnomalyMonitor.cpp \
+    src/anomaly/AnomalyTracker.cpp \
     src/condition/CombinationConditionTracker.cpp \
     src/condition/condition_util.cpp \
     src/condition/SimpleConditionTracker.cpp \
@@ -39,7 +40,6 @@
     src/matchers/CombinationLogMatchingTracker.cpp \
     src/matchers/matcher_util.cpp \
     src/matchers/SimpleLogMatchingTracker.cpp \
-    src/anomaly/DiscreteAnomalyTracker.cpp \
     src/metrics/MetricProducer.cpp \
     src/metrics/EventMetricProducer.cpp \
     src/metrics/CountMetricProducer.cpp \
@@ -178,4 +178,3 @@
 statsd_common_c_includes:=
 
 include $(BUILD_NATIVE_TEST)
-
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index abd2a35..fcb5107 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -50,13 +50,24 @@
 const int FIELD_ID_NAME = 2;
 
 StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
+                                     const sp<AnomalyMonitor>& anomalyMonitor,
                                      const std::function<void(const ConfigKey&)>& sendBroadcast)
-    : mUidMap(uidMap), mSendBroadcast(sendBroadcast) {
+    : mUidMap(uidMap), mAnomalyMonitor(anomalyMonitor), mSendBroadcast(sendBroadcast) {
 }
 
 StatsLogProcessor::~StatsLogProcessor() {
 }
 
+void StatsLogProcessor::onAnomalyAlarmFired(
+        const uint64_t timestampNs,
+        unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet) {
+    for (const auto& anomaly : anomalySet) {
+        for (const auto& itr : mMetricsManagers) {
+            itr.second->onAnomalyAlarmFired(timestampNs, anomaly);
+        }
+    }
+}
+
 // TODO: what if statsd service restarts? How do we know what logs are already processed before?
 void StatsLogProcessor::OnLogEvent(const LogEvent& msg) {
     // pass the event to metrics managers.
@@ -93,6 +104,7 @@
     unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(config);
     if (newMetricsManager->isConfigValid()) {
         mUidMap->OnConfigUpdated(key);
+        newMetricsManager->setAnomalyMonitor(mAnomalyMonitor);
         mMetricsManagers[key] = std::move(newMetricsManager);
         // Why doesn't this work? mMetricsManagers.insert({key, std::move(newMetricsManager)});
         ALOGD("StatsdConfig valid");
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 2091774..510dc51 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -32,7 +32,7 @@
 
 class StatsLogProcessor : public ConfigListener {
 public:
-    StatsLogProcessor(const sp<UidMap>& uidMap,
+    StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor,
                       const std::function<void(const ConfigKey&)>& sendBroadcast);
     virtual ~StatsLogProcessor();
 
@@ -42,8 +42,11 @@
     void OnConfigRemoved(const ConfigKey& key);
 
     size_t GetMetricsSize(const ConfigKey& key);
-
+ 
     void onDumpReport(const ConfigKey& key, vector<uint8_t>* outData);
+    void onAnomalyAlarmFired(
+            const uint64_t timestampNs,
+            unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet);
 
 private:
     mutable mutex mBroadcastTimesMutex;
@@ -54,6 +57,8 @@
 
     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
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 31d32b4..1a056df 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -73,7 +73,7 @@
 {
     mUidMap = new UidMap();
     mConfigManager = new ConfigManager();
-    mProcessor = new StatsLogProcessor(mUidMap, [this](const ConfigKey& key) {
+    mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, [this](const ConfigKey& key) {
         auto sc = getStatsCompanionService();
         auto receiver = mConfigManager->GetConfigReceiver(key);
         if (sc == nullptr) {
@@ -554,7 +554,10 @@
 
     if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired succeeded");
     // TODO: check through all counters/timers and see if an anomaly has indeed occurred.
-
+    uint64_t currentTimeNs = time(nullptr) * NS_PER_SEC;
+    std::unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet =
+            mAnomalyMonitor->onAlarmFired(currentTimeNs);
+    mProcessor->onAnomalyAlarmFired(currentTimeNs, anomalySet);
     return Status::ok();
 }
 
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
index 7a46410..da52a9d 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
@@ -129,6 +129,11 @@
     return ((int64_t)timeSec) * 1000;
 }
 
+unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> AnomalyMonitor::onAlarmFired(
+        uint64_t timestampNs) {
+    return popSoonerThan(static_cast<uint32_t>(timestampNs));
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.h b/cmds/statsd/src/anomaly/AnomalyMonitor.h
index d9207e9..0bd5055 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.h
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANOMALY_MONITOR_H
-#define ANOMALY_MONITOR_H
+#pragma once
 
 #include "anomaly/indexed_priority_queue.h"
 
@@ -23,6 +22,8 @@
 #include <utils/RefBase.h>
 
 #include <queue>
+#include <set>
+#include <unordered_map>
 #include <unordered_set>
 #include <vector>
 
@@ -114,6 +115,8 @@
         return mRegisteredAlarmTimeSec;
     }
 
+    unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> onAlarmFired(uint64_t timestampNs);
+
 private:
     std::mutex mLock;
 
@@ -154,5 +157,3 @@
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
-
-#endif  // ANOMALY_MONITOR_H
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
new file mode 100644
index 0000000..0904a04
--- /dev/null
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "AnomalyTracker.h"
+
+#include <time.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+AnomalyTracker::AnomalyTracker(const Alert& alert, const int64_t& bucketSizeNs)
+    : mAlert(alert),
+      mBucketSizeNs(bucketSizeNs),
+      mNumOfPastPackets(mAlert.number_of_buckets() - 1) {
+    VLOG("AnomalyTracker() called");
+    if (mAlert.number_of_buckets() <= 0) {
+        ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
+              (long long)mAlert.number_of_buckets());
+        return;
+    }
+    if (mBucketSizeNs <= 0) {
+        ALOGE("Cannot create DiscreteAnomalyTracker with bucket size %lld ",
+              (long long)mBucketSizeNs);
+        return;
+    }
+    if (!mAlert.has_trigger_if_sum_gt()) {
+        ALOGE("Cannot create DiscreteAnomalyTracker without threshold");
+        return;
+    }
+    reset(); // initialization
+}
+
+AnomalyTracker::~AnomalyTracker() {
+    VLOG("~AnomalyTracker() called");
+    stopAllAlarms();
+}
+
+void AnomalyTracker::reset() {
+    VLOG("reset() called.");
+    stopAllAlarms();
+    mPastBuckets.clear();
+    // Excludes the current bucket.
+    mPastBuckets.resize(mNumOfPastPackets);
+    mSumOverPastBuckets.clear();
+    mMostRecentBucketNum = -1;
+    mLastAlarmTimestampNs = -1;
+}
+
+size_t AnomalyTracker::index(int64_t bucketNum) const {
+    return bucketNum % mNumOfPastPackets;
+}
+
+void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
+    VLOG("addPastBucket() called.");
+    if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastPackets) {
+        ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
+        return;
+    }
+
+    // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
+    // mSumOverPastBuckets.
+    if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastPackets) {
+        mPastBuckets.clear();
+        mPastBuckets.resize(mNumOfPastPackets);
+        mSumOverPastBuckets.clear();
+    } else {
+        for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastPackets + 1));
+             i <= latestPastBucketNum - mNumOfPastPackets; i++) {
+            const int idx = index(i);
+            subtractBucketFromSum(mPastBuckets[idx]);
+            mPastBuckets[idx] = nullptr;  // release (but not clear) the old bucket.
+        }
+    }
+
+    // It is an update operation.
+    if (latestPastBucketNum <= mMostRecentBucketNum &&
+        latestPastBucketNum > mMostRecentBucketNum - mNumOfPastPackets) {
+        subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
+    }
+}
+
+void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
+                                   const int64_t& bucketNum) {
+    flushPastBuckets(bucketNum);
+
+    auto& bucket = mPastBuckets[index(bucketNum)];
+    if (bucket == nullptr) {
+        bucket = std::make_shared<DimToValMap>();
+    }
+    bucket->insert({key, bucketValue});
+    addBucketToSum(bucket);
+    mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
+}
+
+void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucketValues,
+                                   const int64_t& bucketNum) {
+    VLOG("addPastBucket() called.");
+    flushPastBuckets(bucketNum);
+    // Replace the oldest bucket with the new bucket we are adding.
+    mPastBuckets[index(bucketNum)] = bucketValues;
+    addBucketToSum(bucketValues);
+    mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
+}
+
+void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
+    if (bucket == nullptr) {
+        return;
+    }
+    // For each dimension present in the bucket, subtract its value from its corresponding sum.
+    for (const auto& keyValuePair : *bucket) {
+        auto itr = mSumOverPastBuckets.find(keyValuePair.first);
+        if (itr == mSumOverPastBuckets.end()) {
+            continue;
+        }
+        itr->second -= keyValuePair.second;
+        // TODO: No need to look up the object twice like this. Use a var.
+        if (itr->second == 0) {
+            mSumOverPastBuckets.erase(itr);
+        }
+    }
+}
+
+void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
+    if (bucket == nullptr) {
+        return;
+    }
+    // For each dimension present in the bucket, add its value to its corresponding sum.
+    for (const auto& keyValuePair : *bucket) {
+        mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
+    }
+}
+
+int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
+                                           const int64_t& bucketNum) const {
+    const auto& bucket = mPastBuckets[index(bucketNum)];
+    if (bucket == nullptr) {
+        return 0;
+    }
+    const auto& itr = bucket->find(key);
+    return itr == bucket->end() ? 0 : itr->second;
+}
+
+int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
+    const auto& itr = mSumOverPastBuckets.find(key);
+    if (itr != mSumOverPastBuckets.end()) {
+        return itr->second;
+    }
+    return 0;
+}
+
+bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
+                                   const DimToValMap& currentBucket) {
+    if (currentBucketNum > mMostRecentBucketNum + 1) {
+        addPastBucket(nullptr, currentBucketNum - 1);
+    }
+    for (auto itr = currentBucket.begin(); itr != currentBucket.end(); itr++) {
+        if (itr->second + getSumOverPastBuckets(itr->first) > mAlert.trigger_if_sum_gt()) {
+            return true;
+        }
+    }
+    // In theory, we also need to check the dimsions not in the current bucket. In single-thread
+    // mode, usually we could avoid the following loops.
+    for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
+        if (itr->second > mAlert.trigger_if_sum_gt()) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
+                                   const int64_t& currentBucketValue) {
+    if (currentBucketNum > mMostRecentBucketNum + 1) {
+        addPastBucket(key, 0, currentBucketNum - 1);
+    }
+    return getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
+}
+
+void AnomalyTracker::declareAnomaly(const uint64_t& timestamp) {
+    if (mLastAlarmTimestampNs >= 0 &&
+        timestamp - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC) {
+        VLOG("Skipping anomaly check since within refractory period");
+        return;
+    }
+    // TODO(guardrail): Consider guarding against too short refractory periods.
+    mLastAlarmTimestampNs = timestamp;
+
+    if (mAlert.has_incidentd_details()) {
+        // TODO: Can construct a name based on the criteria (and/or relay the criteria).
+        ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
+        // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
+    } else {
+        ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
+    }
+}
+
+void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
+                                                  const uint64_t& timestamp) {
+    auto itr = mAlarms.find(dimensionKey);
+    if (itr == mAlarms.end()) {
+        return;
+    }
+
+    if (itr->second != nullptr &&
+        static_cast<uint32_t>(timestamp / NS_PER_SEC) >= itr->second->timestampSec) {
+        declareAnomaly(timestamp);
+        stopAlarm(dimensionKey);
+    }
+}
+
+void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
+                                             const int64_t& currBucketNum,
+                                             const HashableDimensionKey& key,
+                                             const int64_t& currentBucketValue) {
+    if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
+        declareAnomaly(timestamp);
+    }
+}
+
+void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
+                                             const int64_t& currBucketNum,
+                                             const DimToValMap& currentBucket) {
+    if (detectAnomaly(currBucketNum, currentBucket)) {
+        declareAnomaly(timestamp);
+    }
+}
+
+void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
+                                const uint64_t& timestamp) {
+    sp<const AnomalyAlarm> alarm = new AnomalyAlarm{static_cast<uint32_t>(timestamp / NS_PER_SEC)};
+    mAlarms.insert({dimensionKey, alarm});
+    if (mAnomalyMonitor != nullptr) {
+        mAnomalyMonitor->add(alarm);
+    }
+}
+
+void AnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
+    auto itr = mAlarms.find(dimensionKey);
+    if (itr != mAlarms.end()) {
+        mAlarms.erase(dimensionKey);
+    }
+    if (mAnomalyMonitor != nullptr) {
+        mAnomalyMonitor->remove(itr->second);
+    }
+}
+
+void AnomalyTracker::stopAllAlarms() {
+    std::set<HashableDimensionKey> keys;
+    for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
+        keys.insert(itr->first);
+    }
+    for (auto key : keys) {
+        stopAlarm(key);
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
new file mode 100644
index 0000000..ce6c995
--- /dev/null
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gtest/gtest_prod.h>
+#include "AnomalyMonitor.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // Alert
+#include "stats_util.h"  // HashableDimensionKey and DimToValMap
+
+#include <memory> // unique_ptr
+#include <stdlib.h>
+#include <utils/RefBase.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::unordered_map;
+using std::shared_ptr;
+
+// This anomaly track assmues that all values are non-negative.
+class AnomalyTracker : public virtual RefBase {
+public:
+    AnomalyTracker(const Alert& alert, const int64_t& bucketSizeNs);
+
+    virtual ~AnomalyTracker();
+
+    // Adds a bucket.
+    // Bucket index starts from 0.
+    void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum);
+    void addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
+                       const int64_t& bucketNum);
+
+    // Returns true if detected anomaly for the existing buckets on one or more dimension keys.
+    bool detectAnomaly(const int64_t& currBucketNum, const DimToValMap& currentBucket);
+    bool detectAnomaly(const int64_t& currBucketNum, const HashableDimensionKey& key,
+                       const int64_t& currentBucketValue);
+
+    // Informs incidentd about the detected alert.
+    void declareAnomaly(const uint64_t& timestamp);
+
+    // Detects the alert and informs the incidentd when applicable.
+    void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
+                                 const DimToValMap& currentBucket);
+    void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
+                                 const HashableDimensionKey& key,
+                                 const int64_t& currentBucketValue);
+
+    // Starts the alarm at the given timestamp.
+    void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime);
+    // Stops the alarm.
+    void stopAlarm(const HashableDimensionKey& dimensionKey);
+
+    // Stop all the alarms owned by this tracker.
+    void stopAllAlarms();
+
+    // Init the anmaly monitor which is shared across anomaly trackers.
+    inline void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
+        mAnomalyMonitor = anomalyMonitor;
+    }
+
+    // Declares the anomaly when the alarm expired given the current timestamp.
+    void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
+                                      const uint64_t& timestamp);
+
+    // Helper function to return the sum value of past buckets at given dimension.
+    int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const;
+
+    // Helper function to return the value for a past bucket.
+    int64_t getPastBucketValue(const HashableDimensionKey& key, const int64_t& bucketNum) const;
+
+    // Returns the anomaly threshold.
+    inline int64_t getAnomalyThreshold() const {
+        return mAlert.trigger_if_sum_gt();
+    }
+
+    // Helper function to return the last alarm timestamp.
+    inline int64_t getLastAlarmTimestampNs() const {
+        return mLastAlarmTimestampNs;
+    }
+
+    inline int getNumOfPastPackets() const {
+        return mNumOfPastPackets;
+    }
+
+protected:
+    void flushPastBuckets(const int64_t& currBucketNum);
+    // statsd_config.proto Alert message that defines this tracker.
+    const Alert mAlert;
+
+    // Bucket duration in ns.
+    int64_t mBucketSizeNs = 0;
+
+    // The number of past packets to track in the anomaly detection.
+    int mNumOfPastPackets = 0;
+
+    // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
+    // are still active.
+    std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms;
+
+    // Anomaly alarm monitor.
+    sp<AnomalyMonitor> mAnomalyMonitor;
+
+    // The exisiting bucket list.
+    std::vector<shared_ptr<DimToValMap>> mPastBuckets;
+
+    // Sum over all existing buckets cached in mPastBuckets.
+    DimToValMap mSumOverPastBuckets;
+
+    // The bucket number of the last added bucket.
+    int64_t mMostRecentBucketNum = -1;
+
+    // The timestamp when the last anomaly was declared.
+    int64_t mLastAlarmTimestampNs = -1;
+
+    // Add the information in the given bucket to mSumOverPastBuckets.
+    void addBucketToSum(const shared_ptr<DimToValMap>& bucket);
+
+    // Subtract the information in the given bucket from mSumOverPastBuckets
+    // and remove any items with value 0.
+    void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
+
+    // Calculates the corresponding bucket index within the circular array.
+    size_t index(int64_t bucketNum) const;
+
+    // Resets all data. For use when all the data gets stale.
+    void reset();
+
+    FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
+    FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
+    FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
+    FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
+    FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+    FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
+    FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp
deleted file mode 100644
index 6492177..0000000
--- a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define DEBUG true  // STOPSHIP if true
-#include "Log.h"
-
-#include "DiscreteAnomalyTracker.h"
-
-#include <time.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-DiscreteAnomalyTracker::DiscreteAnomalyTracker(const Alert& alert) : mAlert(alert) {
-    VLOG("DiscreteAnomalyTracker() called");
-    if (mAlert.number_of_buckets() <= 0) {
-        ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
-              (long long)mAlert.number_of_buckets());
-        return;
-    }
-    mPastBuckets.resize(mAlert.number_of_buckets());
-    reset(); // initialization
-}
-
-DiscreteAnomalyTracker::~DiscreteAnomalyTracker() {
-    VLOG("~DiscreteAnomalyTracker() called");
-}
-
-void DiscreteAnomalyTracker::reset() {
-    VLOG("reset() called.");
-    mPastBuckets.clear();
-    mPastBuckets.resize(mAlert.number_of_buckets());
-    mSumOverPastBuckets.clear();
-    mCurrentBucketIndex = -1;
-    mLastAlarmAtBucketIndex = -1;
-    mAnomalyDeclared = 0;
-}
-
-size_t DiscreteAnomalyTracker::index(int64_t bucketNum) {
-    return bucketNum % mAlert.number_of_buckets();
-}
-
-void DiscreteAnomalyTracker::addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues,
-                                               int64_t bucketIndex) {
-    VLOG("addPastBucket() called.");
-    if (bucketIndex <= mCurrentBucketIndex - mAlert.number_of_buckets()) {
-        ALOGE("Cannot add a past bucket %lld units in past", (long long)bucketIndex);
-        return;
-    }
-
-    // Empty out old mPastBuckets[i] values and update mSumOverPastBuckets.
-    if (bucketIndex - mCurrentBucketIndex >= mAlert.number_of_buckets()) {
-        mPastBuckets.clear();
-        mPastBuckets.resize(mAlert.number_of_buckets());
-        mSumOverPastBuckets.clear();
-    } else {
-        for (int64_t i = std::max(
-                     0LL, (long long)(mCurrentBucketIndex - mAlert.number_of_buckets() + 1));
-             i < bucketIndex - mAlert.number_of_buckets(); i++) {
-            const int idx = index(i);
-            subtractBucketFromSum(mPastBuckets[idx]);
-            mPastBuckets[idx] = nullptr;  // release (but not clear) the old bucket.
-        }
-    }
-    subtractBucketFromSum(mPastBuckets[index(bucketIndex)]);
-    mPastBuckets[index(bucketIndex)] = nullptr;  // release (but not clear) the old bucket.
-
-    // Replace the oldest bucket with the new bucket we are adding.
-    mPastBuckets[index(bucketIndex)] = BucketValues;
-    addBucketToSum(BucketValues);
-
-    mCurrentBucketIndex = std::max(mCurrentBucketIndex, bucketIndex);
-}
-
-void DiscreteAnomalyTracker::subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket) {
-    if (bucket == nullptr) {
-        return;
-    }
-    // For each dimension present in the bucket, subtract its value from its corresponding sum.
-    for (const auto& keyValuePair : *bucket) {
-        auto itr = mSumOverPastBuckets.find(keyValuePair.first);
-        if (itr == mSumOverPastBuckets.end()) {
-            continue;
-        }
-        itr->second -= keyValuePair.second;
-        // TODO: No need to look up the object twice like this. Use a var.
-        if (itr->second == 0) {
-            mSumOverPastBuckets.erase(itr);
-        }
-    }
-}
-
-void DiscreteAnomalyTracker::addBucketToSum(const shared_ptr<const DimToValMap>& bucket) {
-    if (bucket == nullptr) {
-        return;
-    }
-    // For each dimension present in the bucket, add its value to its corresponding sum.
-    for (const auto& keyValuePair : *bucket) {
-        mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
-    }
-}
-
-bool DiscreteAnomalyTracker::detectAnomaly() {
-    for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
-        if (mAlert.has_trigger_if_sum_gt() && itr->second > mAlert.trigger_if_sum_gt()) {
-            return true;
-        }
-    }
-    return false;
-}
-
-void DiscreteAnomalyTracker::declareAndDeclareAnomaly() {
-    if (detectAnomaly()) {
-        declareAnomaly();
-    }
-}
-
-void DiscreteAnomalyTracker::declareAnomaly() {
-    if (mLastAlarmAtBucketIndex >= 0 && mCurrentBucketIndex - mLastAlarmAtBucketIndex <=
-                                        (long long)mAlert.refractory_period_in_buckets()) {
-        VLOG("Skipping anomaly check since within refractory period");
-        return;
-    }
-    mAnomalyDeclared++;
-    // TODO(guardrail): Consider guarding against too short refractory periods.
-    mLastAlarmAtBucketIndex = mCurrentBucketIndex;
-
-    if (mAlert.has_incidentd_details()) {
-        // TODO: Can construct a name based on the criteria (and/or relay the criteria).
-        ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
-        // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
-    } else {
-        ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
-    }
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h
deleted file mode 100644
index ed7d5d7..0000000
--- a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <gtest/gtest_prod.h>
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
-#include "stats_util.h" // HashableDimensionKey and DimToValMap
-
-#include <memory> // unique_ptr
-#include <stdlib.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using std::unordered_map;
-using std::shared_ptr;
-
-// This anomaly track assmues that all values are non-negative.
-class DiscreteAnomalyTracker {
- public:
-    DiscreteAnomalyTracker(const Alert& alert);
-
-    virtual ~DiscreteAnomalyTracker();
-
-    // Adds a new bucket or updates an existing bucket.
-    // Bucket index starts from 0.
-    void addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues, int64_t bucketIndex);
-
-    // Returns true if detected anomaly for the existing buckets on one or more dimension keys.
-    bool detectAnomaly();
-
-    // Informs incidentd about the detected alert.
-    void declareAnomaly();
-
-    // Detects the alert and informs the incidentd when applicable.
-    void declareAndDeclareAnomaly();
-
-private:
-    // statsd_config.proto Alert message that defines this tracker.
-    const Alert mAlert;
-
-    // The exisiting bucket list.
-    std::vector<shared_ptr<const DimToValMap>> mPastBuckets;
-
-    // Sum over all existing buckets cached in mPastBuckets.
-    DimToValMap mSumOverPastBuckets;
-
-    // Current bucket index of the current anomaly detection window. Bucket index starts from 0.
-    int64_t mCurrentBucketIndex = -1;
-
-    // The bucket index when the last anomaly was declared.
-    int64_t mLastAlarmAtBucketIndex = -1;
-
-    // The total number of declared anomalies.
-    int64_t mAnomalyDeclared = 0;
-
-    // Add the information in the given bucket to mSumOverPastBuckets.
-    void addBucketToSum(const shared_ptr<const DimToValMap>& bucket);
-
-    // Subtract the information in the given bucket from mSumOverPastBuckets
-    // and remove any items with value 0.
-    void subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket);
-
-    // Calculates the corresponding bucket index within the circular array.
-    size_t index(int64_t bucketNum);
-
-    // Resets all data. For use when all the data gets stale.
-    void reset();
-
-    FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
-    FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/anomaly/indexed_priority_queue.h b/cmds/statsd/src/anomaly/indexed_priority_queue.h
index 1a2e9c2..4982d4b 100644
--- a/cmds/statsd/src/anomaly/indexed_priority_queue.h
+++ b/cmds/statsd/src/anomaly/indexed_priority_queue.h
@@ -16,8 +16,6 @@
 
 #pragma once
 
-#include "Log.h"
-
 #include <utils/RefBase.h>
 #include <unordered_map>
 #include <vector>
@@ -28,7 +26,7 @@
 namespace os {
 namespace statsd {
 
-/** Defines a hash function for sp<AA>, returning the hash of the underlying pointer. */
+/** Defines a hash function for sp<const AA>, returning the hash of the underlying pointer. */
 template <class AA>
 struct SpHash {
     size_t operator()(const sp<const AA>& k) const {
@@ -39,7 +37,7 @@
 /**
  * Min priority queue for generic type AA.
  * Unlike a regular priority queue, this class is also capable of removing interior elements.
- * @tparam Comparator must implement [bool operator()(sp<const AA> a, sp<const AA> b)], returning
+ * @tparam Comparator must implement [bool operator()(sp< AA> a, sp< AA> b)], returning
  *    whether a should be closer to the top of the queue than b.
  */
 template <class AA, class Comparator>
@@ -104,7 +102,6 @@
     if (!contains(a)) return;
     size_t idx = indices[a];
     if (idx >= pq.size()) {
-        ALOGE("indexed_priority_queue: Invalid index in map of indices.");
         return;
     }
     if (idx == size()) {  // if a is the last element, i.e. at index idx == size() == (pq.size()-1)
@@ -193,7 +190,6 @@
 template <class AA, class Comparator>
 bool indexed_priority_queue<AA, Comparator>::higher(size_t idx1, size_t idx2) const {
     if (!(0u < idx1 && idx1 < pq.size() && 0u < idx2 && idx2 < pq.size())) {
-        ALOGE("indexed_priority_queue: Attempting to access invalid index");
         return false;  // got to do something.
     }
     return Comparator()(pq[idx1], pq[idx2]);
@@ -208,7 +204,6 @@
 template <class AA, class Comparator>
 void indexed_priority_queue<AA, Comparator>::swap_indices(size_t i, size_t j) {
     if (!(0u < i && i < pq.size() && 0u < j && j < pq.size())) {
-        ALOGE("indexed_priority_queue: Attempting to swap invalid index");
         return;
     }
     sp<const AA> val_i = pq[i];
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index ba93feb..57a92b6 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -73,6 +73,7 @@
         SettingChanged setting_changed = 41;
         ActivityForegroundStateChanged activity_foreground_state_changed = 42;
         IsolatedUidChanged isolated_uid_changed = 43;
+        PacketWakeupOccurred packet_wakeup_occurred = 44;
         // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 
@@ -906,3 +907,32 @@
     optional uint64 freq_idx = 2;
     optional uint64 time_ms = 3;
 }
+
+/*
+ * Logs the reception of an incoming network packet causing the main system to wake up for
+ * processing that packet. These events are notified by the kernel via Netlink NFLOG to Netd
+ * and processed by WakeupController.cpp.
+ */
+message PacketWakeupOccurred {
+    // The uid owning the socket into which the packet was delivered, or -1 if the packet was
+    // delivered nowhere.
+    optional int32 uid = 1;
+    // The interface name on which the packet was received.
+    optional string iface = 2;
+    // The ethertype value of the packet.
+    optional int32 ethertype = 3;
+    // String representation of the destination MAC address of the packet.
+    optional string destination_hardware_address = 4;
+    // String representation of the source address of the packet if this was an IP packet.
+    optional string source_ip = 5;
+    // String representation of the destination address of the packet if this was an IP packet.
+    optional string destination_ip = 6;
+    // The value of the protocol field if this was an IPv4 packet or the value of the Next Header
+    // field if this was an IPv6 packet. The range of possible values is the same for both IP
+    // families.
+    optional int32 ip_next_header = 7;
+    // The source port if this was a TCP or UDP packet.
+    optional int32 source_port = 8;
+    // The destination port if this was a TCP or UDP packet.
+    optional int32 destination_port = 9;
+}
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index f9da68e..d47bd4f 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -17,7 +17,6 @@
 #define DEBUG true  // STOPSHIP if true
 #include "Log.h"
 
-#include "../anomaly/DiscreteAnomalyTracker.h"
 #include "CountMetricProducer.h"
 #include "stats_util.h"
 
@@ -114,7 +113,7 @@
     // Dump current bucket if it's stale.
     // If current bucket is still on-going, don't force dump current bucket.
     // In finish(), We can force dump current bucket.
-    flushCounterIfNeeded(endTime);
+    flushIfNeeded(endTime);
     VLOG("metric %s dump report now...", mMetric.name().c_str());
 
     for (const auto& counter : mPastBuckets) {
@@ -170,7 +169,6 @@
 
     startNewProtoOutputStream(endTime);
     mPastBuckets.clear();
-    mByteSize = 0;
 
     return buffer;
 
@@ -188,7 +186,7 @@
         const LogEvent& event, bool scheduledPull) {
     uint64_t eventTimeNs = event.GetTimestampNs();
 
-    flushCounterIfNeeded(eventTimeNs);
+    flushIfNeeded(eventTimeNs);
 
     if (condition == false) {
         return;
@@ -205,41 +203,41 @@
         count++;
     }
 
-    VLOG("metric %s %s->%d", mMetric.name().c_str(), eventKey.c_str(),
-         (*mCurrentSlicedCounter)[eventKey]);
+    for (auto& tracker : mAnomalyTrackers) {
+        tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey,
+                                         mCurrentSlicedCounter->find(eventKey)->second);
+    }
+
+    VLOG("metric %s %s->%lld", mMetric.name().c_str(), eventKey.c_str(),
+         (long long)(*mCurrentSlicedCounter)[eventKey]);
 }
 
 // When a new matched event comes in, we check if event falls into the current
 // bucket. If not, flush the old counter to past buckets and initialize the new bucket.
-void CountMetricProducer::flushCounterIfNeeded(const uint64_t eventTimeNs) {
-    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
+void CountMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
+    if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
         return;
     }
 
-    // adjust the bucket start time
-    // TODO: This (and addPastBucket to which it goes) doesn't really need to be an int64.
-    uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-
     CountBucket info;
     info.mBucketStartNs = mCurrentBucketStartTimeNs;
     info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.mBucketNum = mCurrentBucketNum;
     for (const auto& counter : *mCurrentSlicedCounter) {
         info.mCount = counter.second;
         auto& bucketList = mPastBuckets[counter.first];
         bucketList.push_back(info);
-        VLOG("metric %s, dump key value: %s -> %d", mMetric.name().c_str(), counter.first.c_str(),
-             counter.second);
-        mByteSize += sizeof(info);
+        VLOG("metric %s, dump key value: %s -> %lld", mMetric.name().c_str(), counter.first.c_str(),
+             (long long)counter.second);
     }
 
     for (auto& tracker : mAnomalyTrackers) {
-        tracker->addOrUpdateBucket(mCurrentSlicedCounter, mCurrentBucketNum);
-        tracker->declareAndDeclareAnomaly();
+        tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum);
     }
 
     // Reset counters (do not clear, since the old one is still referenced in mAnomalyTrackers).
     mCurrentSlicedCounter = std::make_shared<DimToValMap>();
-
+    uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
     mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
     mCurrentBucketNum += numBucketsForward;
     VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
@@ -250,7 +248,11 @@
 // greater than actual data size as it contains each dimension of
 // CountMetricData is  duplicated.
 size_t CountMetricProducer::byteSize() {
-    return mByteSize;
+    size_t totalSize = 0;
+    for (const auto& pair : mPastBuckets) {
+        totalSize += pair.second.size() * kBucketSize;
+    }
+    return totalSize;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index b7e480c..b3f8ee3 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -21,9 +21,9 @@
 
 #include <android/util/ProtoOutputStream.h>
 #include <gtest/gtest_prod.h>
+#include "../anomaly/AnomalyTracker.h"
 #include "../condition/ConditionTracker.h"
 #include "../matchers/matcher_util.h"
-#include "../anomaly/DiscreteAnomalyTracker.h"
 #include "MetricProducer.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
@@ -36,6 +36,7 @@
     int64_t mBucketStartNs;
     int64_t mBucketEndNs;
     int64_t mCount;
+    uint64_t mBucketNum;
 };
 
 class CountMetricProducer : public MetricProducer {
@@ -50,6 +51,8 @@
 
     void finish() override;
 
+    void flushIfNeeded(const uint64_t newEventTime) override;
+
     // TODO: Pass a timestamp as a parameter in onDumpReport.
     std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
 
@@ -76,18 +79,15 @@
     // TODO: Add a lock to mPastBuckets.
     std::unordered_map<HashableDimensionKey, std::vector<CountBucket>> mPastBuckets;
 
-    size_t mByteSize;
-
     // The current bucket.
     std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>();
 
-    vector<std::unique_ptr<DiscreteAnomalyTracker>> mAnomalyTrackers;
-
-    void flushCounterIfNeeded(const uint64_t newEventTime);
+    static const size_t kBucketSize = sizeof(CountBucket{});
 
     FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
+    FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index eba2e06..b0a97b1 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -110,16 +110,16 @@
 }
 
 unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
-        vector<DurationBucket>& bucket) {
+        const HashableDimensionKey& eventKey, vector<DurationBucket>& bucket) {
     switch (mMetric.aggregation_type()) {
         case DurationMetric_AggregationType_SUM:
-            return make_unique<OringDurationTracker>(mWizard, mConditionTrackerIndex, mNested,
-                                                     mCurrentBucketStartTimeNs, mBucketSizeNs,
-                                                     bucket);
+            return make_unique<OringDurationTracker>(eventKey, mWizard, mConditionTrackerIndex,
+                                                     mNested, mCurrentBucketStartTimeNs,
+                                                     mBucketSizeNs, mAnomalyTrackers, bucket);
         case DurationMetric_AggregationType_MAX_SPARSE:
-            return make_unique<MaxDurationTracker>(mWizard, mConditionTrackerIndex, mNested,
-                                                   mCurrentBucketStartTimeNs, mBucketSizeNs,
-                                                   bucket);
+            return make_unique<MaxDurationTracker>(eventKey, mWizard, mConditionTrackerIndex,
+                                                   mNested, mCurrentBucketStartTimeNs,
+                                                   mBucketSizeNs, mAnomalyTrackers, bucket);
     }
 }
 
@@ -131,7 +131,6 @@
 void DurationMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
     VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
     // Now for each of the on-going event, check if the condition has changed for them.
-    flushIfNeeded(eventTime);
     for (auto& pair : mCurrentSlicedDuration) {
         pair.second->onSlicedConditionMayChange(eventTime);
     }
@@ -142,27 +141,11 @@
     mCondition = conditionMet;
     // TODO: need to populate the condition change time from the event which triggers the condition
     // change, instead of using current time.
-
-    flushIfNeeded(eventTime);
     for (auto& pair : mCurrentSlicedDuration) {
         pair.second->onConditionChanged(conditionMet, eventTime);
     }
 }
 
-static void addDurationBucketsToReport(StatsLogReport_DurationMetricDataWrapper& wrapper,
-                                       const vector<KeyValuePair>& key,
-                                       const vector<DurationBucketInfo>& buckets) {
-    DurationMetricData* data = wrapper.add_data();
-    for (const auto& kv : key) {
-        data->add_dimension()->CopyFrom(kv);
-    }
-    for (const auto& bucket : buckets) {
-        data->add_bucket_info()->CopyFrom(bucket);
-        VLOG("\t bucket [%lld - %lld] duration(ns): %lld", bucket.start_bucket_nanos(),
-             bucket.end_bucket_nanos(), bucket.duration_nanos());
-    }
-}
-
 std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() {
     long long endTime = time(nullptr) * NS_PER_SEC;
 
@@ -231,7 +214,6 @@
     if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
         return;
     }
-
     VLOG("flushing...........");
     for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end();) {
         if (it->second->flushIfNeeded(eventTime)) {
@@ -244,6 +226,7 @@
 
     int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
     mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
+    mCurrentBucketNum += numBucketsForward;
 }
 
 void DurationMetricProducer::onMatchedLogEventInternal(
@@ -262,7 +245,7 @@
     HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
 
     if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
-        mCurrentSlicedDuration[eventKey] = createDurationTracker(mPastBuckets[eventKey]);
+        mCurrentSlicedDuration[eventKey] = createDurationTracker(eventKey, mPastBuckets[eventKey]);
     }
 
     auto it = mCurrentSlicedDuration.find(eventKey);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index bb5d4d9..4c8dbcb 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef DURATION_METRIC_PRODUCER_H
-#define DURATION_METRIC_PRODUCER_H
+#pragma once
+
 
 #include <unordered_map>
 
@@ -48,6 +48,7 @@
     void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
 
     void finish() override;
+    void flushIfNeeded(const uint64_t newEventTime) override;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
     std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
@@ -95,11 +96,8 @@
     std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
             mCurrentSlicedDuration;
 
-    void flushDurationIfNeeded(const uint64_t newEventTime);
-
-    std::unique_ptr<DurationTracker> createDurationTracker(std::vector<DurationBucket>& bucket);
-
-    void flushIfNeeded(uint64_t timestamp);
+    std::unique_ptr<DurationTracker> createDurationTracker(const HashableDimensionKey& eventKey,
+                                                           std::vector<DurationBucket>& bucket);
 
     static const size_t kBucketSize = sizeof(DurationBucket{});
 };
@@ -108,4 +106,3 @@
 }  // namespace os
 }  // namespace android
 
-#endif  // DURATION_METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 0dccdf4..0c453cd 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -47,6 +47,8 @@
     void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
 
     void finish() override;
+    void flushIfNeeded(const uint64_t newEventTime) override {
+    }
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
     std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index ed18f89..42ac1a2 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -63,8 +63,9 @@
 const int FIELD_ID_GAUGE = 3;
 
 GaugeMetricProducer::GaugeMetricProducer(const GaugeMetric& metric, const int conditionIndex,
-                                         const sp<ConditionWizard>& wizard, const int pullTagId)
-    : MetricProducer((time(nullptr) * NS_PER_SEC), conditionIndex, wizard),
+                                         const sp<ConditionWizard>& wizard, const int pullTagId,
+                                         const int64_t startTimeNs)
+    : MetricProducer(startTimeNs, conditionIndex, wizard),
       mMetric(metric),
       mPullTagId(pullTagId) {
     if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
@@ -114,7 +115,7 @@
     // Dump current bucket if it's stale.
     // If current bucket is still on-going, don't force dump current bucket.
     // In finish(), We can force dump current bucket.
-    flushGaugeIfNeededLocked(time(nullptr) * NS_PER_SEC);
+    flushIfNeeded(time(nullptr) * NS_PER_SEC);
 
     for (const auto& pair : mPastBuckets) {
         const HashableDimensionKey& hashableKey = pair.first;
@@ -168,7 +169,6 @@
 
     startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
     mPastBuckets.clear();
-    mByteSize = 0;
 
     return buffer;
 
@@ -178,15 +178,18 @@
 void GaugeMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
     AutoMutex _l(mLock);
     VLOG("Metric %s onConditionChanged", mMetric.name().c_str());
+    flushIfNeeded(eventTime);
     mCondition = conditionMet;
 
-    // Push mode. Nothing to do.
+    // Push mode. No need to proactively pull the gauge data.
     if (mPullTagId == -1) {
         return;
     }
-    // If (1) the condition is not met or (2) we already pulled the gauge metric in the current
-    // bucket, do not pull gauge again.
-    if (!mCondition || mCurrentSlicedBucket.size() > 0) {
+    if (!mCondition) {
+        return;
+    }
+    // Already have gauge metric for the current bucket, do not do it again.
+    if (mCurrentSlicedBucket->size() > 0) {
         return;
     }
     vector<std::shared_ptr<LogEvent>> allData;
@@ -197,16 +200,16 @@
     for (const auto& data : allData) {
         onMatchedLogEvent(0, *data, false /*scheduledPull*/);
     }
-    flushGaugeIfNeededLocked(eventTime);
+    flushIfNeeded(eventTime);
 }
 
 void GaugeMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
     VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
 }
 
-long GaugeMetricProducer::getGauge(const LogEvent& event) {
+int64_t GaugeMetricProducer::getGauge(const LogEvent& event) {
     status_t err = NO_ERROR;
-    long val = event.GetLong(mMetric.gauge_field(), &err);
+    int64_t val = event.GetLong(mMetric.gauge_field(), &err);
     if (err == NO_ERROR) {
         return val;
     } else {
@@ -217,14 +220,9 @@
 
 void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
     AutoMutex mutex(mLock);
-    if (allData.size() == 0) {
-        return;
-    }
     for (const auto& data : allData) {
         onMatchedLogEvent(0, *data, true /*scheduledPull*/);
     }
-    uint64_t eventTime = allData.at(0)->GetTimestampNs();
-    flushGaugeIfNeededLocked(eventTime);
 }
 
 void GaugeMetricProducer::onMatchedLogEventInternal(
@@ -241,15 +239,21 @@
         return;
     }
 
-    // For gauge metric, we just simply use the latest guage in the given bucket.
-    const long gauge = getGauge(event);
-    if (gauge < 0) {
-        VLOG("Invalid gauge at event Time: %lld", (long long)eventTimeNs);
+    // When the event happens in a new bucket, flush the old buckets.
+    if (eventTimeNs >= mCurrentBucketStartTimeNs + mBucketSizeNs) {
+        flushIfNeeded(eventTimeNs);
+    }
+
+    // For gauge metric, we just simply use the first guage in the given bucket.
+    if (!mCurrentSlicedBucket->empty()) {
         return;
     }
-    mCurrentSlicedBucket[eventKey] = gauge;
-    if (mPullTagId < 0) {
-        flushGaugeIfNeededLocked(eventTimeNs);
+    const long gauge = getGauge(event);
+    if (gauge >= 0) {
+        (*mCurrentSlicedBucket)[eventKey] = gauge;
+    }
+    for (auto& tracker : mAnomalyTrackers) {
+        tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, gauge);
     }
 }
 
@@ -258,39 +262,45 @@
 // bucket.
 // if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside
 // the GaugeMetricProducer while holding the lock.
-void GaugeMetricProducer::flushGaugeIfNeededLocked(const uint64_t eventTimeNs) {
-    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
-        VLOG("event time is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
-             (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
+void GaugeMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
+    if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
         return;
     }
 
-    // Adjusts the bucket start time
-    int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-
     GaugeBucket info;
     info.mBucketStartNs = mCurrentBucketStartTimeNs;
     info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.mBucketNum = mCurrentBucketNum;
 
-    for (const auto& slice : mCurrentSlicedBucket) {
+    for (const auto& slice : *mCurrentSlicedBucket) {
         info.mGauge = slice.second;
         auto& bucketList = mPastBuckets[slice.first];
         bucketList.push_back(info);
-        mByteSize += sizeof(info);
-
-        VLOG("gauge metric %s, dump key value: %s -> %ld", mMetric.name().c_str(),
-             slice.first.c_str(), slice.second);
+        VLOG("gauge metric %s, dump key value: %s -> %lld", mMetric.name().c_str(),
+             slice.first.c_str(), (long long)slice.second);
     }
-    // Reset counters
-    mCurrentSlicedBucket.clear();
 
+    // Reset counters
+    for (auto& tracker : mAnomalyTrackers) {
+        tracker->addPastBucket(mCurrentSlicedBucket, mCurrentBucketNum);
+    }
+
+    mCurrentSlicedBucket = std::make_shared<DimToValMap>();
+
+    // Adjusts the bucket start time
+    int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
     mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+    mCurrentBucketNum += numBucketsForward;
     VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
          (long long)mCurrentBucketStartTimeNs);
 }
 
 size_t GaugeMetricProducer::byteSize() {
-    return mByteSize;
+    size_t totalSize = 0;
+    for (const auto& pair : mPastBuckets) {
+        totalSize += pair.second.size() * kBucketSize;
+    }
+    return totalSize;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index f9e4deb..930afb2 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -19,6 +19,7 @@
 #include <unordered_map>
 
 #include <android/util/ProtoOutputStream.h>
+#include <gtest/gtest_prod.h>
 #include "../condition/ConditionTracker.h"
 #include "../external/PullDataReceiver.h"
 #include "../external/StatsPullerManager.h"
@@ -35,6 +36,7 @@
     int64_t mBucketStartNs;
     int64_t mBucketEndNs;
     int64_t mGauge;
+    uint64_t mBucketNum;
 };
 
 // This gauge metric producer first register the puller to automatically pull the gauge at the
@@ -46,7 +48,8 @@
     // TODO: Pass in the start time from MetricsManager, it should be consistent
     // for all metrics.
     GaugeMetricProducer(const GaugeMetric& countMetric, const int conditionIndex,
-                        const sp<ConditionWizard>& wizard, const int pullTagId);
+                        const sp<ConditionWizard>& wizard, const int pullTagId,
+                        const int64_t startTimeNs);
 
     virtual ~GaugeMetricProducer();
 
@@ -57,6 +60,7 @@
     void onSlicedConditionMayChange(const uint64_t eventTime) override;
 
     void finish() override;
+    void flushIfNeeded(const uint64_t newEventTime) override;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
     std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
@@ -92,13 +96,15 @@
     std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
 
     // The current bucket.
-    std::unordered_map<HashableDimensionKey, long> mCurrentSlicedBucket;
+    std::shared_ptr<DimToValMap> mCurrentSlicedBucket = std::make_shared<DimToValMap>();
 
-    void flushGaugeIfNeededLocked(const uint64_t newEventTime);
+    int64_t getGauge(const LogEvent& event);
 
-    long getGauge(const LogEvent& event);
+    static const size_t kBucketSize = sizeof(GaugeBucket{});
 
-    size_t mByteSize;
+    FRIEND_TEST(GaugeMetricProducerTest, TestWithCondition);
+    FRIEND_TEST(GaugeMetricProducerTest, TestNoCondition);
+    FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index c7982a8..0f93744 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -17,6 +17,7 @@
 #ifndef METRIC_PRODUCER_H
 #define METRIC_PRODUCER_H
 
+#include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionWizard.h"
 #include "matchers/matcher_util.h"
 #include "packages/PackageInfoListener.h"
@@ -58,6 +59,7 @@
     // This is called when the metric collecting is done, e.g., when there is a new configuration
     // coming. MetricProducer should do the clean up, and dump existing data to dropbox.
     virtual void finish() = 0;
+    virtual void flushIfNeeded(const uint64_t newEventTime) = 0;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport and update all its
     // implementations.
@@ -72,6 +74,14 @@
     // state.
     virtual size_t byteSize() = 0;
 
+    void addAnomalyTracker(sp<AnomalyTracker> tracker) {
+        mAnomalyTrackers.push_back(tracker);
+    }
+
+    int64_t getBuckeSizeInNs() const {
+        return mBucketSizeNs;
+    }
+
 protected:
     const uint64_t mStartTimeNs;
 
@@ -97,6 +107,8 @@
 
     std::vector<EventConditionLink> mConditionLinks;
 
+    std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
+
     /*
      * Individual metrics can implement their own business logic here. All pre-processing is done.
      *
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index e8a862f..5916b040 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -38,8 +38,8 @@
 
 MetricsManager::MetricsManager(const StatsdConfig& config) {
     mConfigValid = initStatsdConfig(config, mTagIds, mAllLogEntryMatchers, mAllConditionTrackers,
-                                    mAllMetricProducers, mConditionToMetricMap, mTrackerToMetricMap,
-                                    mTrackerToConditionMap);
+                                    mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
+                                    mTrackerToMetricMap, mTrackerToConditionMap);
 }
 
 MetricsManager::~MetricsManager() {
@@ -150,6 +150,19 @@
     }
 }
 
+void MetricsManager::onAnomalyAlarmFired(const uint64_t timestampNs,
+                                         sp<const AnomalyAlarm> anomaly) {
+    for (const auto& itr : mAllAnomalyTrackers) {
+        itr->declareAnomaly(timestampNs);
+    }
+}
+
+void MetricsManager::setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
+    for (auto& itr : mAllAnomalyTrackers) {
+        itr->setAnomalyMonitor(anomalyMonitor);
+    }
+}
+
 // Returns the total byte size of all metrics managed by a single config source.
 size_t MetricsManager::byteSize() {
     size_t totalSize = 0;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index fb16779..59ade7c 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include "anomaly/AnomalyMonitor.h"
+#include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionTracker.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "logd/LogEvent.h"
@@ -43,6 +45,10 @@
     // Called when everything should wrap up. We are about to finish (e.g., new config comes).
     void finish();
 
+    void onAnomalyAlarmFired(const uint64_t timestampNs, sp<const AnomalyAlarm> anomaly);
+
+    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();
 
@@ -70,6 +76,9 @@
     // Hold all metrics from the config.
     std::vector<sp<MetricProducer>> mAllMetricProducers;
 
+    // Hold all alert trackers.
+    std::vector<sp<AnomalyTracker>> mAllAnomalyTrackers;
+
     // To make the log processing more efficient, we want to do as much filtering as possible
     // before we go into individual trackers and conditions to match.
 
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index a35070b..9cbe6f6 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -188,7 +188,6 @@
 
     startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
     mPastBuckets.clear();
-    mByteSize = 0;
 
     return buffer;
 
@@ -215,7 +214,7 @@
             for (const auto& data : allData) {
                 onMatchedLogEvent(0, *data, false);
             }
-            flush_if_needed(eventTime);
+            flushIfNeeded(eventTime);
         }
         return;
     }
@@ -230,12 +229,12 @@
         uint64_t eventTime = allData.at(0)->GetTimestampNs();
         // alarm is not accurate and might drift.
         if (eventTime > mCurrentBucketStartTimeNs + mBucketSizeNs * 3 / 2) {
-            flush_if_needed(eventTime);
+            flushIfNeeded(eventTime);
         }
         for (const auto& data : allData) {
             onMatchedLogEvent(0, *data, true);
         }
-        flush_if_needed(eventTime);
+        flushIfNeeded(eventTime);
     }
 }
 
@@ -282,7 +281,7 @@
             }
         }
     } else {
-        flush_if_needed(eventTimeNs);
+        flushIfNeeded(eventTimeNs);
         interval.raw.push_back(make_pair(value, 0));
     }
 }
@@ -298,18 +297,18 @@
     }
 }
 
-void ValueMetricProducer::flush_if_needed(const uint64_t eventTimeNs) {
+void ValueMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
     if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
         VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
              (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
         return;
     }
-
     VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs,
          (int)mCurrentSlicedBucket.size());
     ValueBucket info;
     info.mBucketStartNs = mCurrentBucketStartTimeNs;
     info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.mBucketNum = mCurrentBucketNum;
 
     int tainted = 0;
     for (const auto& slice : mCurrentSlicedBucket) {
@@ -329,23 +328,29 @@
         // it will auto create new vector of ValuebucketInfo if the key is not found.
         auto& bucketList = mPastBuckets[slice.first];
         bucketList.push_back(info);
-        mByteSize += sizeof(info);
     }
 
     // Reset counters
     mCurrentSlicedBucket.swap(mNextSlicedBucket);
     mNextSlicedBucket.clear();
+
     int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+    mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+    mCurrentBucketNum += numBucketsForward;
+
     if (numBucketsForward > 1) {
         VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
     }
-    mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
     VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
          (long long)mCurrentBucketStartTimeNs);
 }
 
 size_t ValueMetricProducer::byteSize() {
-    return mByteSize;
+    size_t totalSize = 0;
+    for (const auto& pair : mPastBuckets) {
+        totalSize += pair.second.size() * kBucketSize;
+    }
+    return totalSize;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index c6c87f5..2b0b0ad 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -33,6 +33,7 @@
     int64_t mBucketStartNs;
     int64_t mBucketEndNs;
     int64_t mValue;
+    uint64_t mBucketNum;
 };
 
 class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
@@ -46,6 +47,7 @@
     void onConditionChanged(const bool condition, const uint64_t eventTime) override;
 
     void finish() override;
+    void flushIfNeeded(const uint64_t eventTimeNs) override;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
     std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
@@ -102,9 +104,7 @@
 
     long get_value(const LogEvent& event);
 
-    void flush_if_needed(const uint64_t eventTimeNs);
-
-    size_t mByteSize;
+    static const size_t kBucketSize = sizeof(ValueBucket{});
 
     FRIEND_TEST(ValueMetricProducerTest, TestNonDimensionalEvents);
     FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition);
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 18b3349..c91ea0f 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -17,6 +17,7 @@
 #ifndef DURATION_TRACKER_H
 #define DURATION_TRACKER_H
 
+#include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionWizard.h"
 #include "stats_util.h"
 
@@ -50,23 +51,28 @@
 };
 
 struct DurationBucket {
-    int64_t mBucketStartNs;
-    int64_t mBucketEndNs;
-    int64_t mDuration;
+    uint64_t mBucketStartNs;
+    uint64_t mBucketEndNs;
+    uint64_t mDuration;
+    uint64_t mBucketNum;
 };
 
 class DurationTracker {
 public:
-    DurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
-                    uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+    DurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
+                    int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+                    uint64_t bucketSizeNs, const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                     std::vector<DurationBucket>& bucket)
-        : mWizard(wizard),
+        : mEventKey(eventKey),
+          mWizard(wizard),
           mConditionTrackerIndex(conditionIndex),
           mBucketSizeNs(bucketSizeNs),
           mNested(nesting),
           mCurrentBucketStartTimeNs(currentBucketStartNs),
           mBucket(bucket),
-          mDuration(0){};
+          mDuration(0),
+          mCurrentBucketNum(0),
+          mAnomalyTrackers(anomalyTrackers){};
     virtual ~DurationTracker(){};
     virtual void noteStart(const HashableDimensionKey& key, bool condition,
                            const uint64_t eventTime, const ConditionKey& conditionKey) = 0;
@@ -79,7 +85,58 @@
     // events, so that the owner can safely remove the tracker.
     virtual bool flushIfNeeded(uint64_t timestampNs) = 0;
 
+    // Predict the anomaly timestamp given the current status.
+    virtual int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+                                              const uint64_t currentTimestamp) const = 0;
+
 protected:
+    // Starts the anomaly alarm.
+    void startAnomalyAlarm(const uint64_t eventTime) {
+        for (auto& anomalyTracker : mAnomalyTrackers) {
+            if (anomalyTracker != nullptr) {
+                anomalyTracker->startAlarm(mEventKey,
+                                           predictAnomalyTimestampNs(*anomalyTracker, eventTime));
+            }
+        }
+    }
+
+    // Stops the anomaly alarm.
+    void stopAnomalyAlarm() {
+        for (auto& anomalyTracker : mAnomalyTrackers) {
+            if (anomalyTracker != nullptr) {
+                anomalyTracker->stopAlarm(mEventKey);
+            }
+        }
+    }
+
+    void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) {
+        for (auto& anomalyTracker : mAnomalyTrackers) {
+            if (anomalyTracker != nullptr) {
+                anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum);
+            }
+        }
+    }
+
+    void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
+                                 const int64_t& currentBucketValue) {
+        for (auto& anomalyTracker : mAnomalyTrackers) {
+            if (anomalyTracker != nullptr) {
+                anomalyTracker->detectAndDeclareAnomaly(timestamp, currBucketNum, mEventKey,
+                                                        currentBucketValue);
+            }
+        }
+    }
+
+    void declareAnomalyIfAlarmExpired(const uint64_t& timestamp) {
+        for (auto& anomalyTracker : mAnomalyTrackers) {
+            if (anomalyTracker != nullptr) {
+                anomalyTracker->declareAnomalyIfAlarmExpired(mEventKey, timestamp);
+            }
+        }
+    }
+
+    HashableDimensionKey mEventKey;
+
     sp<ConditionWizard> mWizard;
 
     const int mConditionTrackerIndex;
@@ -93,6 +150,13 @@
     std::vector<DurationBucket>& mBucket;  // where to write output
 
     int64_t mDuration;  // current recorded duration result
+
+    uint64_t mCurrentBucketNum;
+
+    std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
+
+    FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 8c7bfb6..06e743d 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -23,14 +23,18 @@
 namespace os {
 namespace statsd {
 
-MaxDurationTracker::MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
+MaxDurationTracker::MaxDurationTracker(const HashableDimensionKey& eventKey,
+                                       sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                                        uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                                       const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                                        std::vector<DurationBucket>& bucket)
-    : DurationTracker(wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, bucket) {
+    : DurationTracker(eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs,
+                      anomalyTrackers, bucket) {
 }
 
 void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
                                    const uint64_t eventTime, const ConditionKey& conditionKey) {
+    flushIfNeeded(eventTime);
     // this will construct a new DurationInfo if this key didn't exist.
     DurationInfo& duration = mInfos[key];
     duration.conditionKeys = conditionKey;
@@ -56,8 +60,11 @@
     }
 }
 
+
 void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
                                   bool forceStop) {
+    flushIfNeeded(eventTime);
+    declareAnomalyIfAlarmExpired(eventTime);
     VLOG("MaxDuration: key %s stop", key.c_str());
     if (mInfos.find(key) == mInfos.end()) {
         // we didn't see a start event before. do nothing.
@@ -78,6 +85,7 @@
                      (long long)duration.lastStartTime, (long long)eventTime,
                      (long long)durationTime);
                 duration.lastDuration = duration.lastDuration + durationTime;
+                duration.lastStartTime = -1;
                 VLOG("  record duration: %lld ", (long long)duration.lastDuration);
             }
             break;
@@ -93,6 +101,7 @@
 
     if (duration.lastDuration > mDuration) {
         mDuration = duration.lastDuration;
+        detectAndDeclareAnomaly(eventTime, mCurrentBucketNum, mDuration);
         VLOG("Max: new max duration: %lld", (long long)mDuration);
     }
     // Once an atom duration ends, we erase it. Next time, if we see another atom event with the
@@ -101,9 +110,14 @@
         mInfos.erase(key);
     }
 }
+
 void MaxDurationTracker::noteStopAll(const uint64_t eventTime) {
-    for (auto& pair : mInfos) {
-        noteStop(pair.first, eventTime, true);
+    std::set<HashableDimensionKey> keys;
+    for (const auto& pair : mInfos) {
+        keys.insert(pair.first);
+    }
+    for (auto& key : keys) {
+        noteStop(key, eventTime, true);
     }
 }
 
@@ -122,7 +136,7 @@
     DurationBucket info;
     info.mBucketStartNs = mCurrentBucketStartTimeNs;
     info.mBucketEndNs = endTime;
-
+    info.mBucketNum = mCurrentBucketNum;
 
     uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
     mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
@@ -165,6 +179,7 @@
     if (mDuration != 0) {
         info.mDuration = mDuration;
         mBucket.push_back(info);
+        addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
         VLOG("  final duration for last bucket: %lld", (long long)mDuration);
     }
 
@@ -174,11 +189,15 @@
             DurationBucket info;
             info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
             info.mBucketEndNs = endTime + mBucketSizeNs * i;
+            info.mBucketNum = mCurrentBucketNum + i;
             info.mDuration = mBucketSizeNs;
             mBucket.push_back(info);
+            addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
             VLOG("  filling gap bucket with duration %lld", (long long)mBucketSizeNs);
         }
     }
+
+    mCurrentBucketNum += numBucketsForward;
     // If this tracker has no pending events, tell owner to remove.
     return !hasPendingEvent;
 }
@@ -204,6 +223,8 @@
 
 void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
                                               const uint64_t timestamp) {
+    flushIfNeeded(timestamp);
+    declareAnomalyIfAlarmExpired(timestamp);
     auto it = mInfos.find(key);
     if (it == mInfos.end()) {
         return;
@@ -215,7 +236,6 @@
             if (!conditionMet) {
                 it->second.state = DurationState::kPaused;
                 it->second.lastDuration += (timestamp - it->second.lastStartTime);
-
                 VLOG("MaxDurationTracker Key: %s Started->Paused ", key.c_str());
             }
             break;
@@ -232,6 +252,16 @@
             }
             break;
     }
+    if (it->second.lastDuration > mDuration) {
+        mDuration = it->second.lastDuration;
+        detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+    }
+}
+
+int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+                                                      const uint64_t currentTimestamp) const {
+    ALOGE("Max duration producer does not support anomaly timestamp prediction!!!");
+    return currentTimestamp;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index 167f81e..ca10210 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -28,8 +28,10 @@
 // they stop or bucket expires.
 class MaxDurationTracker : public DurationTracker {
 public:
-    MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
-                       uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+    MaxDurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
+                       int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+                       uint64_t bucketSizeNs,
+                       const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                        std::vector<DurationBucket>& bucket);
     void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
                    const ConditionKey& conditionKey) override;
@@ -40,11 +42,20 @@
     void onSlicedConditionMayChange(const uint64_t timestamp) override;
     void onConditionChanged(bool condition, const uint64_t timestamp) override;
 
+    int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+                                      const uint64_t currentTimestamp) const override;
+
 private:
     std::map<HashableDimensionKey, DurationInfo> mInfos;
 
     void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
                               const uint64_t timestamp);
+
+    FRIEND_TEST(MaxDurationTrackerTest, TestSimpleMaxDuration);
+    FRIEND_TEST(MaxDurationTrackerTest, TestCrossBucketBoundary);
+    FRIEND_TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition);
+    FRIEND_TEST(MaxDurationTrackerTest, TestStopAll);
+    FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index faf5ce5..29b6c89 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -23,11 +23,14 @@
 
 using std::pair;
 
-OringDurationTracker::OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+OringDurationTracker::OringDurationTracker(const HashableDimensionKey& eventKey,
+                                           sp<ConditionWizard> wizard, int conditionIndex,
                                            bool nesting, uint64_t currentBucketStartNs,
                                            uint64_t bucketSizeNs,
+                                           const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                                            std::vector<DurationBucket>& bucket)
-    : DurationTracker(wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, bucket),
+    : DurationTracker(eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs,
+                      anomalyTrackers, bucket),
       mStarted(),
       mPaused() {
     mLastStartTime = 0;
@@ -35,10 +38,12 @@
 
 void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
                                      const uint64_t eventTime, const ConditionKey& conditionKey) {
+    flushIfNeeded(eventTime);
     if (condition) {
         if (mStarted.size() == 0) {
             mLastStartTime = eventTime;
             VLOG("record first start....");
+            startAnomalyAlarm(eventTime);
         }
         mStarted[key]++;
     } else {
@@ -54,6 +59,8 @@
 
 void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp,
                                     const bool stopAll) {
+    flushIfNeeded(timestamp);
+    declareAnomalyIfAlarmExpired(timestamp);
     VLOG("Oring: %s stop", key.c_str());
     auto it = mStarted.find(key);
     if (it != mStarted.end()) {
@@ -64,6 +71,8 @@
         }
         if (mStarted.empty()) {
             mDuration += (timestamp - mLastStartTime);
+            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+            mLastStartTime = -1;
             VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
                  (long long)mDuration);
         }
@@ -76,56 +85,70 @@
             mPaused.erase(pausedIt);
             mConditionKeyMap.erase(key);
         }
-        }
+    }
+    if (mStarted.empty()) {
+        stopAnomalyAlarm();
+    }
 }
+
 void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
+    flushIfNeeded(timestamp);
+    declareAnomalyIfAlarmExpired(timestamp);
     if (!mStarted.empty()) {
         mDuration += (timestamp - mLastStartTime);
         VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
              (long long)mDuration);
+        detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
     }
 
+    stopAnomalyAlarm();
     mStarted.clear();
     mPaused.clear();
     mConditionKeyMap.clear();
+    mLastStartTime = -1;
 }
 
 bool OringDurationTracker::flushIfNeeded(uint64_t eventTime) {
-    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+    if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) {
         return false;
     }
     VLOG("OringDurationTracker Flushing.............");
     // adjust the bucket start time
     int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-    DurationBucket info;
-    uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
-    info.mBucketStartNs = mCurrentBucketStartTimeNs;
-    info.mBucketEndNs = endTime;
-
-    uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
-    mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
-
+    DurationBucket current_info;
+    current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
+    current_info.mBucketEndNs = current_info.mBucketStartNs + mBucketSizeNs;
+    current_info.mBucketNum = mCurrentBucketNum;
+    // Process the current bucket.
     if (mStarted.size() > 0) {
-        mDuration += (endTime - mLastStartTime);
+        mDuration += (current_info.mBucketEndNs - mLastStartTime);
+        mLastStartTime = current_info.mBucketEndNs;
     }
-    if (mDuration != 0) {
-        info.mDuration = mDuration;
-        // it will auto create new vector of CountbucketInfo if the key is not found.
-        mBucket.push_back(info);
-        VLOG("  duration: %lld", (long long)mDuration);
+    if (mDuration > 0) {
+        current_info.mDuration = mDuration;
+        mBucket.push_back(current_info);
+        addPastBucketToAnomalyTrackers(current_info.mDuration, current_info.mBucketNum);
+        VLOG("  duration: %lld", (long long)current_info.mDuration);
     }
 
     if (mStarted.size() > 0) {
         for (int i = 1; i < numBucketsForward; i++) {
             DurationBucket info;
-            info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
-            info.mBucketEndNs = endTime + mBucketSizeNs * i;
+            info.mBucketStartNs = mCurrentBucketStartTimeNs + mBucketSizeNs * i;
+            info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
+            info.mBucketNum = mCurrentBucketNum + i;
             info.mDuration = mBucketSizeNs;
-            mBucket.push_back(info);
-            VLOG("  add filling bucket with duration %lld", (long long)mBucketSizeNs);
+            mLastStartTime = info.mBucketEndNs;
+            if (info.mDuration > 0) {
+                mBucket.push_back(info);
+                addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
+                VLOG("  add filling bucket with duration %lld", (long long)info.mDuration);
+            }
         }
     }
-    mLastStartTime = mCurrentBucketStartTimeNs;
+    mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
+    mCurrentBucketNum += numBucketsForward;
+
     mDuration = 0;
 
     // if all stopped, then tell owner it's safe to remove this tracker.
@@ -133,6 +156,8 @@
 }
 
 void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
+    flushIfNeeded(timestamp);
+    declareAnomalyIfAlarmExpired(timestamp);
     vector<pair<HashableDimensionKey, int>> startedToPaused;
     vector<pair<HashableDimensionKey, int>> pausedToStarted;
     if (!mStarted.empty()) {
@@ -154,9 +179,11 @@
         }
 
         if (mStarted.empty()) {
-            mDuration += (timestamp - mLastStartTime);
+            mDuration = (timestamp - mLastStartTime);
+            mLastStartTime = -1;
             VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
                  (long long)mDuration);
+            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
         }
     }
 
@@ -183,26 +210,95 @@
         }
     }
 
+    if (mStarted.empty() && !pausedToStarted.empty()) {
+        startAnomalyAlarm(timestamp);
+    }
     mStarted.insert(pausedToStarted.begin(), pausedToStarted.end());
     mPaused.insert(startedToPaused.begin(), startedToPaused.end());
+
+    if (mStarted.empty()) {
+        stopAnomalyAlarm();
+    }
 }
 
 void OringDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
+    flushIfNeeded(timestamp);
+    declareAnomalyIfAlarmExpired(timestamp);
     if (condition) {
         if (!mPaused.empty()) {
             VLOG("Condition true, all started");
             if (mStarted.empty()) {
-                mLastStartTime = timestamp;
+                mLastStartTime = -1;
+            }
+            if (mStarted.empty() && !mPaused.empty()) {
+                startAnomalyAlarm(timestamp);
             }
             mStarted.insert(mPaused.begin(), mPaused.end());
+            mPaused.clear();
         }
     } else {
         if (!mStarted.empty()) {
             VLOG("Condition false, all paused");
-            mDuration += (timestamp - mLastStartTime);
+            mDuration = (timestamp - mLastStartTime);
+            mLastStartTime = -1;
             mPaused.insert(mStarted.begin(), mStarted.end());
+            mStarted.clear();
+            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
         }
     }
+    if (mStarted.empty()) {
+        stopAnomalyAlarm();
+    }
+}
+
+int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+                                                        const uint64_t eventTimestampNs) const {
+    // TODO: Unit-test this and see if it can be done more efficiently (e.g. use int32).
+    // All variables below represent durations (not timestamps).
+
+    // The time until the current bucket ends. This is how much more 'space' it can hold.
+    const int64_t currRemainingBucketSizeNs =
+            mBucketSizeNs - (eventTimestampNs - mCurrentBucketStartTimeNs);
+    // TODO: This should never be < 0. Document/guard against possible failures if it is.
+
+    const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold();
+
+    // As we move into the future, old buckets get overwritten (so their old data is erased).
+
+    // Sum of past durations. Will change as we overwrite old buckets.
+    int64_t pastNs = mDuration;
+    pastNs += anomalyTracker.getSumOverPastBuckets(mEventKey);
+
+    // How much of the threshold is still unaccounted after considering pastNs.
+    int64_t leftNs = thresholdNs - pastNs;
+
+    // First deal with the remainder of the current bucket.
+    if (leftNs <= currRemainingBucketSizeNs) {  // Predict the anomaly will occur in this bucket.
+        return eventTimestampNs + leftNs;
+    }
+    // The remainder of this bucket contributes, but we must then move to the next bucket.
+    pastNs += currRemainingBucketSizeNs;
+
+    // Now deal with the past buckets, starting with the oldest.
+    for (int futBucketIdx = 0; futBucketIdx < anomalyTracker.getNumOfPastPackets();
+         futBucketIdx++) {
+        // We now overwrite the oldest bucket with the previous 'current', and start a new
+        // 'current'.
+        pastNs -= anomalyTracker.getPastBucketValue(
+                mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastPackets() + futBucketIdx);
+        leftNs = thresholdNs - pastNs;
+        if (leftNs <= mBucketSizeNs) {  // Predict anomaly will occur in this bucket.
+            return eventTimestampNs + currRemainingBucketSizeNs + (futBucketIdx * mBucketSizeNs) +
+                   leftNs;
+        } else {  // This bucket would be entirely filled, and we'll need to move to the next
+                  // bucket.
+            pastNs += mBucketSizeNs;
+        }
+    }
+
+    // If we have reached this point, we even have to overwrite the the original current bucket.
+    // Thus, none of the past data will still be extant - pastNs is now 0.
+    return eventTimestampNs + thresholdNs;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 78760ba..6f92113 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -27,8 +27,10 @@
 // Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted.
 class OringDurationTracker : public DurationTracker {
 public:
-    OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
-                         uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+    OringDurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
+                         int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+                         uint64_t bucketSizeNs,
+                         const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                          std::vector<DurationBucket>& bucket);
     void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
                    const ConditionKey& conditionKey) override;
@@ -39,6 +41,9 @@
     void onConditionChanged(bool condition, const uint64_t timestamp) override;
     bool flushIfNeeded(uint64_t timestampNs) override;
 
+    int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+                                      const uint64_t currentTimestamp) const override;
+
 private:
     // We don't need to keep track of individual durations. The information that's needed is:
     // 1) which keys are started. We record the first start time.
@@ -49,6 +54,12 @@
     std::map<HashableDimensionKey, int> mPaused;
     int64_t mLastStartTime;
     std::map<HashableDimensionKey, ConditionKey> mConditionKeyMap;
+
+    FRIEND_TEST(OringDurationTrackerTest, TestDurationOverlap);
+    FRIEND_TEST(OringDurationTrackerTest, TestCrossBucketBoundary);
+    FRIEND_TEST(OringDurationTrackerTest, TestDurationConditionChange);
+    FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index d83c144..2344cb4 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -190,7 +190,8 @@
                  vector<sp<ConditionTracker>>& allConditionTrackers,
                  vector<sp<MetricProducer>>& allMetricProducers,
                  unordered_map<int, std::vector<int>>& conditionToMetricMap,
-                 unordered_map<int, std::vector<int>>& trackerToMetricMap) {
+                 unordered_map<int, std::vector<int>>& trackerToMetricMap,
+                 unordered_map<string, int>& metricMap) {
     sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
     const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
                                 config.event_metric_size() + config.value_metric_size();
@@ -208,6 +209,7 @@
         }
 
         int metricIndex = allMetricProducers.size();
+        metricMap.insert({metric.name(), metricIndex});
         int trackerIndex;
         if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
                                          allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
@@ -236,6 +238,7 @@
     for (int i = 0; i < config.duration_metric_size(); i++) {
         int metricIndex = allMetricProducers.size();
         const DurationMetric& metric = config.duration_metric(i);
+        metricMap.insert({metric.name(), metricIndex});
 
         auto what_it = conditionTrackerMap.find(metric.what());
         if (what_it == conditionTrackerMap.end()) {
@@ -305,6 +308,7 @@
     for (int i = 0; i < config.event_metric_size(); i++) {
         int metricIndex = allMetricProducers.size();
         const EventMetric& metric = config.event_metric(i);
+        metricMap.insert({metric.name(), metricIndex});
         if (!metric.has_name() || !metric.has_what()) {
             ALOGW("cannot find the metric name or what in config");
             return false;
@@ -342,6 +346,7 @@
         }
 
         int metricIndex = allMetricProducers.size();
+        metricMap.insert({metric.name(), metricIndex});
         int trackerIndex;
         if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
                                          allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
@@ -387,6 +392,7 @@
         }
 
         int metricIndex = allMetricProducers.size();
+        metricMap.insert({metric.name(), metricIndex});
         int trackerIndex;
         if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
                                          allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
@@ -419,21 +425,43 @@
         }
 
         sp<MetricProducer> gaugeProducer =
-                new GaugeMetricProducer(metric, conditionIndex, wizard, pullTagId);
+                new GaugeMetricProducer(metric, conditionIndex, wizard, pullTagId, startTimeNs);
         allMetricProducers.push_back(gaugeProducer);
     }
     return true;
 }
 
+bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& metricProducerMap,
+                vector<sp<MetricProducer>>& allMetricProducers,
+                vector<sp<AnomalyTracker>>& allAnomalyTrackers) {
+    for (int i = 0; i < config.alert_size(); i++) {
+        const Alert& alert = config.alert(i);
+        const auto& itr = metricProducerMap.find(alert.metric_name());
+        if (itr == metricProducerMap.end()) {
+            ALOGW("alert has unknown metric name: %s %s", alert.name().c_str(),
+                  alert.metric_name().c_str());
+            return false;
+        }
+        const int metricIndex = itr->second;
+        sp<AnomalyTracker> anomalyTracker =
+                new AnomalyTracker(alert, allMetricProducers[metricIndex]->getBuckeSizeInNs());
+        allMetricProducers[metricIndex]->addAnomalyTracker(anomalyTracker);
+        allAnomalyTrackers.push_back(anomalyTracker);
+    }
+    return true;
+}
+
 bool initStatsdConfig(const StatsdConfig& config, set<int>& allTagIds,
                       vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
                       vector<sp<ConditionTracker>>& allConditionTrackers,
                       vector<sp<MetricProducer>>& allMetricProducers,
+                      vector<sp<AnomalyTracker>>& allAnomalyTrackers,
                       unordered_map<int, std::vector<int>>& conditionToMetricMap,
                       unordered_map<int, std::vector<int>>& trackerToMetricMap,
                       unordered_map<int, std::vector<int>>& trackerToConditionMap) {
     unordered_map<string, int> logTrackerMap;
     unordered_map<string, int> conditionTrackerMap;
+    unordered_map<string, int> metricProducerMap;
 
     if (!initLogTrackers(config, logTrackerMap, allLogEntryMatchers, allTagIds)) {
         ALOGE("initLogMatchingTrackers failed");
@@ -449,10 +477,14 @@
 
     if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allLogEntryMatchers,
                      allConditionTrackers, allMetricProducers, conditionToMetricMap,
-                     trackerToMetricMap)) {
+                     trackerToMetricMap, metricProducerMap)) {
         ALOGE("initMetricProducers failed");
         return false;
     }
+    if (!initAlerts(config, metricProducerMap, allMetricProducers, allAnomalyTrackers)) {
+        ALOGE("initAlerts failed");
+        return false;
+    }
     return true;
 }
 
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index edf3af0..7d7e0c3 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -88,6 +88,7 @@
                       std::vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
                       std::vector<sp<ConditionTracker>>& allConditionTrackers,
                       std::vector<sp<MetricProducer>>& allMetricProducers,
+                      vector<sp<AnomalyTracker>>& allAnomalyTrackers,
                       std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
                       std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
                       std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index e1d0aceb..b7d8f97 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -35,15 +35,14 @@
 
 typedef std::map<std::string, HashableDimensionKey> ConditionKey;
 
-// TODO: For P, change int to int64_t.
-// TODO: Should HashableDimensionKey be marked here as const?
-typedef std::unordered_map<HashableDimensionKey, int> DimToValMap;
+typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
 
 EventMetricData parse(log_msg msg);
 
 int getTagId(log_msg msg);
 
 std::string getHashableKey(std::vector<KeyValuePair> key);
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index d3b04ba..e75a37f 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -113,11 +113,11 @@
 }
 
 message EventConditionLink {
-  optional string condition = 1;
+    optional string condition = 1;
 
-  repeated KeyMatcher key_in_main = 2;
+    repeated KeyMatcher key_in_main = 2;
 
-  repeated KeyMatcher key_in_condition = 3;
+    repeated KeyMatcher key_in_condition = 3;
 }
 
 message EventMetric {
@@ -217,8 +217,6 @@
     optional int32 refractory_period_secs = 5;
 
     optional int64 trigger_if_sum_gt = 6;
-
-    optional int32 refractory_period_in_buckets = 7;
 }
 
 message StatsdConfig {
diff --git a/cmds/statsd/tests/AnomalyMonitor_test.cpp b/cmds/statsd/tests/AnomalyMonitor_test.cpp
index 59fa160..920ca08 100644
--- a/cmds/statsd/tests/AnomalyMonitor_test.cpp
+++ b/cmds/statsd/tests/AnomalyMonitor_test.cpp
@@ -20,6 +20,8 @@
 
 #ifdef __ANDROID__
 TEST(AnomalyMonitor, popSoonerThan) {
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
     unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> set;
     AnomalyMonitor am(2);
 
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index caa1cf4..3dd4e70 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -34,6 +34,7 @@
 using std::set;
 using std::unordered_map;
 using std::vector;
+using android::os::statsd::Condition;
 
 #ifdef __ANDROID__
 
@@ -71,6 +72,19 @@
     combination->add_matcher("SCREEN_IS_ON");
     combination->add_matcher("SCREEN_IS_OFF");
 
+    CountMetric* metric = config.add_count_metric();
+    metric->set_name("3");
+    metric->set_what("SCREEN_IS_ON");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    KeyMatcher* keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(1);
+
+    auto alert = config.add_alert();
+    alert->set_name("3");
+    alert->set_metric_name("3");
+    alert->set_number_of_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
     return config;
 }
 
@@ -100,6 +114,29 @@
     return config;
 }
 
+StatsdConfig buildAlertWithUnknownMetric() {
+    StatsdConfig config;
+    config.set_name("12345");
+
+    LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_IS_ON");
+
+    CountMetric* metric = config.add_count_metric();
+    metric->set_name("3");
+    metric->set_what("SCREEN_IS_ON");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    KeyMatcher* keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(1);
+
+    auto alert = config.add_alert();
+    alert->set_name("3");
+    alert->set_metric_name("2");
+    alert->set_number_of_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
+    return config;
+}
+
 StatsdConfig buildMissingMatchers() {
     StatsdConfig config;
     config.set_name("12345");
@@ -156,6 +193,12 @@
     KeyMatcher* keyMatcher = metric->add_dimension();
     keyMatcher->set_key(1);
 
+    auto alert = config.add_alert();
+    alert->set_name("3");
+    alert->set_metric_name("3");
+    alert->set_number_of_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
     return config;
 }
 
@@ -183,7 +226,7 @@
     simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
 
-    Condition* condition = config.add_condition();
+    auto condition = config.add_condition();
     condition->set_name("SCREEN_IS_ON");
     SimpleCondition* simpleCondition = condition->mutable_simple_condition();
     simpleCondition->set_start("SCREEN_IS_ON");
@@ -206,13 +249,16 @@
     vector<sp<LogMatchingTracker>> allLogEntryMatchers;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
     EXPECT_TRUE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                 allMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                                 trackerToConditionMap));
+                                 allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                 trackerToMetricMap, trackerToConditionMap));
+    EXPECT_EQ(1u, allMetricProducers.size());
+    EXPECT_EQ(1u, allAnomalyTrackers.size());
 }
 
 TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
@@ -221,13 +267,14 @@
     vector<sp<LogMatchingTracker>> allLogEntryMatchers;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
     EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                                  trackerToConditionMap));
+                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                  trackerToMetricMap, trackerToConditionMap));
 }
 
 TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
@@ -236,13 +283,14 @@
     vector<sp<LogMatchingTracker>> allLogEntryMatchers;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
     EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                                  trackerToConditionMap));
+                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                  trackerToMetricMap, trackerToConditionMap));
 }
 
 TEST(MetricsManagerTest, TestMissingMatchers) {
@@ -251,13 +299,13 @@
     vector<sp<LogMatchingTracker>> allLogEntryMatchers;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-
     EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                                  trackerToConditionMap));
+                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                  trackerToMetricMap, trackerToConditionMap));
 }
 
 TEST(MetricsManagerTest, TestCircleConditionDependency) {
@@ -266,13 +314,30 @@
     vector<sp<LogMatchingTracker>> allLogEntryMatchers;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
     EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                                  trackerToConditionMap));
+                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                  trackerToMetricMap, trackerToConditionMap));
+}
+
+TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
+    StatsdConfig config = buildAlertWithUnknownMetric();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+
+    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
+                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                  trackerToMetricMap, trackerToConditionMap));
 }
 
 #else
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 4c12b03..0c19468 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -34,7 +34,8 @@
 
 TEST(UidMapTest, TestIsolatedUID) {
     sp<UidMap> m = new UidMap();
-    StatsLogProcessor p(m, nullptr);
+    sp<AnomalyMonitor> anomalyMonitor;
+    StatsLogProcessor p(m, anomalyMonitor, nullptr);
     LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
     addEvent.write(100);  // parent UID
     addEvent.write(101);  // isolated UID
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index b8150d0..e0200f27 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/anomaly/DiscreteAnomalyTracker.h"
+#include "src/anomaly/AnomalyTracker.h"
 
 #include <gtest/gtest.h>
 #include <stdio.h>
@@ -37,7 +37,7 @@
     }
 }
 
-std::shared_ptr<DimToValMap> MockeBucket(
+std::shared_ptr<DimToValMap> MockBucket(
         const std::vector<std::pair<string, long>>& key_value_pair_list) {
     std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
     AddValueToBucket(key_value_pair_list, bucket);
@@ -45,190 +45,240 @@
 }
 
 TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
+    const int64_t bucketSizeNs = 30 * NS_PER_SEC;
     Alert alert;
     alert.set_number_of_buckets(3);
-    alert.set_refractory_period_in_buckets(3);
+    alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
     alert.set_trigger_if_sum_gt(2);
 
-    DiscreteAnomalyTracker anomaly_tracker(alert);
+    AnomalyTracker anomalyTracker(alert, bucketSizeNs);
 
-    std::shared_ptr<DimToValMap> bucket0 = MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}});
-    // Adds bucket #0
-    anomaly_tracker.addOrUpdateBucket(bucket0, 0);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
+    std::shared_ptr<DimToValMap> bucket0 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
+    int64_t eventTimestamp0 = 10;
+    std::shared_ptr<DimToValMap> bucket1 = MockBucket({{"a", 1}});
+    int64_t eventTimestamp1 = bucketSizeNs + 11;
+    std::shared_ptr<DimToValMap> bucket2 = MockBucket({{"b", 1}});
+    int64_t eventTimestamp2 = 2 * bucketSizeNs + 12;
+    std::shared_ptr<DimToValMap> bucket3 = MockBucket({{"a", 2}});
+    int64_t eventTimestamp3 = 3 * bucketSizeNs + 13;
+    std::shared_ptr<DimToValMap> bucket4 = MockBucket({{"b", 1}});
+    int64_t eventTimestamp4 = 4 * bucketSizeNs + 14;
+    std::shared_ptr<DimToValMap> bucket5 = MockBucket({{"a", 2}});
+    int64_t eventTimestamp5 = 5 * bucketSizeNs + 15;
+    std::shared_ptr<DimToValMap> bucket6 = MockBucket({{"a", 2}});
+    int64_t eventTimestamp6 = 6 * bucketSizeNs + 16;
 
-    // Adds bucket #0 again. The sum does not change.
-    anomaly_tracker.addOrUpdateBucket(bucket0, 0);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 0L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(0, *bucket0));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp0, 0, *bucket0);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
 
-    // Adds bucket #1.
-    std::shared_ptr<DimToValMap> bucket1 = MockeBucket({{"b", 2}});
-    anomaly_tracker.addOrUpdateBucket(bucket1, 1);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    // Alarm.
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+    // Adds past bucket #0
+    anomalyTracker.addPastBucket(bucket0, 0);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
 
-    // Adds bucket #1 again. The sum does not change.
-    anomaly_tracker.addOrUpdateBucket(bucket1, 1);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    // Alarm.
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+    // Adds past bucket #0 again. The sum does not change.
+    anomalyTracker.addPastBucket(bucket0, 0);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
 
-    // Adds bucket #2.
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 2);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 2L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    // Adds past bucket #1.
+    anomalyTracker.addPastBucket(bucket1, 1);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+
+    // Adds past bucket #1 again. Nothing changes.
+    anomalyTracker.addPastBucket(bucket1, 1);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+
+    // Adds past bucket #2.
+    anomalyTracker.addPastBucket(bucket2, 2);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 2L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(3, *bucket3));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3);
     // Within refractory period.
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
 
     // Adds bucket #3.
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 3);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 3L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
-    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+    anomalyTracker.addPastBucket(bucket3, 3L);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 3L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(4, *bucket4));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
 
-    // Adds bucket #3.
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 2}}), 4);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 4L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    // Within refractory period.
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+    // Adds bucket #4.
+    anomalyTracker.addPastBucket(bucket4, 4);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(5, *bucket5));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
 
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 5);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 5L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    // Adds bucket #5.
+    anomalyTracker.addPastBucket(bucket5, 5);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(6, *bucket6));
     // Within refractory period.
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 5L);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
 }
 
 TEST(AnomalyTrackerTest, TestSparseBuckets) {
+    const int64_t bucketSizeNs = 30 * NS_PER_SEC;
     Alert alert;
     alert.set_number_of_buckets(3);
-    alert.set_refractory_period_in_buckets(3);
+    alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
     alert.set_trigger_if_sum_gt(2);
 
-    DiscreteAnomalyTracker anomaly_tracker(alert);
+    AnomalyTracker anomalyTracker(alert, bucketSizeNs);
 
-    // Add bucket #9
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}}), 9);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 9L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
+    std::shared_ptr<DimToValMap> bucket9 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
+    std::shared_ptr<DimToValMap> bucket16 = MockBucket({{"b", 4}});
+    std::shared_ptr<DimToValMap> bucket18 = MockBucket({{"b", 1}, {"c", 1}});
+    std::shared_ptr<DimToValMap> bucket20 = MockBucket({{"b", 3}, {"c", 1}});
+    std::shared_ptr<DimToValMap> bucket25 = MockBucket({{"d", 1}});
+    std::shared_ptr<DimToValMap> bucket28 = MockBucket({{"e", 2}});
 
-    // Add bucket #16
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 4}}), 16);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 16L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+    int64_t eventTimestamp1 = bucketSizeNs * 8 + 1;
+    int64_t eventTimestamp2 = bucketSizeNs * 15 + 11;
+    int64_t eventTimestamp3 = bucketSizeNs * 17 + 1;
+    int64_t eventTimestamp4 = bucketSizeNs * 19 + 2;
+    int64_t eventTimestamp5 = bucketSizeNs * 24 + 3;
+    int64_t eventTimestamp6 = bucketSizeNs * 27 + 3;
 
-    // Add bucket #18
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(9, *bucket9));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 9, *bucket9);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1);
+
+    // Add past bucket #9
+    anomalyTracker.addPastBucket(bucket9, 9);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 9L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(16, *bucket16));
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 16, *bucket16);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
+
+    // Add past bucket #16
+    anomalyTracker.addPastBucket(bucket16, 16);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(18, *bucket18));
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
     // Within refractory period.
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
 
-    // Add bucket #18 again.
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+    // Add past bucket #18
+    anomalyTracker.addPastBucket(bucket18, 18);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 18L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
 
-    // Add bucket #20
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 3}, {"d", 1}}), 20);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 20L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
+    // Add bucket #18 again. Nothing changes.
+    anomalyTracker.addPastBucket(bucket18, 18);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20);
+    // Within refractory period.
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
 
-    // Add bucket #25
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"d", 1}}), 25);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 25L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1L);
-    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
+    // Add past bucket #20
+    anomalyTracker.addPastBucket(bucket20, 20);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 20L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 3LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(25, *bucket25));
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 25, *bucket25);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
 
-    // Add bucket #28
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"e", 5}}), 28);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 28L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("e")->second, 5L);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 3L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 28L);
+    // Add past bucket #25
+    anomalyTracker.addPastBucket(bucket25, 25);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("d"), 1LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(28, *bucket28));
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 28, *bucket28);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
+
+    // Updates current bucket #28.
+    (*bucket28)["e"] = 5;
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(28, *bucket28));
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6 + 7, 28, *bucket28);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp6 + 7);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/indexed_priority_queue_test.cpp b/cmds/statsd/tests/indexed_priority_queue_test.cpp
index 600b953..d6cd876 100644
--- a/cmds/statsd/tests/indexed_priority_queue_test.cpp
+++ b/cmds/statsd/tests/indexed_priority_queue_test.cpp
@@ -22,10 +22,12 @@
 
 /** struct for template in indexed_priority_queue */
 struct AATest : public RefBase {
-    AATest(uint32_t val) : val(val) {
+    AATest(uint32_t val, std::string a, std::string b) : val(val), a(a), b(b) {
     }
 
     const int val;
+    const std::string a;
+    const std::string b;
 
     struct Smaller {
         bool operator()(const sp<const AATest> a, const sp<const AATest> b) const {
@@ -36,9 +38,11 @@
 
 #ifdef __ANDROID__
 TEST(indexed_priority_queue, empty_and_size) {
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> aa4 = new AATest{4};
-    sp<const AATest> aa8 = new AATest{8};
+    sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa8 = new AATest{8, emptyMetricId, emptyDimensionId};
 
     EXPECT_EQ(0u, ipq.size());
     EXPECT_TRUE(ipq.empty());
@@ -61,13 +65,15 @@
 }
 
 TEST(indexed_priority_queue, top) {
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> aa2 = new AATest{2};
-    sp<const AATest> aa4 = new AATest{4};
-    sp<const AATest> aa8 = new AATest{8};
-    sp<const AATest> aa12 = new AATest{12};
-    sp<const AATest> aa16 = new AATest{16};
-    sp<const AATest> aa20 = new AATest{20};
+    sp<const AATest> aa2 = new AATest{2, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa8 = new AATest{8, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa12 = new AATest{12, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa16 = new AATest{16, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa20 = new AATest{20, emptyMetricId, emptyDimensionId};
 
     EXPECT_EQ(ipq.top(), nullptr);
 
@@ -113,9 +119,11 @@
 }
 
 TEST(indexed_priority_queue, push_same_aa) {
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> aa4_a = new AATest{4};
-    sp<const AATest> aa4_b = new AATest{4};
+    sp<const AATest> aa4_a = new AATest{4, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa4_b = new AATest{4, emptyMetricId, emptyDimensionId};
 
     ipq.push(aa4_a);
     EXPECT_EQ(1u, ipq.size());
@@ -134,9 +142,11 @@
 }
 
 TEST(indexed_priority_queue, remove_nonexistant) {
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> aa4 = new AATest{4};
-    sp<const AATest> aa5 = new AATest{5};
+    sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa5 = new AATest{5, emptyMetricId, emptyDimensionId};
 
     ipq.push(aa4);
     ipq.remove(aa5);
@@ -147,8 +157,10 @@
 
 TEST(indexed_priority_queue, remove_same_aa) {
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> aa4_a = new AATest{4};
-    sp<const AATest> aa4_b = new AATest{4};
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
+    sp<const AATest> aa4_a = new AATest{4, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa4_b = new AATest{4, emptyMetricId, emptyDimensionId};
 
     ipq.push(aa4_a);
     ipq.push(aa4_b);
@@ -184,9 +196,11 @@
 
 TEST(indexed_priority_queue, pop) {
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> a = new AATest{1};
-    sp<const AATest> b = new AATest{2};
-    sp<const AATest> c = new AATest{3};
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
+    sp<const AATest> a = new AATest{1, emptyMetricId, emptyDimensionId};
+    sp<const AATest> b = new AATest{2, emptyMetricId, emptyDimensionId};
+    sp<const AATest> c = new AATest{3, emptyMetricId, emptyDimensionId};
 
     ipq.push(c);
     ipq.push(b);
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index b7c9b40..35e08af 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -54,21 +54,26 @@
     // 2 events in bucket 1.
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
-    countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+
+    // Flushes at event #2.
+    countProducer.flushIfNeeded(bucketStartTimeNs + 2);
+    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
+    // Flushes.
+    countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
     const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
     EXPECT_EQ(1UL, buckets.size());
-    const auto& bucketInfo = buckets[0];
-    EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
-    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
-    EXPECT_EQ(2LL, bucketInfo.mCount);
+    EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
+    EXPECT_EQ(2LL, buckets[0].mCount);
 
     // 1 matched event happens in bucket 2.
     LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 2);
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3, false);
-    countProducer.flushCounterIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1);
+    countProducer.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
@@ -79,7 +84,7 @@
     EXPECT_EQ(1LL, bucketInfo2.mCount);
 
     // nothing happens in bucket 3. we should not record anything for bucket 3.
-    countProducer.flushCounterIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 1);
+    countProducer.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
@@ -108,20 +113,22 @@
     EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
 
     countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2);
+    // Upon this match event, the matched event1 is flushed.
     countProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
     EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
 
-    countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
-
+    countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
-    const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
-    EXPECT_EQ(1UL, buckets.size());
-    const auto& bucketInfo = buckets[0];
-    EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
-    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
-    EXPECT_EQ(1LL, bucketInfo.mCount);
+    {
+        const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+        EXPECT_EQ(1UL, buckets.size());
+        const auto& bucketInfo = buckets[0];
+        EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
+        EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
+        EXPECT_EQ(1LL, bucketInfo.mCount);
+    }
 }
 
 TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
@@ -158,10 +165,11 @@
                                       bucketStartTimeNs);
 
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
+    countProducer.flushIfNeeded(bucketStartTimeNs + 1);
+    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
-
-    countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
-
+    countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
@@ -173,6 +181,68 @@
     EXPECT_EQ(1LL, bucketInfo.mCount);
 }
 
+TEST(CountMetricProducerTest, TestAnomalyDetection) {
+    Alert alert;
+    alert.set_name("alert");
+    alert.set_metric_name("1");
+    alert.set_trigger_if_sum_gt(2);
+    alert.set_number_of_buckets(2);
+    alert.set_refractory_period_secs(1);
+
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs = 30 * NS_PER_SEC;
+    int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+    int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+
+    CountMetric metric;
+    metric.set_name("1");
+    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    CountMetricProducer countProducer(metric, -1 /*-1 meaning no condition*/, wizard,
+                                      bucketStartTimeNs);
+    countProducer.addAnomalyTracker(anomalyTracker);
+
+    int tagId = 1;
+    LogEvent event1(tagId, bucketStartTimeNs + 1);
+    LogEvent event2(tagId, bucketStartTimeNs + 2);
+    LogEvent event3(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 1);
+    LogEvent event4(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1);
+    LogEvent event5(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2);
+    LogEvent event6(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3);
+    LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3 + NS_PER_SEC);
+
+    // Two events in bucket #0.
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
+
+    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+
+    // One event in bucket #2. No alarm as bucket #0 is trashed out.
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3, false);
+    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+
+    // Two events in bucket #3.
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4, false);
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event5, false);
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event6, false);
+    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second);
+    // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event5.GetTimestampNs());
+
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7, false);
+    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event7.GetTimestampNs());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
new file mode 100644
index 0000000..b9e2b8a
--- /dev/null
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -0,0 +1,201 @@
+// 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 "logd/LogEvent.h"
+#include "metrics_test_helper.h"
+#include "src/metrics/GaugeMetricProducer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(GaugeMetricProducerTest, TestWithCondition) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+
+    GaugeMetric metric;
+    metric.set_name("1");
+    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_gauge_field(2);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    GaugeMetricProducer gaugeProducer(metric, 1 /*has condition*/, wizard, -1, bucketStartTimeNs);
+
+    vector<std::shared_ptr<LogEvent>> allData;
+    std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
+    event1->write(1);
+    event1->write(13);
+    event1->init();
+    allData.push_back(event1);
+
+    std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 10);
+    event2->write(1);
+    event2->write(15);
+    event2->init();
+    allData.push_back(event2);
+
+    gaugeProducer.onDataPulled(allData);
+    gaugeProducer.flushIfNeeded(event2->GetTimestampNs() + 1);
+    EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
+
+    gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 11);
+    gaugeProducer.onConditionChanged(false, bucketStartTimeNs + 21);
+    gaugeProducer.onConditionChanged(true, bucketStartTimeNs + bucketSizeNs + 11);
+    std::shared_ptr<LogEvent> event3 =
+            std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
+    event3->write(1);
+    event3->write(25);
+    event3->init();
+    allData.push_back(event3);
+    gaugeProducer.onDataPulled(allData);
+    gaugeProducer.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 10);
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(25L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    // One dimension.
+    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size());
+    EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.front().mGauge);
+    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.front().mBucketNum);
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              gaugeProducer.mPastBuckets.begin()->second.front().mBucketStartNs);
+}
+
+TEST(GaugeMetricProducerTest, TestNoCondition) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+
+    GaugeMetric metric;
+    metric.set_name("1");
+    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_gauge_field(2);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    GaugeMetricProducer gaugeProducer(metric, -1 /*no condition*/, wizard, -1, bucketStartTimeNs);
+
+    vector<std::shared_ptr<LogEvent>> allData;
+    std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
+    event1->write(1);
+    event1->write(13);
+    event1->init();
+    allData.push_back(event1);
+
+    std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 10);
+    event2->write(1);
+    event2->write(15);
+    event2->init();
+    allData.push_back(event2);
+
+    std::shared_ptr<LogEvent> event3 =
+            std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
+    event3->write(1);
+    event3->write(25);
+    event3->init();
+    allData.push_back(event3);
+
+    gaugeProducer.onDataPulled(allData);
+    // Has one slice
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(25L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
+    EXPECT_EQ(13L, gaugeProducer.mPastBuckets.begin()->second.front().mGauge);
+    EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.begin()->second.front().mBucketNum);
+    EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.back().mGauge);
+    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              gaugeProducer.mPastBuckets.begin()->second.back().mBucketStartNs);
+}
+
+TEST(GaugeMetricProducerTest, TestAnomalyDetection) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    GaugeMetric metric;
+    metric.set_name("1");
+    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_gauge_field(2);
+    GaugeMetricProducer gaugeProducer(metric, -1 /*no condition*/, wizard, -1, bucketStartTimeNs);
+
+    Alert alert;
+    alert.set_name("alert");
+    alert.set_metric_name("1");
+    alert.set_trigger_if_sum_gt(25);
+    alert.set_number_of_buckets(2);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+    gaugeProducer.addAnomalyTracker(anomalyTracker);
+
+    std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
+    event1->write(1);
+    event1->write(13);
+    event1->init();
+
+    gaugeProducer.onDataPulled({event1});
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+
+    std::shared_ptr<LogEvent> event2 =
+            std::make_shared<LogEvent>(1, bucketStartTimeNs + bucketSizeNs + 10);
+    event2->write(1);
+    event2->write(15);
+    event2->init();
+
+    gaugeProducer.onDataPulled({event2});
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event2->GetTimestampNs());
+
+    std::shared_ptr<LogEvent> event3 =
+            std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
+    event3->write(1);
+    event3->write(24);
+    event3->init();
+
+    gaugeProducer.onDataPulled({event3});
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(24L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
+
+    // The event4 does not have the gauge field. Thus the current bucket value is 0.
+    std::shared_ptr<LogEvent> event4 =
+            std::make_shared<LogEvent>(1, bucketStartTimeNs + 3 * bucketSizeNs + 10);
+    event4->write(1);
+    event4->init();
+    gaugeProducer.onDataPulled({event4});
+    EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 9cc184a..9e169bb 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -45,17 +45,49 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker(wizard, -1, false, bucketStartTimeNs, bucketSizeNs, buckets);
+    MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
+                               buckets);
 
-    tracker.noteStart("", true, bucketStartTimeNs, key1);
-    tracker.noteStop("", bucketStartTimeNs + 10, false);
+    tracker.noteStart("1", true, bucketStartTimeNs, key1);
+    // Event starts again. This would not change anything as it already starts.
+    tracker.noteStart("1", true, bucketStartTimeNs + 3, key1);
+    // Stopped.
+    tracker.noteStop("1", bucketStartTimeNs + 10, false);
 
-    tracker.noteStart("", true, bucketStartTimeNs + 20, key1);
-    tracker.noteStop("", bucketStartTimeNs + 40, false);
+    // Another event starts in this bucket.
+    tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
+    tracker.noteStop("2", bucketStartTimeNs + 40, false /*stop all*/);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(20, buckets[0].mDuration);
+    EXPECT_EQ(20ULL, buckets[0].mDuration);
+}
+
+TEST(MaxDurationTrackerTest, TestStopAll) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    vector<DurationBucket> buckets;
+    ConditionKey key1;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
+                               buckets);
+
+    tracker.noteStart("1", true, bucketStartTimeNs + 1, key1);
+
+    // Another event starts in this bucket.
+    tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
+    tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
+    EXPECT_TRUE(tracker.mInfos.empty());
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(40ULL, buckets[1].mDuration);
 }
 
 TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
@@ -67,14 +99,33 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker(wizard, -1, false, bucketStartTimeNs, bucketSizeNs, buckets);
+    MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
+                               buckets);
 
+    // The event starts.
     tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
-    tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
 
+    // Starts again. Does not change anything.
+    tracker.noteStart("", true, bucketStartTimeNs + bucketSizeNs + 1, key1);
+
+    // Flushes at early 2nd bucket. The event still does not stop yet.
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
+
+    // Flushes at the end of the 2nd bucket. The event still does not stop yet.
+    tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs));
     EXPECT_EQ(2u, buckets.size());
-    EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].mDuration);
-    EXPECT_EQ((long long)bucketSizeNs, buckets[1].mDuration);
+    EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
+    EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[1].mDuration);
+
+    // The event stops at early 4th bucket.
+    tracker.noteStop("", bucketStartTimeNs + (3 * bucketSizeNs) + 20, false /*stop all*/);
+    tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 21);
+    EXPECT_EQ(3u, buckets.size());
+    EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
+    EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[1].mDuration);
+    EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[2].mDuration);
 }
 
 TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
@@ -86,7 +137,8 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker(wizard, -1, true, bucketStartTimeNs, bucketSizeNs, buckets);
+    MaxDurationTracker tracker("event", wizard, -1, true, bucketStartTimeNs, bucketSizeNs, {},
+                               buckets);
 
     // 2 starts
     tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
@@ -97,15 +149,17 @@
     tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
 
     EXPECT_EQ(2u, buckets.size());
-    EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].mDuration);
-    EXPECT_EQ((long long)bucketSizeNs, buckets[1].mDuration);
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
 
     // real stop now.
     tracker.noteStop("", bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
     tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1);
 
     EXPECT_EQ(3u, buckets.size());
-    EXPECT_EQ(5, buckets[2].mDuration);
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+    EXPECT_EQ(5ULL, buckets[2].mDuration);
 }
 
 TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
@@ -124,17 +178,65 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     int64_t durationTimeNs = 2 * 1000;
 
-    MaxDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
+    MaxDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
+                               buckets);
+    EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.onSlicedConditionMayChange(eventStartTimeNs + 2 * bucketSizeNs + 5);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
 
-    tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
+    tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + durationTimeNs, false);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+    EXPECT_TRUE(tracker.mInfos.empty());
+    EXPECT_EQ(6LL, tracker.mDuration);
 
-    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
+    tracker.noteStart("2:maps", false, eventStartTimeNs + 3 * bucketSizeNs + 10, key1);
+    EXPECT_EQ(1u, tracker.mInfos.size());
+    for (const auto& itr : tracker.mInfos) {
+        EXPECT_EQ(DurationState::kPaused, itr.second.state);
+        EXPECT_EQ(0LL, itr.second.lastDuration);
+    }
+    EXPECT_EQ(3u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+    EXPECT_EQ(6ULL, buckets[2].mDuration);
+}
 
-    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
-    EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(5, buckets[0].mDuration);
+TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
+    Alert alert;
+    alert.set_name("alert");
+    alert.set_metric_name("1");
+    alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
+    alert.set_number_of_buckets(2);
+    alert.set_refractory_period_secs(1);
+
+    vector<DurationBucket> buckets;
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+    uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
+    uint64_t bucketSizeNs = 30 * NS_PER_SEC;
+
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+    MaxDurationTracker tracker("event", wizard, -1, true, bucketStartTimeNs, bucketSizeNs,
+                               {anomalyTracker}, buckets);
+
+    tracker.noteStart("1", true, eventStartTimeNs, key1);
+    tracker.noteStop("1", eventStartTimeNs + 10, false);
+    EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
+    EXPECT_EQ(10LL, tracker.mDuration);
+
+    tracker.noteStart("2", true, eventStartTimeNs + 20, key1);
+    tracker.noteStop("2", eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
+    EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration);
+    EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs,
+              (long long)(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC));
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index f495d6b..f4edffd 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -45,16 +45,18 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
-    int64_t durationTimeNs = 2 * 1000;
+    uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
+    OringDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
+                                 buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
     tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
+    EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
 
     tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
-
-    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
     EXPECT_EQ(durationTimeNs, buckets[0].mDuration);
 }
@@ -71,7 +73,8 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    OringDurationTracker tracker(wizard, 1, true, bucketStartTimeNs, bucketSizeNs, buckets);
+    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
+                                 buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
     tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
@@ -81,7 +84,66 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(2003, buckets[0].mDuration);
+    EXPECT_EQ(2003ULL, buckets[0].mDuration);
+}
+
+TEST(OringDurationTrackerTest, TestStopAll) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    vector<DurationBucket> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
+                                 buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart("3:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
+
+    tracker.noteStopAll(eventStartTimeNs + 2003);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(2003ULL, buckets[0].mDuration);
+}
+
+TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    vector<DurationBucket> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    uint64_t durationTimeNs = 2 * 1000;
+
+    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
+                                 buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
+    tracker.noteStart("2:maps", true, eventStartTimeNs + 2 * bucketSizeNs, key1);
+    EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
+
+    tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+
+    tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 10, false);
+    tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 12, false);
+    tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
 }
 
 TEST(OringDurationTrackerTest, TestDurationConditionChange) {
@@ -98,19 +160,19 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
-    int64_t durationTimeNs = 2 * 1000;
+    uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
+    OringDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
+                                 buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
 
-    tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
-
-    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
-
-    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
-    EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(5, buckets[0].mDuration);
+    tracker.onSlicedConditionMayChange(eventStartTimeNs + 2 * bucketSizeNs + 5);
+    tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + durationTimeNs, false);
+    tracker.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + durationTimeNs);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
 }
 
 TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
@@ -128,7 +190,8 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    OringDurationTracker tracker(wizard, 1, true, bucketStartTimeNs, bucketSizeNs, buckets);
+    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
+                                 buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
     tracker.noteStart("2:maps", true, eventStartTimeNs + 2, key1);
@@ -141,7 +204,105 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(15, buckets[0].mDuration);
+    EXPECT_EQ(15ULL, buckets[0].mDuration);
+}
+
+TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
+    Alert alert;
+    alert.set_name("alert");
+    alert.set_metric_name("1");
+    alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
+    alert.set_number_of_buckets(2);
+    alert.set_refractory_period_secs(1);
+
+    vector<DurationBucket> buckets;
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+    uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
+    uint64_t bucketSizeNs = 30 * NS_PER_SEC;
+
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs,
+                                 {anomalyTracker}, buckets);
+
+    // Nothing in the past bucket.
+    tracker.noteStart("", true, eventStartTimeNs, key1);
+    EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs),
+              tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
+
+    tracker.noteStop("", eventStartTimeNs + 3, false);
+    EXPECT_EQ(0u, buckets.size());
+
+    uint64_t event1StartTimeNs = eventStartTimeNs + 10;
+    tracker.noteStart("1", true, event1StartTimeNs, key1);
+    // No past buckets. The anomaly will happen in bucket #0.
+    EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3),
+              tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs));
+
+    uint64_t event1StopTimeNs = eventStartTimeNs + bucketSizeNs + 10;
+    tracker.noteStop("1", event1StopTimeNs, false);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10,
+              buckets[0].mDuration);
+
+    const int64_t bucket0Duration = 3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10;
+    const int64_t bucket1Duration = eventStartTimeNs + 10 - bucketStartTimeNs;
+
+    // One past buckets. The anomaly will happen in bucket #1.
+    uint64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15;
+    tracker.noteStart("1", true, event2StartTimeNs, key1);
+    EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration -
+                          bucket1Duration),
+              tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
+    tracker.noteStop("1", event2StartTimeNs + 1, false);
+
+    // Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in
+    // bucket #2.
+    uint64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC;
+    tracker.noteStart("1", true, event3StartTimeNs, key1);
+    EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL),
+              tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
+}
+
+TEST(OringDurationTrackerTest, TestAnomalyDetection) {
+    Alert alert;
+    alert.set_name("alert");
+    alert.set_metric_name("1");
+    alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
+    alert.set_number_of_buckets(2);
+    alert.set_refractory_period_secs(1);
+
+    vector<DurationBucket> buckets;
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+    uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
+    uint64_t bucketSizeNs = 30 * NS_PER_SEC;
+
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+    OringDurationTracker tracker("event", wizard, 1, true /*nesting*/, bucketStartTimeNs,
+                                 bucketSizeNs, {anomalyTracker}, buckets);
+
+    tracker.noteStart("", true, eventStartTimeNs, key1);
+    tracker.noteStop("", eventStartTimeNs + 10, false);
+    EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
+    EXPECT_TRUE(tracker.mStarted.empty());
+    EXPECT_EQ(-1LL, tracker.mLastStartTime);
+    EXPECT_EQ(10LL, tracker.mDuration);
+
+    EXPECT_EQ(0u, tracker.mStarted.size());
+
+    tracker.noteStart("", true, eventStartTimeNs + 20, key1);
+    EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+    EXPECT_EQ((long long)(51ULL * NS_PER_SEC),
+              (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
+    tracker.noteStop("", eventStartTimeNs + 2 * bucketSizeNs + 25, false);
+    EXPECT_EQ(anomalyTracker->getSumOverPastBuckets("event"), (long long)(bucketSizeNs));
+    EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25),
+              anomalyTracker->mLastAlarmTimestampNs);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index cd647cb..1ed3636 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -277,7 +277,7 @@
     EXPECT_EQ(20, curInterval.raw.back().first);
     EXPECT_EQ(0UL, valueProducer.mNextSlicedBucket.size());
 
-    valueProducer.flush_if_needed(bucket3StartTimeNs);
+    valueProducer.flushIfNeeded(bucket3StartTimeNs);
     EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
     EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
     EXPECT_EQ(30, valueProducer.mPastBuckets.begin()->second.back().mValue);
diff --git a/cmds/webview_zygote/Android.mk b/cmds/webview_zygote/Android.mk
index 66e762c..955e58e 100644
--- a/cmds/webview_zygote/Android.mk
+++ b/cmds/webview_zygote/Android.mk
@@ -21,6 +21,8 @@
 
 LOCAL_SRC_FILES := webview_zygote.cpp
 
+LOCAL_CFLAGS := -Wall -Werror
+
 LOCAL_SHARED_LIBRARIES := \
 	libandroid_runtime \
 	libbinder \
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index e491a4f..da5569d 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -359,6 +359,8 @@
             if (DEBUG_PARCEL) Log.d(TAG, "Finished reading: at " + mCurParcel.dataPosition()
                     + ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows
                     + ", views=" + mNumReadViews);
+            mCurParcel.recycle();
+            mCurParcel = null; // Parcel cannot be used after recycled.
         }
 
         Parcel readParcel(int validateToken, int level) {
@@ -396,20 +398,23 @@
 
         private void fetchData() {
             Parcel data = Parcel.obtain();
-            data.writeInterfaceToken(DESCRIPTOR);
-            data.writeStrongBinder(mTransferToken);
-            if (DEBUG_PARCEL) Log.d(TAG, "Requesting data with token " + mTransferToken);
-            if (mCurParcel != null) {
-                mCurParcel.recycle();
-            }
-            mCurParcel = Parcel.obtain();
             try {
-                mChannel.transact(TRANSACTION_XFER, data, mCurParcel, 0);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failure reading AssistStructure data", e);
-                throw new IllegalStateException("Failure reading AssistStructure data: " + e);
+                data.writeInterfaceToken(DESCRIPTOR);
+                data.writeStrongBinder(mTransferToken);
+                if (DEBUG_PARCEL) Log.d(TAG, "Requesting data with token " + mTransferToken);
+                if (mCurParcel != null) {
+                    mCurParcel.recycle();
+                }
+                mCurParcel = Parcel.obtain();
+                try {
+                    mChannel.transact(TRANSACTION_XFER, data, mCurParcel, 0);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failure reading AssistStructure data", e);
+                    throw new IllegalStateException("Failure reading AssistStructure data: " + e);
+                }
+            } finally {
+                data.recycle();
             }
-            data.recycle();
             mNumReadWindows = mNumReadViews = 0;
         }
     }
diff --git a/core/java/android/net/metrics/WakeupEvent.java b/core/java/android/net/metrics/WakeupEvent.java
index 8f1a5c4..af9a73c 100644
--- a/core/java/android/net/metrics/WakeupEvent.java
+++ b/core/java/android/net/metrics/WakeupEvent.java
@@ -29,7 +29,7 @@
     public String iface;
     public int uid;
     public int ethertype;
-    public byte[] dstHwAddr;
+    public MacAddress dstHwAddr;
     public String srcIp;
     public String dstIp;
     public int ipNextHeader;
@@ -44,7 +44,7 @@
         j.add(iface);
         j.add("uid: " + Integer.toString(uid));
         j.add("eth=0x" + Integer.toHexString(ethertype));
-        j.add("dstHw=" + MacAddress.stringAddrFromByteAddr(dstHwAddr));
+        j.add("dstHw=" + dstHwAddr);
         if (ipNextHeader > 0) {
             j.add("ipNxtHdr=" + ipNextHeader);
             j.add("srcIp=" + srcIp);
diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java
index 1ba9777..23c1f20 100644
--- a/core/java/android/net/metrics/WakeupStats.java
+++ b/core/java/android/net/metrics/WakeupStats.java
@@ -16,7 +16,6 @@
 
 package android.net.metrics;
 
-import android.net.MacAddress;
 import android.os.Process;
 import android.os.SystemClock;
 import android.util.SparseIntArray;
@@ -80,7 +79,7 @@
                 break;
         }
 
-        switch (MacAddress.macAddressType(ev.dstHwAddr)) {
+        switch (ev.dstHwAddr.addressType()) {
             case UNICAST:
                 l2UnicastCount++;
                 break;
diff --git a/core/java/android/service/autofill/InternalSanitizer.java b/core/java/android/service/autofill/InternalSanitizer.java
index 95d2f66..d77e41e 100644
--- a/core/java/android/service/autofill/InternalSanitizer.java
+++ b/core/java/android/service/autofill/InternalSanitizer.java
@@ -16,6 +16,7 @@
 package android.service.autofill;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.os.Parcelable;
 import android.view.autofill.AutofillValue;
@@ -32,7 +33,11 @@
     /**
      * Sanitizes an {@link AutofillValue}.
      *
+     * @return sanitized value or {@code null} if value could not be sanitized (for example: didn't
+     * match regex, it's an invalid type, regex failed, etc).
+     *
      * @hide
      */
+    @Nullable
     public abstract AutofillValue sanitize(@NonNull AutofillValue value);
 }
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 9a1dcbb..bc4b3fc 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -613,6 +613,11 @@
          *         usernameId, passwordId);
          * </pre>
          *
+         * <p>The sanitizer can also be used as an alternative for a
+         * {@link #setValidator(Validator) validator}&mdashif any of the {@code ids} is a
+         * {@link #SaveInfo.Builder(int, AutofillId[]) required id} and the {@code sanitizer} fail
+         * for it, then the save UI is not shown.
+         *
          * @param sanitizer an implementation provided by the Android System.
          * @param ids id of fields whose value will be sanitized.
          * @return this builder.
diff --git a/core/java/android/service/autofill/TextValueSanitizer.java b/core/java/android/service/autofill/TextValueSanitizer.java
index 12e85b1..a3a98d8 100644
--- a/core/java/android/service/autofill/TextValueSanitizer.java
+++ b/core/java/android/service/autofill/TextValueSanitizer.java
@@ -19,6 +19,7 @@
 import static android.view.autofill.Helper.sDebug;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -62,24 +63,31 @@
     /** @hide */
     @Override
     @TestApi
+    @Nullable
     public AutofillValue sanitize(@NonNull AutofillValue value) {
         if (value == null) {
             Slog.w(TAG, "sanitize() called with null value");
             return null;
         }
-        if (!value.isText()) return value;
+        if (!value.isText()) {
+            if (sDebug) Slog.d(TAG, "sanitize() called with non-text value: " + value);
+            return null;
+        }
 
         final CharSequence text = value.getTextValue();
 
         try {
             final Matcher matcher = mRegex.matcher(text);
-            if (!matcher.matches()) return value;
+            if (!matcher.matches()) {
+                if (sDebug) Slog.d(TAG, "sanitize(): " + mRegex + " failed for " + value);
+                return null;
+            }
 
             final CharSequence sanitized = matcher.replaceAll(mSubst);
             return AutofillValue.forText(sanitized);
         } catch (Exception e) {
             Slog.w(TAG, "Exception evaluating " + mRegex + "/" + mSubst + ": " + e);
-            return value;
+            return null;
         }
     }
 
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 5641009..aa2f1c1 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -61,6 +61,8 @@
     private static native long nativeCreateTransaction();
     private static native long nativeGetNativeTransactionFinalizer();
     private static native void nativeApplyTransaction(long transactionObj, boolean sync);
+    private static native void nativeMergeTransaction(long transactionObj,
+            long otherTransactionObj);
     private static native void nativeSetAnimationTransaction(long transactionObj);
 
     private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder);
@@ -654,6 +656,19 @@
         }
     }
 
+    /**
+     * Merge the supplied transaction in to the deprecated "global" transaction.
+     * This clears the supplied transaction in an identical fashion to {@link Transaction#merge}.
+     * <p>
+     * This is a utility for interop with legacy-code and will go away with the Global Transaction.
+     */
+    @Deprecated
+    public static void mergeToGlobalTransaction(Transaction t) {
+        synchronized(sGlobalTransaction) {
+            sGlobalTransaction.merge(t);
+        }
+    }
+
     /** end a transaction */
     public static void closeTransaction() {
         closeTransaction(false);
@@ -1368,7 +1383,7 @@
          * Sets the security of the surface.  Setting the flag is equivalent to creating the
          * Surface with the {@link #SECURE} flag.
          */
-        Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+        public Transaction setSecure(SurfaceControl sc, boolean isSecure) {
             sc.checkNotReleased();
             if (isSecure) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
@@ -1449,5 +1464,14 @@
             nativeSetAnimationTransaction(mNativeObject);
             return this;
         }
+
+        /**
+         * Merge the other transaction into this transaction, clearing the
+         * other transaction as if it had been applied.
+         */
+        public Transaction merge(Transaction other) {
+            nativeMergeTransaction(mNativeObject, other.mNativeObject);
+            return this;
+        }
     }
 }
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 534335b..c192f5c 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1663,14 +1663,6 @@
     void writeToProto(ProtoOutputStream proto, long fieldId);
 
     /**
-     * Returns whether a given window type can be magnified.
-     *
-     * @param windowType The window type.
-     * @return True if the window can be magnified.
-     */
-    public boolean canMagnifyWindow(int windowType);
-
-    /**
      * Returns whether a given window type is considered a top level one.
      * A top level window does not have a container, i.e. attached window,
      * or if it has a container it is laid out as a top-level window, not
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 4cf3461..63519a6 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -34,6 +34,8 @@
     private final UserInfo mUserInfo;
     private final PackageInfo mPackageInfo;
 
+    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.O_MR1;
+
     public UserPackage(UserInfo user, PackageInfo packageInfo) {
         this.mUserInfo = user;
         this.mPackageInfo = packageInfo;
@@ -83,7 +85,7 @@
      * supported by the current framework version.
      */
     public static boolean hasCorrectTargetSdkVersion(PackageInfo packageInfo) {
-        return packageInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O_MR1;
+        return packageInfo.applicationInfo.targetSdkVersion >= MINIMUM_SUPPORTED_SDK;
     }
 
     public UserInfo getUserInfo() {
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 203de9c..e493739 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -29,10 +29,10 @@
 /**
  * Manages settings state for a WebView. When a WebView is first created, it
  * obtains a set of default settings. These default settings will be returned
- * from any getter call. A WebSettings object obtained from
- * WebView.getSettings() is tied to the life of the WebView. If a WebView has
- * been destroyed, any method call on WebSettings will throw an
- * IllegalStateException.
+ * from any getter call. A {@code WebSettings} object obtained from
+ * {@link WebView#getSettings()} is tied to the life of the WebView. If a WebView has
+ * been destroyed, any method call on {@code WebSettings} will throw an
+ * {@link IllegalStateException}.
  */
 // This is an abstract base class: concrete WebViewProviders must
 // create a class derived from this, and return an instance of it in the
@@ -41,13 +41,13 @@
     /**
      * Enum for controlling the layout of html.
      * <ul>
-     *   <li>NORMAL means no rendering changes. This is the recommended choice for maximum
+     *   <li>{@code NORMAL} means no rendering changes. This is the recommended choice for maximum
      *       compatibility across different platforms and Android versions.</li>
-     *   <li>SINGLE_COLUMN moves all content into one column that is the width of the
+     *   <li>{@code SINGLE_COLUMN} moves all content into one column that is the width of the
      *       view.</li>
-     *   <li>NARROW_COLUMNS makes all columns no wider than the screen if possible. Only use
+     *   <li>{@code NARROW_COLUMNS} makes all columns no wider than the screen if possible. Only use
      *       this for API levels prior to {@link android.os.Build.VERSION_CODES#KITKAT}.</li>
-     *   <li>TEXT_AUTOSIZING boosts font size of paragraphs based on heuristics to make
+     *   <li>{@code TEXT_AUTOSIZING} boosts font size of paragraphs based on heuristics to make
      *       the text readable when viewing a wide-viewport layout in the overview mode.
      *       It is recommended to enable zoom support {@link #setSupportZoom} when
      *       using this mode. Supported from API level
@@ -98,9 +98,9 @@
     /**
      * Enum for specifying the WebView's desired density.
      * <ul>
-     *   <li>FAR makes 100% looking like in 240dpi</li>
-     *   <li>MEDIUM makes 100% looking like in 160dpi</li>
-     *   <li>CLOSE makes 100% looking like in 120dpi</li>
+     *   <li>{@code FAR} makes 100% looking like in 240dpi</li>
+     *   <li>{@code MEDIUM} makes 100% looking like in 160dpi</li>
+     *   <li>{@code CLOSE} makes 100% looking like in 120dpi</li>
      * </ul>
      */
     public enum ZoomDensity {
@@ -652,7 +652,7 @@
      * true, {@link WebChromeClient#onCreateWindow} must be implemented by the
      * host application. The default is {@code false}.
      *
-     * @param support whether to suport multiple windows
+     * @param support whether to support multiple windows
      */
     public abstract void setSupportMultipleWindows(boolean support);
 
@@ -665,7 +665,7 @@
     public abstract boolean supportMultipleWindows();
 
     /**
-     * Sets the underlying layout algorithm. This will cause a relayout of the
+     * Sets the underlying layout algorithm. This will cause a re-layout of the
      * WebView. The default is {@link LayoutAlgorithm#NARROW_COLUMNS}.
      *
      * @param l the layout algorithm to use, as a {@link LayoutAlgorithm} value
@@ -1198,7 +1198,7 @@
 
     /**
      * Tells JavaScript to open windows automatically. This applies to the
-     * JavaScript function window.open(). The default is {@code false}.
+     * JavaScript function {@code window.open()}. The default is {@code false}.
      *
      * @param flag {@code true} if JavaScript can open windows automatically
      */
@@ -1208,7 +1208,7 @@
      * Gets whether JavaScript can open windows automatically.
      *
      * @return {@code true} if JavaScript can open windows automatically during
-     *         window.open()
+     *         {@code window.open()}
      * @see #setJavaScriptCanOpenWindowsAutomatically
      */
     public abstract boolean getJavaScriptCanOpenWindowsAutomatically();
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 7abc76a..408a4e9 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -9556,7 +9556,7 @@
             if (vScroll == 0 && hScroll == 0) {
                 return false;
             }
-            mRecyclerView.scrollBy(hScroll, vScroll);
+            mRecyclerView.smoothScrollBy(hScroll, vScroll);
             return true;
         }
 
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 4e88a83..d7300c4 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -36,7 +36,7 @@
 #include <hwui/MinikinUtils.h>
 #include <hwui/Paint.h>
 #include <minikin/FontCollection.h>
-#include <minikin/LineBreaker.h>
+#include <minikin/AndroidLineBreakerHelper.h>
 #include <minikin/MinikinFont.h>
 
 namespace android {
@@ -52,63 +52,6 @@
 static jclass gLineBreaks_class;
 static JLineBreaksID gLineBreaks_fieldID;
 
-class JNILineBreakerLineWidth : public minikin::LineBreaker::LineWidthDelegate {
-    public:
-        JNILineBreakerLineWidth(float firstWidth, int32_t firstLineCount, float restWidth,
-                const std::vector<float>& indents, const std::vector<float>& leftPaddings,
-                const std::vector<float>& rightPaddings, int32_t indentsAndPaddingsOffset)
-            : mFirstWidth(firstWidth), mFirstLineCount(firstLineCount), mRestWidth(restWidth),
-              mIndents(indents), mLeftPaddings(leftPaddings),
-              mRightPaddings(rightPaddings), mOffset(indentsAndPaddingsOffset) {}
-
-        float getLineWidth(size_t lineNo) override {
-            const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount)
-                    ? mFirstWidth : mRestWidth;
-            return width - get(mIndents, lineNo);
-        }
-
-        float getMinLineWidth() override {
-            // A simpler algorithm would have been simply looping until the larger of
-            // mFirstLineCount and mIndents.size()-mOffset, but that does unnecessary calculations
-            // when mFirstLineCount is large. Instead, we measure the first line, all the lines that
-            // have an indent, and the first line after firstWidth ends and restWidth starts.
-            float minWidth = std::min(getLineWidth(0), getLineWidth(mFirstLineCount));
-            for (size_t lineNo = 1; lineNo + mOffset < mIndents.size(); lineNo++) {
-                minWidth = std::min(minWidth, getLineWidth(lineNo));
-            }
-            return minWidth;
-        }
-
-        float getLeftPadding(size_t lineNo) override {
-            return get(mLeftPaddings, lineNo);
-        }
-
-        float getRightPadding(size_t lineNo) override {
-            return get(mRightPaddings, lineNo);
-        }
-
-    private:
-        float get(const std::vector<float>& vec, size_t lineNo) {
-            if (vec.empty()) {
-                return 0;
-            }
-            const size_t index = lineNo + mOffset;
-            if (index < vec.size()) {
-                return vec[index];
-            } else {
-                return vec.back();
-            }
-        }
-
-        const float mFirstWidth;
-        const int32_t mFirstLineCount;
-        const float mRestWidth;
-        const std::vector<float>& mIndents;
-        const std::vector<float>& mLeftPaddings;
-        const std::vector<float>& mRightPaddings;
-        const int32_t mOffset;
-};
-
 static inline std::vector<float> jintArrayToFloatVector(JNIEnv* env, jintArray javaArray) {
     if (javaArray == nullptr) {
          return std::vector<float>();
@@ -118,109 +61,8 @@
     }
 }
 
-class Run {
-    public:
-        Run(int32_t start, int32_t end) : mRange(start, end) {}
-        virtual ~Run() {}
-
-        virtual void addTo(minikin::LineBreaker* lineBreaker) = 0;
-
-    protected:
-        minikin::Range mRange;
-
-    private:
-        // Forbid copy and assign.
-        Run(const Run&) = delete;
-        void operator=(const Run&) = delete;
-};
-
-class StyleRun : public Run {
-    public:
-        StyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
-                std::shared_ptr<minikin::FontCollection>&& collection, bool isRtl)
-            : Run(start, end), mPaint(std::move(paint)), mCollection(std::move(collection)),
-              mIsRtl(isRtl) {}
-
-        void addTo(minikin::LineBreaker* lineBreaker) override {
-            lineBreaker->addStyleRun(&mPaint, mCollection, mRange, mIsRtl);
-        }
-
-    private:
-        minikin::MinikinPaint mPaint;
-        std::shared_ptr<minikin::FontCollection> mCollection;
-        const bool mIsRtl;
-};
-
-class Replacement : public Run {
-    public:
-        Replacement(int32_t start, int32_t end, float width, uint32_t localeListId)
-            : Run(start, end), mWidth(width), mLocaleListId(localeListId) {}
-
-        void addTo(minikin::LineBreaker* lineBreaker) override {
-            lineBreaker->addReplacement(mRange, mWidth, mLocaleListId);
-        }
-
-    private:
-        const float mWidth;
-        const uint32_t mLocaleListId;
-};
-
-class StaticLayoutNative {
-    public:
-        StaticLayoutNative(
-                minikin::BreakStrategy strategy, minikin::HyphenationFrequency frequency,
-                bool isJustified, std::vector<float>&& indents, std::vector<float>&& leftPaddings,
-                std::vector<float>&& rightPaddings)
-            : mStrategy(strategy), mFrequency(frequency), mIsJustified(isJustified),
-              mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)),
-              mRightPaddings(std::move(rightPaddings)) {}
-
-        void addStyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
-                         std::shared_ptr<minikin::FontCollection> collection, bool isRtl) {
-            mRuns.emplace_back(std::make_unique<StyleRun>(
-                    start, end, std::move(paint), std::move(collection), isRtl));
-        }
-
-        void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) {
-            mRuns.emplace_back(std::make_unique<Replacement>(start, end, width, localeListId));
-        }
-
-        // Only valid while this instance is alive.
-        inline std::unique_ptr<minikin::LineBreaker::LineWidthDelegate> buildLineWidthDelegate(
-                float firstWidth, int32_t firstLineCount, float restWidth,
-                int32_t indentsAndPaddingsOffset) {
-            return std::make_unique<JNILineBreakerLineWidth>(
-                firstWidth, firstLineCount, restWidth, mIndents, mLeftPaddings, mRightPaddings,
-                indentsAndPaddingsOffset);
-        }
-
-        void addRuns(minikin::LineBreaker* lineBreaker) {
-            for (const auto& run : mRuns) {
-                run->addTo(lineBreaker);
-            }
-        }
-
-        void clearRuns() {
-            mRuns.clear();
-        }
-
-        inline minikin::BreakStrategy getStrategy() const { return mStrategy; }
-        inline minikin::HyphenationFrequency getFrequency() const { return mFrequency; }
-        inline bool isJustified() const { return mIsJustified; }
-
-    private:
-        const minikin::BreakStrategy mStrategy;
-        const minikin::HyphenationFrequency mFrequency;
-        const bool mIsJustified;
-        const std::vector<float> mIndents;
-        const std::vector<float> mLeftPaddings;
-        const std::vector<float> mRightPaddings;
-
-        std::vector<std::unique_ptr<Run>> mRuns;
-};
-
-static inline StaticLayoutNative* toNative(jlong ptr) {
-    return reinterpret_cast<StaticLayoutNative*>(ptr);
+static inline minikin::android::StaticLayoutNative* toNative(jlong ptr) {
+    return reinterpret_cast<minikin::android::StaticLayoutNative*>(ptr);
 }
 
 // set text and set a number of parameters for creating a layout (width, tabstops, strategy,
@@ -228,7 +70,7 @@
 static jlong nInit(JNIEnv* env, jclass /* unused */,
         jint breakStrategy, jint hyphenationFrequency, jboolean isJustified,
         jintArray indents, jintArray leftPaddings, jintArray rightPaddings) {
-    return reinterpret_cast<jlong>(new StaticLayoutNative(
+    return reinterpret_cast<jlong>(new minikin::android::StaticLayoutNative(
             static_cast<minikin::BreakStrategy>(breakStrategy),
             static_cast<minikin::HyphenationFrequency>(hyphenationFrequency),
             isJustified,
@@ -291,7 +133,7 @@
         jintArray recycleFlags,
         jfloatArray charWidths) {
 
-    StaticLayoutNative* builder = toNative(nativePtr);
+    minikin::android::StaticLayoutNative* builder = toNative(nativePtr);
 
     ScopedCharArrayRO text(env, javaText);
 
@@ -327,7 +169,7 @@
 // Basically similar to Paint.getTextRunAdvances but with C++ interface
 // CriticalNative
 static void nAddStyleRun(jlong nativePtr, jlong nativePaint, jint start, jint end, jboolean isRtl) {
-    StaticLayoutNative* builder = toNative(nativePtr);
+    minikin::android::StaticLayoutNative* builder = toNative(nativePtr);
     Paint* paint = reinterpret_cast<Paint*>(nativePaint);
     const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
     minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
@@ -337,7 +179,7 @@
 // CriticalNative
 static void nAddReplacementRun(jlong nativePtr, jlong nativePaint, jint start, jint end,
         jfloat width) {
-    StaticLayoutNative* builder = toNative(nativePtr);
+    minikin::android::StaticLayoutNative* builder = toNative(nativePtr);
     Paint* paint = reinterpret_cast<Paint*>(nativePaint);
     builder->addReplacementRun(start, end, width, paint->getMinikinLocaleListId());
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index cfeba83..bb1bfad 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -311,6 +311,14 @@
     transaction->apply(sync);
 }
 
+static void nativeMergeTransaction(JNIEnv* env, jclass clazz,
+        jlong transactionObj, jlong otherTransactionObj) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    auto otherTransaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(
+            otherTransactionObj);
+    transaction->merge(std::move(*otherTransaction));
+}
+
 static void nativeSetAnimationTransaction(JNIEnv* env, jclass clazz, jlong transactionObj) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     transaction->setAnimationTransaction();
@@ -882,6 +890,8 @@
             (void*)nativeApplyTransaction },
     {"nativeGetNativeTransactionFinalizer", "()J",
             (void*)nativeGetNativeTransactionFinalizer },
+    {"nativeMergeTransaction", "(JJ)V",
+            (void*)nativeMergeTransaction },
     {"nativeSetAnimationTransaction", "(J)V",
             (void*)nativeSetAnimationTransaction },
     {"nativeSetLayer", "(JJI)V",
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index dddd52b..cea9cd7 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -28,7 +28,7 @@
     <string name="unknownName" msgid="6867811765370350269">"غير معروف"</string>
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"البريد الصوتي"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
-    <string name="mmiError" msgid="5154499457739052907">"‏حدثت مشكلة في الاتصال أو أن كود MMI غير صحيح."</string>
+    <string name="mmiError" msgid="5154499457739052907">"‏حدثت مشكلة في الاتصال أو أن رمز MMI غير صحيح."</string>
     <string name="mmiFdnError" msgid="5224398216385316471">"تم تقييد التشغيل لأرقام الاتصال الثابت فقط."</string>
     <string name="mmiErrorWhileRoaming" msgid="762488890299284230">"يتعذر تغيير إعدادات إعادة توجيه المكالمات من هاتفك أثناء التجوال."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"تم تمكين الخدمة."</string>
@@ -43,7 +43,7 @@
     <string name="mismatchPin" msgid="609379054496863419">"أرقام التعريف الشخصية التي كتبتها غير مطابقة."</string>
     <string name="invalidPin" msgid="3850018445187475377">"ادخل رقم تعريف شخصي مكون من ٤ إلى ٨ أرقام."</string>
     <string name="invalidPuk" msgid="8761456210898036513">"‏اكتب رمز PUK مكونًا من ٨ أرقام أو أكثر."</string>
-    <string name="needPuk" msgid="919668385956251611">"‏شريحة SIM مؤمّنة بكود PUK. اكتب كود PUK لإلغاء تأمينها."</string>
+    <string name="needPuk" msgid="919668385956251611">"‏شريحة SIM مؤمّنة برمز PUK. اكتب رمز PUK لإلغاء تأمينها."</string>
     <string name="needPuk2" msgid="4526033371987193070">"‏اكتب PUK2 لإلغاء تأمين شريحة SIM."</string>
     <string name="enablePin" msgid="209412020907207950">"‏محاولة غير ناجحة، مكّن قفل SIM/RUIM."</string>
     <plurals name="pinpuk_attempts" formatted="false" msgid="1251012001539225582">
@@ -139,8 +139,8 @@
     <string name="cfTemplateForwardedTime" msgid="9206251736527085256">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> بعد <xliff:g id="TIME_DELAY">{2}</xliff:g> ثانية"</string>
     <string name="cfTemplateRegistered" msgid="5073237827620166285">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: لم تتم إعادة التوجيه"</string>
     <string name="cfTemplateRegisteredTime" msgid="6781621964320635172">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: لم تتم إعادة التوجيه"</string>
-    <string name="fcComplete" msgid="3118848230966886575">"اكتمل كود الميزة."</string>
-    <string name="fcError" msgid="3327560126588500777">"حدثت مشكلة بالاتصال أو أن كود الميزة غير صحيح."</string>
+    <string name="fcComplete" msgid="3118848230966886575">"اكتمل رمز الميزة."</string>
+    <string name="fcError" msgid="3327560126588500777">"حدثت مشكلة بالاتصال أو أن رمز الميزة غير صحيح."</string>
     <string name="httpErrorOk" msgid="1191919378083472204">"حسنًا"</string>
     <string name="httpError" msgid="7956392511146698522">"حدث خطأ في الشبكة."</string>
     <string name="httpErrorLookup" msgid="4711687456111963163">"‏تعذر العثور على عنوان URL."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 05ab42d..658e951 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1169,7 +1169,7 @@
     <string name="sim_done_button" msgid="827949989369963775">"Terminé"</string>
     <string name="sim_added_title" msgid="3719670512889674693">"Carte SIM ajoutée."</string>
     <string name="sim_added_message" msgid="6599945301141050216">"Redémarrez votre appareil pour accéder au réseau mobile."</string>
-    <string name="sim_restart_button" msgid="4722407842815232347">"Recommencer"</string>
+    <string name="sim_restart_button" msgid="4722407842815232347">"Redémarrer"</string>
     <string name="carrier_app_dialog_message" msgid="7066156088266319533">"Pour que la nouvelle carte SIM fonctionne correctement, vous devez installer et ouvrir une application fournie par votre fournisseur de services."</string>
     <string name="carrier_app_dialog_button" msgid="7900235513678617329">"TÉLÉCHARGER L\'APPLICATION"</string>
     <string name="carrier_app_dialog_not_now" msgid="6361378684292268027">"PAS MAINTENANT"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index cea220a..c866c4c 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -172,7 +172,7 @@
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Wasifu wako wa kazini haupatikani tena kwenye kifaa hiki"</string>
     <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Umejaribu kuweka nenosiri mara nyingi mno"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Kifaa kinadhibitiwa"</string>
-    <string name="network_logging_notification_text" msgid="7930089249949354026">"Shirika lako linadhibiti kifaa hiki na huenda likafuatilia shughuli kwenye mtandao. Gonga ili upate maelezo zaidi."</string>
+    <string name="network_logging_notification_text" msgid="7930089249949354026">"Shirika lako linadhibiti kifaa hiki na huenda likafuatilia shughuli kwenye mtandao. Gusa ili upate maelezo zaidi."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Data iliyomo kwenye kifaa chako itafutwa"</string>
     <string name="factory_reset_message" msgid="7972496262232832457">"Huwezi kutumia programu ya msimamizi. Sasa data iliyo kwenye kifaa chako itafutwa.\n\nIkiwa una maswali yoyote, wasiliana na msimamizi wa shirika lako."</string>
     <string name="me" msgid="6545696007631404292">"Mimi"</string>
@@ -251,7 +251,7 @@
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Programu zinazotumia betri"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumia betri"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Programu <xliff:g id="NUMBER">%1$d</xliff:g> zinatumia betri"</string>
-    <string name="foreground_service_tap_for_details" msgid="372046743534354644">"Gonga ili upate maelezo kuhusu betri na matumizi ya data"</string>
+    <string name="foreground_service_tap_for_details" msgid="372046743534354644">"Gusa ili upate maelezo kuhusu betri na matumizi ya data"</string>
     <string name="foreground_service_multiple_separator" msgid="4021901567939866542">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
     <string name="safeMode" msgid="2788228061547930246">"Mtindo salama"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Mfumo wa Android"</string>
@@ -807,7 +807,7 @@
     <string name="js_dialog_before_unload_negative_button" msgid="5614861293026099715">"Bakia kwenye Ukurasa huu"</string>
     <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nJe, una uhakika unataka kutoka kwenye ukurasa huu?"</string>
     <string name="save_password_label" msgid="6860261758665825069">"Thibitisha"</string>
-    <string name="double_tap_toast" msgid="4595046515400268881">"Kidokezo: Gonga mara mbili ili kukuza ndani na nje."</string>
+    <string name="double_tap_toast" msgid="4595046515400268881">"Kidokezo: Gusa mara mbili ili kukuza ndani na nje."</string>
     <string name="autofill_this_form" msgid="4616758841157816676">"Kujaza kiotomatiki"</string>
     <string name="setup_autofill" msgid="7103495070180590814">"Weka uwezo wa kujaza kiotomatiki"</string>
     <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
@@ -971,7 +971,7 @@
     <string name="undo" msgid="7905788502491742328">"Tendua"</string>
     <string name="redo" msgid="7759464876566803888">"Rejesha"</string>
     <string name="autofill" msgid="3035779615680565188">"Kujaza kiotomatiki"</string>
-    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Uchaguzi wa maandishi?"</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Maandishi yaliyoteuliwa"</string>
     <string name="addToDictionary" msgid="4352161534510057874">"Ongeza kwenye kamusi"</string>
     <string name="deleteText" msgid="6979668428458199034">"Futa"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Mbinu ya uingizaji"</string>
@@ -986,7 +986,7 @@
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Baadhi ya vipengee vya mfumo huenda visifanye kazi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Hifadhi haitoshi kwa ajili ya mfumo. Hakikisha una MB 250 za nafasi ya hifadhi isiyotumika na uanzishe upya."</string>
     <string name="app_running_notification_title" msgid="8718335121060787914">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumiwa"</string>
-    <string name="app_running_notification_text" msgid="1197581823314971177">"Gonga ili upate maelezo zaidi au usitishe programu."</string>
+    <string name="app_running_notification_text" msgid="1197581823314971177">"Gusa ili upate maelezo zaidi au usitishe programu."</string>
     <string name="ok" msgid="5970060430562524910">"Sawa"</string>
     <string name="cancel" msgid="6442560571259935130">"Ghairi"</string>
     <string name="yes" msgid="5362982303337969312">"Sawa"</string>
@@ -1062,7 +1062,7 @@
     <string name="android_upgrading_starting_apps" msgid="451464516346926713">"Programu zinaanza"</string>
     <string name="android_upgrading_complete" msgid="1405954754112999229">"Inamaliza kuwasha."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> inaendelea"</string>
-    <string name="heavy_weight_notification_detail" msgid="867643381388543170">"Gonga ili uende kwenye programu"</string>
+    <string name="heavy_weight_notification_detail" msgid="867643381388543170">"Gusa ili uende kwenye programu"</string>
     <string name="heavy_weight_switcher_title" msgid="7153167085403298169">"Badilisha programu?"</string>
     <string name="heavy_weight_switcher_text" msgid="7022631924534406403">"Programmu nyingine tayari inaendeshwa na lazima hiyo ikomeshwe kabla ya kuanza nyingine mpya."</string>
     <string name="old_app_action" msgid="493129172238566282">"Rejea katika <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
@@ -1107,7 +1107,7 @@
     <string name="wifi_available_title_connecting" msgid="1557292688310330032">"Inaunganisha kwenye mtandao wa Wi‑Fi unaotumiwa na mtu yeyote"</string>
     <string name="wifi_available_title_connected" msgid="7542672851522241548">"Imeunganisha kwenye mtandao wa Wi-Fi"</string>
     <string name="wifi_available_title_failed_to_connect" msgid="6861772233582618132">"Imeshindwa kuunganisha kwenye mtandao wa Wi‑Fi"</string>
-    <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Gonga ili uone mitandao yote"</string>
+    <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Gusa ili uone mitandao yote"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Unganisha"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Mitandao Yote"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Ingia kwa mtandao wa Wi-Fi"</string>
@@ -1115,7 +1115,7 @@
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
     <skip />
     <string name="wifi_no_internet" msgid="8451173622563841546">"Wi-Fi haina muunganisho wa intaneti"</string>
-    <string name="wifi_no_internet_detailed" msgid="8083079241212301741">"Gonga ili upate chaguo"</string>
+    <string name="wifi_no_internet_detailed" msgid="8083079241212301741">"Gusa ili upate chaguo"</string>
     <string name="network_switch_metered" msgid="4671730921726992671">"Sasa inatumia <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
     <string name="network_switch_metered_detail" msgid="5325661434777870353">"Kifaa hutumia <xliff:g id="NEW_NETWORK">%1$s</xliff:g> wakati <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> haina Intaneti. Huenda ukalipishwa."</string>
     <string name="network_switch_metered_toast" msgid="5779283181685974304">"Imebadilisha mtandao kutoka <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na sasa inatumia <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
@@ -1136,7 +1136,7 @@
     <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Anzisha Wi-Fi Moja kwa Moja. Hii itazima mteja/mtandao-hewa wa Wi-Fi."</string>
     <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Haikuweza kuanzisha Wi-Fi Moja kwa Moja."</string>
     <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi ya Moja kwa Moja imewashwa"</string>
-    <string name="wifi_p2p_enabled_notification_message" msgid="8064677407830620023">"Gonga ili uweke mipangilio"</string>
+    <string name="wifi_p2p_enabled_notification_message" msgid="8064677407830620023">"Gusa ili uweke mipangilio"</string>
     <string name="accept" msgid="1645267259272829559">"Kubali"</string>
     <string name="decline" msgid="2112225451706137894">"Kataa"</string>
     <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Mwaliko umetumwa"</string>
@@ -1172,7 +1172,7 @@
     <string name="carrier_app_dialog_button" msgid="7900235513678617329">"PATA PROGRAMU"</string>
     <string name="carrier_app_dialog_not_now" msgid="6361378684292268027">"SIYO SASA"</string>
     <string name="carrier_app_notification_title" msgid="8921767385872554621">"SIM mpya imewekwa"</string>
-    <string name="carrier_app_notification_text" msgid="1132487343346050225">"Gonga ili uiweke"</string>
+    <string name="carrier_app_notification_text" msgid="1132487343346050225">"Gusa ili uiweke"</string>
     <string name="time_picker_dialog_title" msgid="8349362623068819295">"Weka saa"</string>
     <string name="date_picker_dialog_title" msgid="5879450659453782278">"Weka tarehe"</string>
     <string name="date_time_set" msgid="5777075614321087758">"Weka"</string>
@@ -1188,11 +1188,11 @@
     <string name="usb_ptp_notification_title" msgid="1347328437083192112">"USB kwa ajili ya kuhamisha picha"</string>
     <string name="usb_midi_notification_title" msgid="4850904915889144654">"USB kwa ajili ya MIDI"</string>
     <string name="usb_accessory_notification_title" msgid="7848236974087653666">"Imeunganishwa kwa kifuasi cha USB"</string>
-    <string name="usb_notification_message" msgid="3370903770828407960">"Gonga ili upate chaguo zaidi."</string>
+    <string name="usb_notification_message" msgid="3370903770828407960">"Gusa ili upate chaguo zaidi."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Imetambua kifaa cha sauti ya analogi"</string>
-    <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Kifaa ulichoambatisha hakitumiki kwenye simu hii. Gonga ili upate maelezo zaidi."</string>
+    <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Kifaa ulichoambatisha hakitumiki kwenye simu hii. Gusa ili upate maelezo zaidi."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Utatuaji wa USB umeunganishwa"</string>
-    <string name="adb_active_notification_message" msgid="4948470599328424059">"Gonga ili uzime utatuaji wa USB."</string>
+    <string name="adb_active_notification_message" msgid="4948470599328424059">"Gusa ili uzime utatuaji wa USB."</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Chagua ili kulemaza utatuaji USB."</string>
     <string name="taking_remote_bugreport_notification_title" msgid="6742483073875060934">"Inatayarisha ripoti ya hitilafu…"</string>
     <string name="share_remote_bugreport_notification_title" msgid="4987095013583691873">"Ungependa kushiriki ripoti ya hitilafu?"</string>
@@ -1204,23 +1204,23 @@
     <string name="show_ime" msgid="2506087537466597099">"Ionyeshe kwenye skrini wakati kibodi halisi inatumika"</string>
     <string name="hardware" msgid="194658061510127999">"Onyesha kibodi pepe"</string>
     <string name="select_keyboard_layout_notification_title" msgid="597189518763083494">"Sanidi kibodi halisi"</string>
-    <string name="select_keyboard_layout_notification_message" msgid="8084622969903004900">"Gonga ili uchague lugha na muundo"</string>
+    <string name="select_keyboard_layout_notification_message" msgid="8084622969903004900">"Gusa ili uchague lugha na muundo"</string>
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="alert_windows_notification_channel_group_name" msgid="1463953341148606396">"Onyesha juu ya programu zingine"</string>
     <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> inachomoza juu ya programu zingine"</string>
     <string name="alert_windows_notification_title" msgid="3697657294867638947">"<xliff:g id="NAME">%s</xliff:g> inachomoza juu ya programu zingine."</string>
-    <string name="alert_windows_notification_message" msgid="8917232109522912560">"Ikiwa hutaki <xliff:g id="NAME">%s</xliff:g> kutumia kipengele hiki, gonga ili ufungue mipangilio na ukizime."</string>
+    <string name="alert_windows_notification_message" msgid="8917232109522912560">"Ikiwa hutaki <xliff:g id="NAME">%s</xliff:g> kutumia kipengele hiki, gusa ili ufungue mipangilio na ukizime."</string>
     <string name="alert_windows_notification_turn_off_action" msgid="3367294525884949878">"ZIMA"</string>
     <string name="ext_media_checking_notification_title" msgid="5734005953288045806">"Inaandaa <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_checking_notification_message" msgid="4747432538578886744">"Inakagua hitilafu"</string>
     <string name="ext_media_new_notification_message" msgid="7589986898808506239">"<xliff:g id="NAME">%s</xliff:g> mpya imegunduliwa"</string>
     <string name="ext_media_ready_notification_message" msgid="4083398150380114462">"Kwa ajili ya kuhamisha picha na maudhui"</string>
     <string name="ext_media_unmountable_notification_title" msgid="8295123366236989588">"<xliff:g id="NAME">%s</xliff:g> iliyoharibika"</string>
-    <string name="ext_media_unmountable_notification_message" msgid="2343202057122495773">"<xliff:g id="NAME">%s</xliff:g> ina hitilafu. Gonga ili uirekebishe."</string>
+    <string name="ext_media_unmountable_notification_message" msgid="2343202057122495773">"<xliff:g id="NAME">%s</xliff:g> ina hitilafu. Gusa ili uirekebishe."</string>
     <string name="ext_media_unmountable_notification_message" product="tv" msgid="3941179940297874950">"<xliff:g id="NAME">%s</xliff:g> imeharibika. Ichague ili uirekebishe."</string>
     <string name="ext_media_unsupported_notification_title" msgid="3797642322958803257">"<xliff:g id="NAME">%s</xliff:g> isiyotumika"</string>
-    <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"Kifaa hiki hakitumii <xliff:g id="NAME">%s</xliff:g>. Gonga ili uweke mipangilio ya muundo unaoweza kutumika."</string>
+    <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"Kifaa hiki hakitumii <xliff:g id="NAME">%s</xliff:g>. Gusa ili uweke mipangilio ya muundo unaoweza kutumika."</string>
     <string name="ext_media_unsupported_notification_message" product="tv" msgid="3725436899820390906">"Kifaa hiki hakitumii <xliff:g id="NAME">%s</xliff:g> hii. Ichague ili uweke muundo unaotumika."</string>
     <string name="ext_media_badremoval_notification_title" msgid="3206248947375505416">"<xliff:g id="NAME">%s</xliff:g> imeondolewa bila kutarajiwa"</string>
     <string name="ext_media_badremoval_notification_message" msgid="380176703346946313">"Ondoa <xliff:g id="NAME">%s</xliff:g> kabla ya kuchomoa ili kuepuka kupoteza data"</string>
@@ -1261,7 +1261,7 @@
     <string name="permdesc_requestDeletePackages" msgid="3406172963097595270">"Huruhusu programu kuomba idhini ya kufuta vifurushi."</string>
     <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"omba kupuuza uimarishji wa betri"</string>
     <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Huruhusu programu kuomba ruhusa ya kupuuza uimarishaji wa betri katika programu yako."</string>
-    <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Gonga mara mbili kwa udhibiti wa kuza"</string>
+    <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Gusa mara mbili kwa udhibiti wa kuza"</string>
     <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Haikuweza kuongeza wijeti."</string>
     <string name="ime_action_go" msgid="8320845651737369027">"Nenda"</string>
     <string name="ime_action_search" msgid="658110271822807811">"Tafuta"</string>
@@ -1292,8 +1292,8 @@
     <string name="notification_ranker_binding_label" msgid="774540592299064747">"Huduma ya kupanga arifa"</string>
     <string name="vpn_title" msgid="19615213552042827">"VPN imewezeshwa"</string>
     <string name="vpn_title_long" msgid="6400714798049252294">"VPN imeamilishwa na <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="vpn_text" msgid="1610714069627824309">"Gonga ili kudhibiti mtandao."</string>
-    <string name="vpn_text_long" msgid="4907843483284977618">"Imeunganishwa kwa <xliff:g id="SESSION">%s</xliff:g>. Gonga ili kudhibiti mtandao"</string>
+    <string name="vpn_text" msgid="1610714069627824309">"Gusa ili kudhibiti mtandao."</string>
+    <string name="vpn_text_long" msgid="4907843483284977618">"Imeunganishwa kwa <xliff:g id="SESSION">%s</xliff:g>. Gusa ili kudhibiti mtandao"</string>
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Kila mara VPN iliyowashwa inaunganishwa…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Kila mara VPN iliyowashwa imeunganishwa"</string>
     <string name="vpn_lockdown_disconnected" msgid="735805531187559719">"Imeondolewa kwenye VPN iliyowashwa kila wakati"</string>
@@ -1304,9 +1304,9 @@
     <string name="reset" msgid="2448168080964209908">"Weka upya"</string>
     <string name="submit" msgid="1602335572089911941">"Wasilisha"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Mtindo wa gari umewezeshwa"</string>
-    <string name="car_mode_disable_notification_message" msgid="6301524980144350051">"Gonga ili ufunge hali ya garini."</string>
+    <string name="car_mode_disable_notification_message" msgid="6301524980144350051">"Gusa ili ufunge hali ya garini."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Kushiriki au kusambaza intaneti kumewashwa"</string>
-    <string name="tethered_notification_message" msgid="2113628520792055377">"Gonga ili uweke mipangilio."</string>
+    <string name="tethered_notification_message" msgid="2113628520792055377">"Gusa ili uweke mipangilio."</string>
     <string name="disable_tether_notification_title" msgid="7526977944111313195">"Umezima kipengele cha kusambaza mtandao"</string>
     <string name="disable_tether_notification_message" msgid="2913366428516852495">"Wasiliana na msimamizi wako ili upate maelezo zaidi"</string>
     <string name="back_button_label" msgid="2300470004503343439">"Nyuma"</string>
@@ -1383,7 +1383,7 @@
     <string name="storage_usb" msgid="3017954059538517278">"Hifadhi ya USB"</string>
     <string name="extract_edit_menu_button" msgid="8940478730496610137">"Badilisha"</string>
     <string name="data_usage_warning_title" msgid="3620440638180218181">"Tahadhari ya matumizi ya data"</string>
-    <string name="data_usage_warning_body" msgid="6660692274311972007">"Gonga ili uangalie matumizi na mipangilio."</string>
+    <string name="data_usage_warning_body" msgid="6660692274311972007">"Gusa ili uangalie matumizi na mipangilio."</string>
     <string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Kikomo data ya 2G-3G kimefikiwa"</string>
     <string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Kikomo cha data ya 4G kimefikiwa"</string>
     <string name="data_usage_mobile_limit_title" msgid="6561099244084267376">"Umefikisha kipimo cha juu cha data"</string>
@@ -1395,7 +1395,7 @@
     <string name="data_usage_wifi_limit_snoozed_title" msgid="8743856006384825974">"Taarifa za Wi-fi zimevuka kiwanga"</string>
     <string name="data_usage_limit_snoozed_body" msgid="7035490278298441767">"<xliff:g id="SIZE">%s</xliff:g> juu ya kikomo kilichobainishwa."</string>
     <string name="data_usage_restricted_title" msgid="5965157361036321914">"Data ya mandhari nyuma imezuiwa"</string>
-    <string name="data_usage_restricted_body" msgid="469866376337242726">"Gonga ili uondoe kizuizi."</string>
+    <string name="data_usage_restricted_body" msgid="469866376337242726">"Gusa ili uondoe kizuizi."</string>
     <string name="ssl_certificate" msgid="6510040486049237639">"Cheti cha usalama"</string>
     <string name="ssl_certificate_is_valid" msgid="6825263250774569373">"Cheti hiki ni halali."</string>
     <string name="issued_to" msgid="454239480274921032">"Kimetolewa kwa:"</string>
@@ -1594,7 +1594,7 @@
     <string name="reason_unknown" msgid="6048913880184628119">"haijulikani"</string>
     <string name="reason_service_unavailable" msgid="7824008732243903268">"Huduma ya uchapishaji haijawashwa"</string>
     <string name="print_service_installed_title" msgid="2246317169444081628">"Huduma ya <xliff:g id="NAME">%s</xliff:g> imesakinisha"</string>
-    <string name="print_service_installed_message" msgid="5897362931070459152">"Gonga ili uwashe"</string>
+    <string name="print_service_installed_message" msgid="5897362931070459152">"Gusa ili uwashe"</string>
     <string name="restr_pin_enter_admin_pin" msgid="8641662909467236832">"Weka PIN ya msimamizi"</string>
     <string name="restr_pin_enter_pin" msgid="3395953421368476103">"Ingiza PIN"</string>
     <string name="restr_pin_incorrect" msgid="8571512003955077924">"Sio sahihi"</string>
@@ -1722,12 +1722,12 @@
     <string name="new_sms_notification_title" msgid="8442817549127555977">"Una ujumbe mpya"</string>
     <string name="new_sms_notification_content" msgid="7002938807812083463">"Fungua programu ya SMS ili uweze kuangalia"</string>
     <string name="user_encrypted_title" msgid="9054897468831672082">"Huenda baadhi ya utendaji ukawa vikwazo"</string>
-    <string name="user_encrypted_message" msgid="4923292604515744267">"Gonga ili ufungue"</string>
+    <string name="user_encrypted_message" msgid="4923292604515744267">"Gusa ili ufungue"</string>
     <string name="user_encrypted_detail" msgid="5708447464349420392">"Data ya mtumiaji imefungwa"</string>
     <string name="profile_encrypted_detail" msgid="3700965619978314974">"Wasifu wa kazini umefungwa"</string>
-    <string name="profile_encrypted_message" msgid="6964994232310195874">"Gonga ili ufungue wasifu wa kazini"</string>
+    <string name="profile_encrypted_message" msgid="6964994232310195874">"Gusa ili ufungue wasifu wa kazini"</string>
     <string name="usb_mtp_launch_notification_title" msgid="8359219638312208932">"Imeunganishwa na <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
-    <string name="usb_mtp_launch_notification_description" msgid="8541876176425411358">"Gonga ili uangalie faili"</string>
+    <string name="usb_mtp_launch_notification_description" msgid="8541876176425411358">"Gusa ili uangalie faili"</string>
     <string name="pin_target" msgid="3052256031352291362">"Bandika"</string>
     <string name="unpin_target" msgid="3556545602439143442">"Bandua"</string>
     <string name="app_info" msgid="6856026610594615344">"Maelezo ya programu"</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9e0722b..7a3fa1a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -152,7 +152,7 @@
     <!-- Displayed to tell the user that they should switch their network preference. -->
     <string name="NetworkPreferenceSwitchTitle">Can\u2019t reach network</string>
     <!-- Displayed to tell the user that they should switch their network preference. -->
-    <string name="NetworkPreferenceSwitchSummary">To improve reception, try changing the type selected at Settings &gt; Network &amp; Internet &gt; Mobile networks &gt; Preferred network type."</string>
+    <string name="NetworkPreferenceSwitchSummary">To improve reception, try changing the type selected at Settings &gt; Network &amp; internet &gt; Mobile networks &gt; Preferred network type."</string>
     <!-- Displayed to tell the user that emergency calls might not be available. -->
     <string name="EmergencyCallWarningTitle">Wi\u2011Fi calling is active</string>
     <!-- Displayed to tell the user that emergency calls might not be available. -->
@@ -3014,7 +3014,7 @@
     <string name="network_available_sign_in_detailed"><xliff:g id="network_ssid">%1$s</xliff:g></string>
 
     <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's title. -->
-    <string name="wifi_no_internet">Wi-Fi has no Internet access</string>
+    <string name="wifi_no_internet">Wi-Fi has no internet access</string>
 
     <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
     <string name="wifi_no_internet_detailed">Tap for options</string>
@@ -3023,7 +3023,7 @@
     <string name="network_switch_metered">Switched to <xliff:g id="network_type">%1$s</xliff:g></string>
 
     <!-- A notification might be shown if the device switches to another network type (e.g., mobile data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the notification's message. %1$s is the network that the device switched to, e.g., cellular data. %2$s is the network type the device switched from, e.g., Wi-Fi. Both are strings in the network_switch_type_name array. -->
-    <string name="network_switch_metered_detail">Device uses <xliff:g id="new_network">%1$s</xliff:g> when <xliff:g id="previous_network">%2$s</xliff:g> has no Internet access. Charges may apply.</string>
+    <string name="network_switch_metered_detail">Device uses <xliff:g id="new_network">%1$s</xliff:g> when <xliff:g id="previous_network">%2$s</xliff:g> has no internet access. Charges may apply.</string>
 
     <!-- A toast might be shown if the device switches to another network type (e.g., mobile data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the text of the toast. %1$s is the network that the device switched from, e.g., Wi-Fi. %2$s is the network type the device switched from, e.g., cellular data. Both are strings in the network_switch_type_name array. -->
     <string name="network_switch_metered_toast">Switched from <xliff:g id="previous_network">%1$s</xliff:g> to <xliff:g id="new_network">%2$s</xliff:g></string>
@@ -3043,7 +3043,7 @@
      <!-- A notification is shown when a user's selected SSID is later disabled due to connectivity problems.  This is the notification's title / ticker. -->
      <string name="wifi_watchdog_network_disabled">Couldn\'t connect to Wi-Fi</string>
      <!-- A notification is shown when a user's selected SSID is later disabled due to connectivity problems.  The complete alert msg is: <hotspot name> + this string, i.e. "Linksys has a poor internet connection" -->
-    <string name="wifi_watchdog_network_disabled_detailed">\u0020has a poor Internet connection.</string>
+    <string name="wifi_watchdog_network_disabled_detailed">\u0020has a poor internet connection.</string>
 
     <!-- Do not translate. Default access point SSID used for tethering -->
     <string name="wifi_tether_configure_ssid_default" translatable="false">AndroidAP</string>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 209f364..dad24da 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -115,10 +115,14 @@
     <family lang="und-Hebr">
         <font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
     </family>
     <family lang="und-Thai" variant="elegant">
         <font weight="400" style="normal">NotoSansThai-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifThai-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
     </family>
     <family lang="und-Thai" variant="compact">
         <font weight="400" style="normal">NotoSansThaiUI-Regular.ttf</font>
@@ -127,14 +131,20 @@
     <family lang="und-Armn">
         <font weight="400" style="normal">NotoSansArmenian-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansArmenian-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifArmenian-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifArmenian-Bold.ttf</font>
     </family>
     <family lang="und-Geor und-Geok">
         <font weight="400" style="normal">NotoSansGeorgian-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansGeorgian-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifGeorgian-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifGeorgian-Bold.ttf</font>
     </family>
     <family lang="und-Deva" variant="elegant">
         <font weight="400" style="normal">NotoSansDevanagari-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansDevanagari-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifDevanagari-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifDevanagari-Bold.ttf</font>
     </family>
     <family lang="und-Deva" variant="compact">
         <font weight="400" style="normal">NotoSansDevanagariUI-Regular.ttf</font>
@@ -147,6 +157,8 @@
     <family lang="und-Gujr" variant="elegant">
         <font weight="400" style="normal">NotoSansGujarati-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifGujarati-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifGujarati-Bold.ttf</font>
     </family>
     <family lang="und-Gujr" variant="compact">
         <font weight="400" style="normal">NotoSansGujaratiUI-Regular.ttf</font>
@@ -163,6 +175,8 @@
     <family lang="und-Taml" variant="elegant">
         <font weight="400" style="normal">NotoSansTamil-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansTamil-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifTamil-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifTamil-Bold.ttf</font>
     </family>
     <family lang="und-Taml" variant="compact">
         <font weight="400" style="normal">NotoSansTamilUI-Regular.ttf</font>
@@ -171,6 +185,8 @@
     <family lang="und-Mlym" variant="elegant">
         <font weight="400" style="normal">NotoSansMalayalam-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansMalayalam-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifMalayalam-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifMalayalam-Bold.ttf</font>
     </family>
     <family lang="und-Mlym" variant="compact">
         <font weight="400" style="normal">NotoSansMalayalamUI-Regular.ttf</font>
@@ -179,6 +195,8 @@
     <family lang="und-Beng" variant="elegant">
         <font weight="400" style="normal">NotoSansBengali-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansBengali-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifBengali-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifBengali-Bold.ttf</font>
     </family>
     <family lang="und-Beng" variant="compact">
         <font weight="400" style="normal">NotoSansBengaliUI-Regular.ttf</font>
@@ -187,6 +205,8 @@
     <family lang="und-Telu" variant="elegant">
         <font weight="400" style="normal">NotoSansTelugu-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansTelugu-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifTelugu-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifTelugu-Bold.ttf</font>
     </family>
     <family lang="und-Telu" variant="compact">
         <font weight="400" style="normal">NotoSansTeluguUI-Regular.ttf</font>
@@ -195,6 +215,8 @@
     <family lang="und-Knda" variant="elegant">
         <font weight="400" style="normal">NotoSansKannada-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansKannada-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifKannada-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifKannada-Bold.ttf</font>
     </family>
     <family lang="und-Knda" variant="compact">
         <font weight="400" style="normal">NotoSansKannadaUI-Regular.ttf</font>
@@ -258,6 +280,8 @@
     <family lang="und-Laoo" variant="elegant">
         <font weight="400" style="normal">NotoSansLao-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifLao-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
     </family>
     <family lang="und-Laoo" variant="compact">
         <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf</font>
@@ -472,15 +496,19 @@
     </family>
     <family lang="zh-Hans">
         <font weight="400" style="normal" index="2">NotoSansCJK-Regular.ttc</font>
+        <font weight="400" style="normal" index="2" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font>
     </family>
     <family lang="zh-Hant zh-Bopo">
         <font weight="400" style="normal" index="3">NotoSansCJK-Regular.ttc</font>
+        <font weight="400" style="normal" index="3" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font>
     </family>
     <family lang="ja">
         <font weight="400" style="normal" index="0">NotoSansCJK-Regular.ttc</font>
+        <font weight="400" style="normal" index="0" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font>
     </family>
     <family lang="ko">
         <font weight="400" style="normal" index="1">NotoSansCJK-Regular.ttc</font>
+        <font weight="400" style="normal" index="1" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font>
     </family>
     <family lang="und-Zsye">
         <font weight="400" style="normal">NotoColorEmoji.ttf</font>
diff --git a/packages/CarrierDefaultApp/res/values-sw/strings.xml b/packages/CarrierDefaultApp/res/values-sw/strings.xml
index c546fcee..a52a733 100644
--- a/packages/CarrierDefaultApp/res/values-sw/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sw/strings.xml
@@ -5,7 +5,7 @@
     <string name="android_system_label" msgid="2797790869522345065">"Mtoa Huduma za Simu"</string>
     <string name="portal_notification_id" msgid="5155057562457079297">"Data ya mtandao wa simu imekwisha"</string>
     <string name="no_data_notification_id" msgid="668400731803969521">"Data yako ya mtandao wa simu imezimwa"</string>
-    <string name="portal_notification_detail" msgid="2295729385924660881">"Gonga ili utembelee tovuti ya %s"</string>
+    <string name="portal_notification_detail" msgid="2295729385924660881">"Gusa ili utembelee tovuti ya %s"</string>
     <string name="no_data_notification_detail" msgid="3112125343857014825">"Tafadhali wasiliana na mtoa huduma wako %s"</string>
     <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Hakuna muunganisho wa data kwa simu za mkononi"</string>
     <string name="no_mobile_data_connection" msgid="544980465184147010">"Ongeza mpango wa mitandao mingine au data kupitia %s"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index ec34253..e0a979d 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -24,7 +24,7 @@
     <string name="wifi_security_none" msgid="7985461072596594400">"لا شيء"</string>
     <string name="wifi_remembered" msgid="4955746899347821096">"تم الحفظ"</string>
     <string name="wifi_disabled_generic" msgid="4259794910584943386">"معطلة"</string>
-    <string name="wifi_disabled_network_failure" msgid="2364951338436007124">"‏أخفقت تهيئة عنوان IP"</string>
+    <string name="wifi_disabled_network_failure" msgid="2364951338436007124">"‏تعذّرت تهيئة عنوان IP"</string>
     <string name="wifi_disabled_by_recommendation_provider" msgid="5168315140978066096">"الجهاز غير متصل بسبب انخفاض جودة الشبكة"</string>
     <string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"‏تعذّر اتصال WiFi"</string>
     <string name="wifi_disabled_password_failure" msgid="8659805351763133575">"حدثت مشكلة في المصادقة"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index bb5d2d8..1b78d30 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"Impossible d\'établir l\'association avec <xliff:g id="DEVICE_NAME">%1$s</xliff:g> en raison d\'un NIP ou d\'une clé d\'accès incorrects."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"Impossible d\'établir la communication avec <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"Association refusée par <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"Ordinateur"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"Écouteurs"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"Téléphone"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"Imagerie"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"Écouteurs"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"Périphérique d\'entrée"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi désactivé."</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fi déconnecté."</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Wi-Fi : une barre."</string>
@@ -209,8 +216,12 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec audio Bluetooth LDAC : qualité de lecture"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Sélectionner le codec audio Bluetooth LDAC :\nQualité de lecture"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Diffusion : <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
-    <string name="dns_tls" msgid="6773814174391131955">"DNS par TLS"</string>
-    <string name="dns_tls_summary" msgid="3692494150251071380">"Si cette option est activée, essayez le DNS par TLS sur le port 853."</string>
+    <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"DNS privé"</string>
+    <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"Sélectionnez le mode DNS privé"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"Désactivé"</string>
+    <string name="private_dns_mode_opportunistic" msgid="7608409735589131766">"Opportuniste"</string>
+    <string name="private_dns_mode_provider" msgid="8354935160639360804">"Nom d\'hôte du fournisseur DNS privé"</string>
+    <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"Entrez le nom d\'hôte du fournisseur DNS"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Afficher les options pour la certification d\'affichage sans fil"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Détailler davantage les données Wi-Fi, afficher par SSID RSSI dans sélect. Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Si cette option est activée, le passage du Wi-Fi aux données cellulaires est forcé lorsque le signal Wi-Fi est faible"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 308e97d8..b66f95f 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -48,7 +48,7 @@
     <string name="speed_label_okay" msgid="2331665440671174858">"OK"</string>
     <string name="speed_label_medium" msgid="3175763313268941953">"普通"</string>
     <string name="speed_label_fast" msgid="7715732164050975057">"速い"</string>
-    <string name="speed_label_very_fast" msgid="2265363430784523409">"とても速い"</string>
+    <string name="speed_label_very_fast" msgid="2265363430784523409">"非常に速い"</string>
     <string name="preference_summary_default_combination" msgid="8532964268242666060">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="bluetooth_disconnected" msgid="6557104142667339895">"切断"</string>
     <string name="bluetooth_disconnecting" msgid="8913264760027764974">"切断中..."</string>
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"PINまたはパスキーが正しくないため、<xliff:g id="DEVICE_NAME">%1$s</xliff:g>をペアに設定できませんでした。"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>と通信できません。"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"ペア設定が<xliff:g id="DEVICE_NAME">%1$s</xliff:g>に拒否されました。"</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"コンピュータ"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"ヘッドセット"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"スマートフォン"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"画像"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"ヘッドフォン"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"入力用周辺機器"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-FiはOFFです。"</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fiが切断されました。"</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Wi-Fiはレベル1です。"</string>
@@ -209,8 +216,12 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth オーディオ LDAC コーデック: 再生音質"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth オーディオ LDAC コーデックを選択:\n再生音質"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ストリーミング: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
-    <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
-    <string name="dns_tls_summary" msgid="3692494150251071380">"この設定を有効にした場合、ポート 853 で DNS over TLS を試行します。"</string>
+    <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"プライベート DNS"</string>
+    <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"プライベート DNS モードを選択"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"OFF"</string>
+    <string name="private_dns_mode_opportunistic" msgid="7608409735589131766">"日和見"</string>
+    <string name="private_dns_mode_provider" msgid="8354935160639360804">"プライベート DNS プロバイダのホスト名"</string>
+    <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"DNS プロバイダのホスト名を入力"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"ワイヤレスディスプレイ認証のオプションを表示"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fiログレベルを上げて、Wi-Fi選択ツールでSSID RSSIごとに表示します"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ON にすると、Wi-Fi の電波強度が弱い場合は強制的にモバイルデータ接続に切り替わるようになります"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 4e3bc33..5a9bbc5 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"PIN 또는 패스키가 잘못되어 <xliff:g id="DEVICE_NAME">%1$s</xliff:g>와(과) 페어링하지 못했습니다."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>와(과) 통신할 수 없습니다."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>에서 페어링을 거부했습니다."</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"컴퓨터"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"헤드셋"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"전화"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"이미징"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"헤드폰"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"입력 주변기기"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"블루투스"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi가 꺼져 있습니다."</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fi 연결이 끊어졌습니다."</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Wi-Fi 신호 막대가 한 개입니다."</string>
@@ -209,8 +216,12 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"블루투스 오디오 LDAC 코덱: 재생 품질"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"블루투스 오디오 LDAC 코덱 선택:\n재생 품질"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"스트리밍: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
-    <string name="dns_tls" msgid="6773814174391131955">"TLS를 통한 DNS"</string>
-    <string name="dns_tls_summary" msgid="3692494150251071380">"사용 설정한 경우, 포트 853에서 TLS를 통해 DNS를 시도합니다."</string>
+    <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"비공개 DNS"</string>
+    <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"비공개 DNS 모드 선택"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"사용 안함"</string>
+    <string name="private_dns_mode_opportunistic" msgid="7608409735589131766">"기회주의적"</string>
+    <string name="private_dns_mode_provider" msgid="8354935160639360804">"비공개 DNS 제공업체 호스트 이름"</string>
+    <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"DNS 제공업체의 호스트 이름 입력"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"무선 디스플레이 인증서 옵션 표시"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi 로깅 수준을 높이고, Wi‑Fi 선택도구에서 SSID RSSI당 값을 표시합니다."</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"사용 설정하면 Wi-Fi 신호가 약할 때 데이터 연결을 Wi-Fi에서 모바일 네트워크로 더욱 적극적으로 핸드오버합니다."</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index a1beef6..2b31bf1 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -316,7 +316,7 @@
     <string name="enable_freeform_support_summary" msgid="8247310463288834487">"Ruhusu uwezo wa kutumia madirisha ya majaribio yenye muundo huru."</string>
     <string name="local_backup_password_title" msgid="3860471654439418822">"Nenosiri la hifadhi rudufu ya eneo kazi"</string>
     <string name="local_backup_password_summary_none" msgid="6951095485537767956">"Hifadhi rudufu kamili za eneo kazi hazijalindwa kwa sasa"</string>
-    <string name="local_backup_password_summary_change" msgid="5376206246809190364">"Gonga ili ubadilishe au uondoe nenosiri la hifadhi rudufu kamili za eneo kazi"</string>
+    <string name="local_backup_password_summary_change" msgid="5376206246809190364">"Gusa ili ubadilishe au uondoe nenosiri la hifadhi rudufu kamili za eneo kazi"</string>
     <string name="local_backup_password_toast_success" msgid="582016086228434290">"Nenosiri jipya la hifadhi rudufu limewekwa"</string>
     <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"Nenosiri jipya na uthibitisho havioani"</string>
     <string name="local_backup_password_toast_validation_failure" msgid="5646377234895626531">"Imeshindwa kuweka nenosiri la hifadhi rudufu"</string>
@@ -331,8 +331,8 @@
     <item msgid="5363960654009010371">"Rangi zilizoboreshwa kwa ajili ya maudhui dijitali"</item>
   </string-array>
     <string name="inactive_apps_title" msgid="1317817863508274533">"Programu zilizozimwa"</string>
-    <string name="inactive_app_inactive_summary" msgid="5091363706699855725">"Haitumika. Gonga ili ugeuze."</string>
-    <string name="inactive_app_active_summary" msgid="4174921824958516106">"Inatumika. Gonga ili ugeuze."</string>
+    <string name="inactive_app_inactive_summary" msgid="5091363706699855725">"Haitumika. Gusa ili ugeuze."</string>
+    <string name="inactive_app_active_summary" msgid="4174921824958516106">"Inatumika. Gusa ili ugeuze."</string>
     <string name="runningservices_settings_title" msgid="8097287939865165213">"Huduma zinazoendeshwa"</string>
     <string name="runningservices_settings_summary" msgid="854608995821032748">"Onyesha na dhibiti huduma zinazoendeshwa kwa sasa"</string>
     <string name="select_webview_provider_title" msgid="4628592979751918907">"Utekelezaji wa WebView"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 5ff39b6..2db6178 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"ไม่สามารถจับคู่กับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ได้เพราะ PIN หรือรหัสผ่านไม่ถูกต้อง"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"ไม่สามารถเชื่อมต่อกับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ปฏิเสธการจับคู่อุปกรณ์"</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"คอมพิวเตอร์"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"ชุดหูฟัง"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"โทรศัพท์"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"การถ่ายภาพ"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"หูฟัง"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"อุปกรณ์อินพุต"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"บลูทูธ"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi ปิดอยู่"</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"ไม่ได้เชื่อมต่อ Wi-Fi"</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"สัญญาณ Wi-Fi 1 ขีด"</string>
@@ -209,8 +216,12 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC: คุณภาพการเล่น"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"เลือกตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC:\nคุณภาพการเล่น"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"สตรีมมิง: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
-    <string name="dns_tls" msgid="6773814174391131955">"DNS ผ่าน TLS"</string>
-    <string name="dns_tls_summary" msgid="3692494150251071380">"หากเปิดใช้แล้ว ให้ลองใช้ DNS ผ่าน TLS บนพอร์ต 853"</string>
+    <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"DNS ส่วนตัว"</string>
+    <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"เลือกโหมด DNS ส่วนตัว"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"ปิด"</string>
+    <string name="private_dns_mode_opportunistic" msgid="7608409735589131766">"ที่ใช้โอกาส"</string>
+    <string name="private_dns_mode_provider" msgid="8354935160639360804">"ชื่อโฮสต์ของผู้ให้บริการ DNS ส่วนตัว"</string>
+    <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"ป้อนชื่อโฮสต์ของผู้ให้บริการ DNS"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"แสดงตัวเลือกสำหรับการรับรองการแสดงผล แบบไร้สาย"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"เพิ่มระดับการบันทึก Wi‑Fi แสดงต่อ SSID RSSI ในตัวเลือก Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"เมื่อเปิดใช้แล้ว Wi-Fi จะส่งผ่านการเชื่อมต่อข้อมูลไปยังเครือข่ายมือถือเมื่อสัญญาณ Wi-Fi อ่อน"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 2554388..2215fe5 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配對,因為 PIN 碼或密碼金鑰不正確。"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 通訊。"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」拒絕配對要求。"</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"電腦"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"免持耳機"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"電話"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"顯像裝置"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"頭戴式耳機"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"周邊輸入裝置"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"藍牙"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"已關閉 Wi-Fi。"</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fi 連線已中斷。"</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Wi-Fi 訊號強度一格。"</string>
@@ -209,8 +216,12 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"藍牙音訊 LDAC 轉碼器:播放品質"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"選取藍牙音訊 LDAC 轉碼器:\n播放品質"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"串流中:<xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
-    <string name="dns_tls" msgid="6773814174391131955">"透過傳輸層安全標準 (TLS) 執行 DNS"</string>
-    <string name="dns_tls_summary" msgid="3692494150251071380">"如果啟用這個選項,即可在通訊埠 853 上嘗試透過傳輸層安全標準 (TLS) 執行 DNS。"</string>
+    <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"私人 DNS"</string>
+    <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"選取私人 DNS 模式"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"停用"</string>
+    <string name="private_dns_mode_opportunistic" msgid="7608409735589131766">"隨機"</string>
+    <string name="private_dns_mode_provider" msgid="8354935160639360804">"私人 DNS 供應商主機名稱"</string>
+    <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"輸入 DNS 供應商的主機名稱"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"顯示無線螢幕分享認證的選項"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"讓 Wi‑Fi 記錄功能升級,在 Wi‑Fi 選擇器中依每個 SSID RSSI 顯示 Wi‑Fi 詳細紀錄"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"啟用時,Wi-Fi 連線在訊號不穩的情況下會更積極轉換成行動數據連線"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 26fd750..d82db41 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -77,7 +77,7 @@
     <!-- Summary for the network but no internet connection was detected. -->
     <string name="wifi_no_internet_no_reconnect">Won\'t automatically connect</string>
     <!-- Summary for the remembered network but no internet connection was detected. -->
-    <string name="wifi_no_internet">No Internet access</string>
+    <string name="wifi_no_internet">No internet access</string>
     <!-- Summary for saved networks -->
     <string name="saved_network">Saved by <xliff:g id="name">%1$s</xliff:g></string>
 
@@ -95,7 +95,7 @@
     <string name="certinstaller_package" translatable="false">com.android.certinstaller</string>
 
     <!-- Summary for Connected wifi network without internet -->
-    <string name="wifi_connected_no_internet">Connected, no Internet</string>
+    <string name="wifi_connected_no_internet">Connected, no internet</string>
 
     <!-- Summary for networks failing to connect due to association rejection status 17, AP full -->
     <string name="wifi_ap_unable_to_handle_new_sta">Access point temporarily full</string>
@@ -192,14 +192,14 @@
     <!-- Bluetooth settings. Connection options screen. The summary for the HID checkbox preference when HID is connected. -->
     <string name="bluetooth_hid_profile_summary_connected">Connected to input device</string>
     <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (user role). [CHAR LIMIT=25]-->
-    <string name="bluetooth_pan_user_profile_summary_connected">Connected to device for Internet access</string>
+    <string name="bluetooth_pan_user_profile_summary_connected">Connected to device for internet access</string>
     <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (NAP role). [CHAR LIMIT=25]-->
-    <string name="bluetooth_pan_nap_profile_summary_connected">Sharing local Internet connection with device</string>
+    <string name="bluetooth_pan_nap_profile_summary_connected">Sharing local internet connection with device</string>
 
     <!-- Bluetooth settings. Connection options screen. The summary
          for the PAN checkbox preference that describes how checking it
          will set the PAN profile as preferred. -->
-    <string name="bluetooth_pan_profile_summary_use_for">Use for Internet access</string>
+    <string name="bluetooth_pan_profile_summary_use_for">Use for internet access</string>
     <!-- Bluetooth settings. Connection options screen.  The summary for the map checkbox preference that describes how checking it will set the map profile as preferred. -->
     <string name="bluetooth_map_profile_summary_use_for">Use for map</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the sap checkbox preference that describes how checking it will set the sap profile as preferred. -->
diff --git a/packages/Shell/res/values-sw/strings.xml b/packages/Shell/res/values-sw/strings.xml
index 3f3dd2a..e4dcbee 100644
--- a/packages/Shell/res/values-sw/strings.xml
+++ b/packages/Shell/res/values-sw/strings.xml
@@ -24,10 +24,10 @@
     <string name="bugreport_updating_wait" msgid="3322151947853929470">"Tafadhali subiri…"</string>
     <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"Tutatuma ripoti ya hitilafu kwenye simu baada ya muda mfupi"</string>
     <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Chagua kushiriki ripoti ya hitilafu"</string>
-    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Gonga ili ushiriki ripoti yako ya hitilafu"</string>
+    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Gusa ili ushiriki ripoti yako ya hitilafu"</string>
     <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Chagua kushiriki ripoti yako ya hitilafu bila picha ya skrini au usubiri picha ya skrini itayarishwe"</string>
-    <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Gonga ili ushiriki ripoti yako ya hitilafu bila picha ya skrini au usubiri picha ya skrini itayarishwe"</string>
-    <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Gonga ili ushiriki ripoti yako ya hitilafu bila picha ya skrini au usubiri picha ya skrini itayarishwe"</string>
+    <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Gusa ili ushiriki ripoti yako ya hitilafu bila picha ya skrini au usubiri picha ya skrini itayarishwe"</string>
+    <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Gusa ili ushiriki ripoti yako ya hitilafu bila picha ya skrini au usubiri picha ya skrini itayarishwe"</string>
     <string name="bugreport_confirm" msgid="5917407234515812495">"Ripoti za hitilafu zinajumuisha data kutoka faili za kumbukumbu mbalimbali zilizo kwenye mfumo, ambazo huenda zinajumuisha data ambayo unachukulia kuwa nyeti (kama vile matumizi ya programu na maelezo kuhusu data ilipo). Shiriki ripoti za hitilafu na watu na programu unazoamini pekee."</string>
     <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Usionyeshe tena"</string>
     <string name="bugreport_storage_title" msgid="5332488144740527109">"Ripoti za hitilafu"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index 04ce752..839fb87 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -117,8 +117,8 @@
       <item quantity="other">‏رمز PUK لشريحة SIM غير صحيح، ويتبقى لديك <xliff:g id="NUMBER_1">%d</xliff:g> من المحاولات تصبح بعدها شريحة SIM غير صالحة للاستخدام بشكل دائم.</item>
       <item quantity="one">‏رمز PUK لشريحة SIM غير صالح، ويتبقى لديك محاولة واحدة (<xliff:g id="NUMBER_0">%d</xliff:g>)، تصبح بعدها شريحة SIM غير صالحة للاستخدام بشكل دائم.</item>
     </plurals>
-    <string name="kg_password_pin_failed" msgid="8769990811451236223">"‏أخفقت عملية \"رقم التعريف الشخصي\" لشريحة SIM"</string>
-    <string name="kg_password_puk_failed" msgid="1331621440873439974">"‏أخفقت عملية PUK لشريحة SIM"</string>
+    <string name="kg_password_pin_failed" msgid="8769990811451236223">"‏تعذّر إتمام عملية \"رقم التعريف الشخصي\" لشريحة SIM"</string>
+    <string name="kg_password_puk_failed" msgid="1331621440873439974">"‏تعذّر إتمام عملية PUK لشريحة SIM"</string>
     <string name="kg_pin_accepted" msgid="7637293533973802143">"تم قبول الرمز"</string>
     <string name="keyguard_carrier_default" msgid="4274828292998453695">"لا تتوفر خدمة."</string>
     <string name="accessibility_ime_switch_button" msgid="2695096475319405612">"تبديل أسلوب الإدخال"</string>
diff --git a/packages/SystemUI/res/values-ar/config.xml b/packages/SystemUI/res/values-ar/config.xml
index 5309563..477f219 100644
--- a/packages/SystemUI/res/values-ar/config.xml
+++ b/packages/SystemUI/res/values-ar/config.xml
@@ -22,5 +22,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="config_overviewServiceComponent" msgid="2288311504315574053">"com.android.launcher3/com.android.quickstep.TouchInteractionService"</string>
     <string name="doze_pickup_subtype_performs_proximity_check" msgid="533127617385956583"></string>
 </resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index b5ac31a..d642118 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -55,15 +55,15 @@
     <string name="bluetooth_tethered" msgid="7094101612161133267">"تم إنشاء الاتصال بالإنترنت عن طريق البلوتوث."</string>
     <string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"إعداد أسلوب الإدخال"</string>
     <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"لوحة مفاتيح فعلية"</string>
-    <string name="usb_device_permission_prompt" msgid="834698001271562057">"‏هل تريد السماح للتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى جهاز USB؟"</string>
-    <string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"‏هل تريد السماح للتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى ملحق USB؟"</string>
-    <string name="usb_device_confirm_prompt" msgid="5161205258635253206">"‏هل تريد فتح <xliff:g id="ACTIVITY">%1$s</xliff:g> عند توصيل جهاز USB هذا؟"</string>
-    <string name="usb_accessory_confirm_prompt" msgid="3808984931830229888">"‏هل تريد فتح <xliff:g id="ACTIVITY">%1$s</xliff:g> عند توصيل ملحق USB هذا؟"</string>
+    <string name="usb_device_permission_prompt" msgid="1825685909587559679">"هل تريد السماح لتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى <xliff:g id="USB_DEVICE">%2$s</xliff:g>؟"</string>
+    <string name="usb_accessory_permission_prompt" msgid="2465531696941369047">"هل تريد السماح لتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>؟"</string>
+    <string name="usb_device_confirm_prompt" msgid="7440562274256843905">"هل تريد فتح <xliff:g id="APPLICATION">%1$s</xliff:g> للتعامل مع <xliff:g id="USB_DEVICE">%2$s</xliff:g>؟"</string>
+    <string name="usb_accessory_confirm_prompt" msgid="4333670517539993561">"هل تريد فتح تطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> للتعامل مع <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>؟"</string>
     <string name="usb_accessory_uri_prompt" msgid="513450621413733343">"‏لا يعمل أي تطبيق مثبت مع ملحق UEB هذا. مزيد من المعلومات عن هذا الملحق على <xliff:g id="URL">%1$s</xliff:g>."</string>
     <string name="title_usb_accessory" msgid="4966265263465181372">"‏ملحق USB"</string>
     <string name="label_view" msgid="6304565553218192990">"عرض"</string>
-    <string name="always_use_device" msgid="1450287437017315906">"‏الاستخدام بشكل افتراضي لجهاز USB هذا"</string>
-    <string name="always_use_accessory" msgid="1210954576979621596">"‏الاستخدام بشكل افتراضي لملحق USB هذا"</string>
+    <string name="always_use_device" msgid="4015357883336738417">"فتح تطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> دائمًا عند توصيل <xliff:g id="USB_DEVICE">%2$s</xliff:g>"</string>
+    <string name="always_use_accessory" msgid="3257892669444535154">"فتح تطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> دائمًا عند توصيل <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>"</string>
     <string name="usb_debugging_title" msgid="4513918393387141949">"‏هل تريد السماح بتصحيح أخطاء USB؟"</string>
     <string name="usb_debugging_message" msgid="2220143855912376496">"‏الملف المرجعي الرئيسي لـ RSA في هذا الكمبيوتر هو:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
     <string name="usb_debugging_always" msgid="303335496705863070">"السماح دائمًا من هذا الكمبيوتر"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 989f6d1..6e1feea 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -51,21 +51,15 @@
     <string name="bluetooth_tethered" msgid="7094101612161133267">"Connexion Bluetooth partagée"</string>
     <string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Configurer les modes de saisie"</string>
     <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Clavier physique"</string>
-    <!-- no translation found for usb_device_permission_prompt (1825685909587559679) -->
-    <skip />
-    <!-- no translation found for usb_accessory_permission_prompt (2465531696941369047) -->
-    <skip />
-    <!-- no translation found for usb_device_confirm_prompt (7440562274256843905) -->
-    <skip />
-    <!-- no translation found for usb_accessory_confirm_prompt (4333670517539993561) -->
-    <skip />
+    <string name="usb_device_permission_prompt" msgid="1825685909587559679">"Autoriser <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_accessory_permission_prompt" msgid="2465531696941369047">"Autoriser <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
+    <string name="usb_device_confirm_prompt" msgid="7440562274256843905">"Ouvrir <xliff:g id="APPLICATION">%1$s</xliff:g> pour utiliser <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_accessory_confirm_prompt" msgid="4333670517539993561">"Ouvrir <xliff:g id="APPLICATION">%1$s</xliff:g> pour utiliser <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
     <string name="usb_accessory_uri_prompt" msgid="513450621413733343">"Aucune application installée compatible avec accessoire USB. En savoir plus sur <xliff:g id="URL">%1$s</xliff:g>"</string>
     <string name="title_usb_accessory" msgid="4966265263465181372">"Accessoire USB"</string>
     <string name="label_view" msgid="6304565553218192990">"Afficher"</string>
-    <!-- no translation found for always_use_device (4015357883336738417) -->
-    <skip />
-    <!-- no translation found for always_use_accessory (3257892669444535154) -->
-    <skip />
+    <string name="always_use_device" msgid="4015357883336738417">"Toujours ouvrir <xliff:g id="APPLICATION">%1$s</xliff:g> lorsque <xliff:g id="USB_DEVICE">%2$s</xliff:g> est connecté"</string>
+    <string name="always_use_accessory" msgid="3257892669444535154">"Toujours ouvrir <xliff:g id="APPLICATION">%1$s</xliff:g> lorsque <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> est connecté"</string>
     <string name="usb_debugging_title" msgid="4513918393387141949">"Autoriser le débogage USB?"</string>
     <string name="usb_debugging_message" msgid="2220143855912376496">"Empreinte numérique de la clé RSA de l\'ordinateur : \n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
     <string name="usb_debugging_always" msgid="303335496705863070">"Toujours autoriser sur cet ordinateur"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index d1bbe50..5853d93 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -51,21 +51,15 @@
     <string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth imefungwa"</string>
     <string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Weka mbinu za ingizo"</string>
     <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Kibodi halisi"</string>
-    <!-- no translation found for usb_device_permission_prompt (1825685909587559679) -->
-    <skip />
-    <!-- no translation found for usb_accessory_permission_prompt (2465531696941369047) -->
-    <skip />
-    <!-- no translation found for usb_device_confirm_prompt (7440562274256843905) -->
-    <skip />
-    <!-- no translation found for usb_accessory_confirm_prompt (4333670517539993561) -->
-    <skip />
+    <string name="usb_device_permission_prompt" msgid="1825685909587559679">"Ungependa kuruhusu <xliff:g id="APPLICATION">%1$s</xliff:g> ifikie <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_accessory_permission_prompt" msgid="2465531696941369047">"Ungependa kuruhusu <xliff:g id="APPLICATION">%1$s</xliff:g> ifikie <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
+    <string name="usb_device_confirm_prompt" msgid="7440562274256843905">"Ungependa kufungua <xliff:g id="APPLICATION">%1$s</xliff:g> ili itumie <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_accessory_confirm_prompt" msgid="4333670517539993561">"Ungependa kufungua <xliff:g id="APPLICATION">%1$s</xliff:g> ili itumie <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
     <string name="usb_accessory_uri_prompt" msgid="513450621413733343">"Hakuna programu zilizosakinishwa zinazofanya kazi na kifaa hiki cha USB. Pata maelezo zaidi kuhusu kifaa hiki kwenye <xliff:g id="URL">%1$s</xliff:g>"</string>
     <string name="title_usb_accessory" msgid="4966265263465181372">"Kifaa cha Usb"</string>
     <string name="label_view" msgid="6304565553218192990">"Ona"</string>
-    <!-- no translation found for always_use_device (4015357883336738417) -->
-    <skip />
-    <!-- no translation found for always_use_accessory (3257892669444535154) -->
-    <skip />
+    <string name="always_use_device" msgid="4015357883336738417">"Fungua <xliff:g id="APPLICATION">%1$s</xliff:g> kila wakati <xliff:g id="USB_DEVICE">%2$s</xliff:g> inaunganishwa"</string>
+    <string name="always_use_accessory" msgid="3257892669444535154">"Fungua <xliff:g id="APPLICATION">%1$s</xliff:g> kila wakati <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> inaunganishwa"</string>
     <string name="usb_debugging_title" msgid="4513918393387141949">"Ruhusu utatuaji wa USB?"</string>
     <string name="usb_debugging_message" msgid="2220143855912376496">"Alama ya kidole ya kitufe cha RSA ya kompyuta ni:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
     <string name="usb_debugging_always" msgid="303335496705863070">"Ruhusu kutoka kwenye kompyuta hii kila wakati"</string>
@@ -77,7 +71,7 @@
     <string name="screenshot_saving_title" msgid="8242282144535555697">"Inahifadhi picha ya skrini..."</string>
     <string name="screenshot_saving_text" msgid="2419718443411738818">"Picha ya skrini inahifadhiwa"</string>
     <string name="screenshot_saved_title" msgid="6461865960961414961">"Picha ya skrini imenaswa."</string>
-    <string name="screenshot_saved_text" msgid="2685605830386712477">"Gonga ili utazame picha ya skrini uliyohifadhi."</string>
+    <string name="screenshot_saved_text" msgid="2685605830386712477">"Gusa ili utazame picha ya skrini uliyohifadhi."</string>
     <string name="screenshot_failed_title" msgid="705781116746922771">"Haikuweza kunasa picha ya skrini"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="7887826345701753830">"Hitilafu imetokea wakati wa kuhifadhi picha ya skrini."</string>
     <string name="screenshot_failed_to_save_text" msgid="2592658083866306296">"Haina nafasi ya kutosha kuhifadhi picha ya skrini."</string>
@@ -363,7 +357,7 @@
     <string name="zen_silence_introduction" msgid="3137882381093271568">"Hatua hii huzuia sauti na mitetemo YOTE, ikiwa ni pamoja na ile inayotokana na kengele, muziki, video na michezo."</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>+"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Arifa zisizo za dharura sana ziko hapo chini"</string>
-    <string name="notification_tap_again" msgid="7590196980943943842">"Gonga tena ili ufungue"</string>
+    <string name="notification_tap_again" msgid="7590196980943943842">"Gusa tena ili ufungue"</string>
     <string name="keyguard_unlock" msgid="8043466894212841998">"Telezesha kidole ili ufungue"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Kifaa hiki kinasimamiwa na shirika lako"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Kifaa hiki kinadhibitiwa na <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
@@ -500,11 +494,11 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Masafa ya ishara ya kampuni ya simu"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Zana za walio na matatizo ya kuona au kusikia"</string>
-    <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. Gonga ili urejeshe."</string>
-    <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. Gonga ili uweke mtetemo. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string>
-    <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. Gonga ili ukomeshe. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string>
-    <string name="volume_stream_content_description_vibrate_a11y" msgid="6427727603978431301">"%1$s. Gonga ili uweke mtetemo."</string>
-    <string name="volume_stream_content_description_mute_a11y" msgid="8995013018414535494">"%1$s. Gonga ili usitishe."</string>
+    <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. Gusa ili urejeshe."</string>
+    <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. Gusa ili uweke mtetemo. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string>
+    <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. Gusa ili ukomeshe. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string>
+    <string name="volume_stream_content_description_vibrate_a11y" msgid="6427727603978431301">"%1$s. Gusa ili uweke mtetemo."</string>
+    <string name="volume_stream_content_description_mute_a11y" msgid="8995013018414535494">"%1$s. Gusa ili usitishe."</string>
     <string name="volume_dialog_accessibility_shown_message" msgid="1834631467074259998">"Inaonyesha %s ya vidhibiti vya sauti. Telezesha kidole juu ili uondoe."</string>
     <string name="volume_dialog_accessibility_dismissed_message" msgid="51543526013711399">"Imeficha vidhibiti vya sauti"</string>
     <string name="system_ui_tuner" msgid="708224127392452018">"Kirekebishi cha kiolesura cha mfumo"</string>
@@ -700,9 +694,9 @@
     <string name="accessibility_action_divider_top_50" msgid="6385859741925078668">"Juu 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="6201455163864841205">"Juu 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="301433196679548001">"Skrini nzima ya chini"</string>
-    <string name="accessibility_qs_edit_tile_label" msgid="8374924053307764245">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Gonga mara mbili ili ubadilishe."</string>
-    <string name="accessibility_qs_edit_add_tile_label" msgid="8133209638023882667">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Gonga mara mbili ili uongeze."</string>
-    <string name="accessibility_qs_edit_position_label" msgid="5055306305919289819">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>. Gonga mara mbili ili uchague."</string>
+    <string name="accessibility_qs_edit_tile_label" msgid="8374924053307764245">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Gusa mara mbili ili ubadilishe."</string>
+    <string name="accessibility_qs_edit_add_tile_label" msgid="8133209638023882667">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Gusa mara mbili ili uongeze."</string>
+    <string name="accessibility_qs_edit_position_label" msgid="5055306305919289819">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>. Gusa mara mbili ili uchague."</string>
     <string name="accessibility_qs_edit_move_tile" msgid="2461819993780159542">"Hamisha <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_qs_edit_remove_tile" msgid="7484493384665907197">"Ondoa <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="8050200862063548309">"<xliff:g id="TILE_NAME">%1$s</xliff:g> imeongezwa kwenye nafasi ya <xliff:g id="POSITION">%2$d</xliff:g>"</string>
@@ -776,7 +770,7 @@
     <string name="qs_dnd_keep" msgid="1825009164681928736">"Usibadilishe"</string>
     <string name="qs_dnd_replace" msgid="8019520786644276623">"Badilisha"</string>
     <string name="running_foreground_services_title" msgid="381024150898615683">"Programu zinatumika chinichini"</string>
-    <string name="running_foreground_services_msg" msgid="6326247670075574355">"Gonga ili upate maelezo kuhusu betri na matumizi ya data"</string>
+    <string name="running_foreground_services_msg" msgid="6326247670075574355">"Gusa ili upate maelezo kuhusu betri na matumizi ya data"</string>
     <string name="data_usage_disable_mobile" msgid="5116269981510015864">"Ungependa kuzima data ya mtandao wa simu?"</string>
     <string name="touch_filtered_warning" msgid="8671693809204767551">"Kwa sababu programu nyingine inazuia ombi la ruhusa, hatuwezi kuthibitisha jibu lako katika Mipangilio."</string>
 </resources>
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index af4668a..9741486 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1167,7 +1167,16 @@
                         break;
                     }
                 }
+
                 value = getSanitizedValue(sanitizers, id, value);
+                if (value == null) {
+                    if (sDebug) {
+                        Slog.d(TAG, "value of required field " + id + " failed sanitization");
+                    }
+                    allRequiredAreNotEmpty = false;
+                    break;
+                }
+                viewState.setSanitizedValue(value);
                 currentValues.put(id, value);
                 final AutofillValue filledValue = viewState.getAutofilledValue();
 
@@ -1337,7 +1346,7 @@
         return sanitizers;
     }
 
-    @NonNull
+    @Nullable
     private AutofillValue getSanitizedValue(
             @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
             @NonNull AutofillId id,
@@ -1431,10 +1440,10 @@
             if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context);
 
             for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) {
-                final ViewState state = mViewStates.valueAt(viewStateNum);
+                final ViewState viewState = mViewStates.valueAt(viewStateNum);
 
-                final AutofillId id = state.id;
-                final AutofillValue value = state.getCurrentValue();
+                final AutofillId id = viewState.id;
+                final AutofillValue value = viewState.getCurrentValue();
                 if (value == null) {
                     if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id);
                     continue;
@@ -1446,9 +1455,17 @@
                 }
                 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
 
-                final AutofillValue sanitizedValue = getSanitizedValue(sanitizers, id, value);
+                AutofillValue sanitizedValue = viewState.getSanitizedValue();
 
-                node.updateAutofillValue(sanitizedValue);
+                if (sanitizedValue == null) {
+                    // Field is optional and haven't been sanitized yet.
+                    sanitizedValue = getSanitizedValue(sanitizers, id, value);
+                }
+                if (sanitizedValue != null) {
+                    node.updateAutofillValue(sanitizedValue);
+                } else if (sDebug) {
+                    Slog.d(TAG, "Not updating field " + id + " because it failed sanitization");
+                }
             }
 
             // Sanitize structure before it's sent to service.
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index 832a66b..0dbdc13 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -76,6 +76,7 @@
     private FillResponse mResponse;
     private AutofillValue mCurrentValue;
     private AutofillValue mAutofilledValue;
+    private AutofillValue mSanitizedValue;
     private Rect mVirtualBounds;
     private int mState;
     private String mDatasetId;
@@ -117,6 +118,15 @@
     }
 
     @Nullable
+    AutofillValue getSanitizedValue() {
+        return mSanitizedValue;
+    }
+
+    void setSanitizedValue(@Nullable AutofillValue value) {
+        mSanitizedValue = value;
+    }
+
+    @Nullable
     FillResponse getResponse() {
         return mResponse;
     }
@@ -218,6 +228,7 @@
         }
         pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue);
         pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
+        pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue);
         pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
     }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 4bdbbe3..e243e56 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.INetdEventCallback;
+import android.net.MacAddress;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.metrics.ConnectStats;
@@ -35,6 +36,7 @@
 import android.util.Log;
 import android.util.ArrayMap;
 import android.util.SparseArray;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -242,13 +244,17 @@
         event.timestampMs = timestampMs;
         event.uid = uid;
         event.ethertype = ethertype;
-        event.dstHwAddr = dstHw;
+        event.dstHwAddr = new MacAddress(dstHw);
         event.srcIp = srcIp;
         event.dstIp = dstIp;
         event.ipNextHeader = ipNextHeader;
         event.srcPort = srcPort;
         event.dstPort = dstPort;
         addWakeupEvent(event);
+
+        String dstMac = event.dstHwAddr.toString();
+        StatsLog.write(StatsLog.PACKET_WAKEUP_OCCURRED,
+                uid, iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort);
     }
 
     private void addWakeupEvent(WakeupEvent event) {
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
index be223f1..d71c3b0 100644
--- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -16,7 +16,7 @@
 
 package com.android.server.media;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 import android.content.Context;
 import android.media.AudioPlaybackConfiguration;
 import android.media.IAudioService;
@@ -27,14 +27,15 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.HashMap;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -49,47 +50,57 @@
     private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
 
     /**
-     * Called when the state of audio player is changed.
+     * Listener for handling the active state changes of audio players.
      */
-    interface OnAudioPlayerStateChangedListener {
-        void onAudioPlayerStateChanged(
-                int uid, int prevState, @Nullable AudioPlaybackConfiguration config);
+    interface OnAudioPlayerActiveStateChangedListener {
+        /**
+         * Called when the active state of audio player is changed.
+         *
+         * @param config The audio playback configuration for the audio player of which active state
+         *              was changed. If {@param isRemoved} is {@code true}, this hold outdated
+         *              information.
+         * @param isRemoved {@code true} if the audio player is removed.
+         */
+        void onAudioPlayerActiveStateChanged(
+                @NonNull AudioPlaybackConfiguration config, boolean isRemoved);
     }
 
     private final static class MessageHandler extends Handler {
-        private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1;
+        private static final int MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED = 1;
 
-        private final OnAudioPlayerStateChangedListener mListsner;
+        private final OnAudioPlayerActiveStateChangedListener mListener;
 
-        public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) {
+        public MessageHandler(Looper looper, OnAudioPlayerActiveStateChangedListener listener) {
             super(looper);
-            mListsner = listener;
+            mListener = listener;
         }
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_AUDIO_PLAYER_STATE_CHANGED:
-                    mListsner.onAudioPlayerStateChanged(
-                            msg.arg1, msg.arg2, (AudioPlaybackConfiguration) msg.obj);
+                case MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED:
+                    mListener.onAudioPlayerActiveStateChanged((AudioPlaybackConfiguration) msg.obj,
+                            msg.arg1 != 0);
                     break;
             }
         }
 
-        public void sendAudioPlayerStateChangedMessage(int uid, int prevState,
-                AudioPlaybackConfiguration config) {
-            obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget();
+        public void sendAudioPlayerActiveStateChangedMessage(
+                final AudioPlaybackConfiguration config, final boolean isRemoved) {
+            obtainMessage(MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED,
+                    isRemoved ? 1 : 0, 0 /* unused */, config).sendToTarget();
         }
     }
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
-    private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap =
-            new HashMap<>();
+    private final Map<OnAudioPlayerActiveStateChangedListener, MessageHandler> mListenerMap =
+            new ArrayMap<>();
     @GuardedBy("mLock")
-    private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>();
+    private final Set<Integer> mActiveAudioUids = new ArraySet();
     @GuardedBy("mLock")
-    private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>();
+    private ArrayMap<Integer, AudioPlaybackConfiguration> mPrevActiveAudioPlaybackConfigs =
+            new ArrayMap<>();
     // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
     // The UID whose audio playback becomes active at the last comes first.
     // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
@@ -122,32 +133,24 @@
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates);
-            final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid =
-                    new HashMap<>(mAudioPlayersForUid);
             synchronized (mLock) {
-                mAudioPlayerStates.clear();
-                mAudioPlayersForUid.clear();
+                // Update mActiveAudioUids
+                mActiveAudioUids.clear();
+                ArrayMap<Integer, AudioPlaybackConfiguration> activeAudioPlaybackConfigs =
+                        new ArrayMap<>();
                 for (AudioPlaybackConfiguration config : configs) {
-                    int pii = config.getPlayerInterfaceId();
-                    int uid = config.getClientUid();
-                    mAudioPlayerStates.put(pii, config.getPlayerState());
-                    HashSet<Integer> players = mAudioPlayersForUid.get(uid);
-                    if (players == null) {
-                        players = new HashSet<Integer>();
-                        players.add(pii);
-                        mAudioPlayersForUid.put(uid, players);
-                    } else {
-                        players.add(pii);
+                    if (config.isActive()) {
+                        mActiveAudioUids.add(config.getClientUid());
+                        activeAudioPlaybackConfigs.put(config.getPlayerInterfaceId(), config);
                     }
                 }
-                for (AudioPlaybackConfiguration config : configs) {
-                    if (!config.isActive()) {
-                        continue;
-                    }
 
-                    int uid = config.getClientUid();
-                    if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) {
+                // Update mSortedAuioPlaybackClientUids.
+                for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) {
+                    AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i);
+                    final int uid = config.getClientUid();
+                    if (!mPrevActiveAudioPlaybackConfigs.containsKey(
+                            config.getPlayerInterfaceId())) {
                         if (DEBUG) {
                             Log.d(TAG, "Found a new active media playback. " +
                                     AudioPlaybackConfiguration.toLogFriendlyString(config));
@@ -163,40 +166,21 @@
                         mSortedAudioPlaybackClientUids.add(0, uid);
                     }
                 }
-                // Notify the change of audio player states.
+                // Notify the active state change of audio players.
                 for (AudioPlaybackConfiguration config : configs) {
-                    final Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId());
-                    final int prevStateInt =
-                            (prevState == null) ? AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN :
-                                prevState.intValue();
-                    if (prevStateInt != config.getPlayerState()) {
-                        sendAudioPlayerStateChangedMessageLocked(
-                                config.getClientUid(), prevStateInt, config);
+                    final int pii = config.getPlayerInterfaceId();
+                    boolean wasActive = mPrevActiveAudioPlaybackConfigs.remove(pii) != null;
+                    if (wasActive != config.isActive()) {
+                        sendAudioPlayerActiveStateChangedMessageLocked(
+                                config, /* isRemoved */ false);
                     }
                 }
-                for (Integer prevUid : prevAudioPlayersForUid.keySet()) {
-                    // If all players for prevUid is removed, notify the prev state was
-                    // PLAYER_STATE_STARTED only when there were a player whose state was
-                    // PLAYER_STATE_STARTED, otherwise any inactive state is okay to notify.
-                    if (!mAudioPlayersForUid.containsKey(prevUid)) {
-                        Set<Integer> prevPlayers = prevAudioPlayersForUid.get(prevUid);
-                        int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN;
-                        for (int pii : prevPlayers) {
-                            Integer state = prevAudioPlayerStates.get(pii);
-                            if (state == null) {
-                                continue;
-                            }
-                            if (state == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                                prevState = state;
-                                break;
-                            } else if (prevState
-                                    == AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN) {
-                                prevState = state;
-                            }
-                        }
-                        sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null);
-                    }
+                for (AudioPlaybackConfiguration config : mPrevActiveAudioPlaybackConfigs.values()) {
+                    sendAudioPlayerActiveStateChangedMessageLocked(config, /* isRemoved */ true);
                 }
+
+                // Update mPrevActiveAudioPlaybackConfigs
+                mPrevActiveAudioPlaybackConfigs = activeAudioPlaybackConfigs;
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -204,9 +188,10 @@
     }
 
     /**
-     * Registers OnAudioPlayerStateChangedListener.
+     * Registers OnAudioPlayerActiveStateChangedListener.
      */
-    public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) {
+    public void registerListener(
+            OnAudioPlayerActiveStateChangedListener listener, Handler handler) {
         synchronized (mLock) {
             mListenerMap.put(listener, new MessageHandler((handler == null) ?
                     Looper.myLooper() : handler.getLooper(), listener));
@@ -214,9 +199,9 @@
     }
 
     /**
-     * Unregisters OnAudioPlayerStateChangedListener.
+     * Unregisters OnAudioPlayerActiveStateChangedListener.
      */
-    public void unregisterListener(OnAudioPlayerStateChangedListener listener) {
+    public void unregisterListener(OnAudioPlayerActiveStateChangedListener listener) {
         synchronized (mLock) {
             mListenerMap.remove(listener);
         }
@@ -239,16 +224,7 @@
      */
     public boolean isPlaybackActive(int uid) {
         synchronized (mLock) {
-            Set<Integer> players = mAudioPlayersForUid.get(uid);
-            if (players == null) {
-                return false;
-            }
-            for (Integer pii : players) {
-                if (isActiveState(mAudioPlayerStates.get(pii))) {
-                    return true;
-                }
-            }
-            return false;
+            return mActiveAudioUids.contains(uid);
         }
     }
 
@@ -314,14 +290,10 @@
         }
     }
 
-    private void sendAudioPlayerStateChangedMessageLocked(
-            final int uid, final int prevState, final AudioPlaybackConfiguration config) {
+    private void sendAudioPlayerActiveStateChangedMessageLocked(
+            final AudioPlaybackConfiguration config, final boolean isRemoved) {
         for (MessageHandler messageHandler : mListenerMap.values()) {
-            messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config);
+            messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved);
         }
     }
-
-    private static boolean isActiveState(Integer state) {
-        return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
-    }
 }
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 3c9e1d4..3e51252 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -19,7 +19,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.Watchdog;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -101,6 +101,8 @@
     private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final Handler mHandler = new Handler();
     private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
+    private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
+    private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
 
     public MediaRouterService(Context context) {
         mContext = context;
@@ -111,7 +113,7 @@
 
         mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
         mAudioPlayerStateMonitor.registerListener(
-                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
+                new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() {
             static final long WAIT_MS = 500;
             final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
                 @Override
@@ -121,39 +123,41 @@
             };
 
             @Override
-            public void onAudioPlayerStateChanged(
-                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+            public void onAudioPlayerActiveStateChanged(
+                    @NonNull AudioPlaybackConfiguration config, boolean isRemoved) {
+                final boolean active = !isRemoved && config.isActive();
+                final int pii = config.getPlayerInterfaceId();
+                final int uid = config.getClientUid();
+
+                final int idx = mActivePlayerMinPriorityQueue.indexOf(pii);
+                // Keep the latest active player and its uid at the end of the queue.
+                if (idx >= 0) {
+                    mActivePlayerMinPriorityQueue.remove(idx);
+                    mActivePlayerUidMinPriorityQueue.remove(idx);
+                }
+
                 int restoreUid = -1;
-                boolean active = config == null ? false : config.isActive();
                 if (active) {
+                    mActivePlayerMinPriorityQueue.add(config.getPlayerInterfaceId());
+                    mActivePlayerUidMinPriorityQueue.add(uid);
                     restoreUid = uid;
-                } else if (prevState != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                    // Noting to do if the prev state is not an active state.
-                    return;
-                } else {
-                    IntArray sortedAudioPlaybackClientUids =
-                            mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
-                    for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) {
-                        if (mAudioPlayerStateMonitor.isPlaybackActive(
-                                sortedAudioPlaybackClientUids.get(i))) {
-                            restoreUid = sortedAudioPlaybackClientUids.get(i);
-                            break;
-                        }
-                    }
+                } else if (mActivePlayerUidMinPriorityQueue.size() > 0) {
+                    restoreUid = mActivePlayerUidMinPriorityQueue.get(
+                            mActivePlayerUidMinPriorityQueue.size() - 1);
                 }
 
                 mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
                 if (restoreUid >= 0) {
                     restoreRoute(restoreUid);
                     if (DEBUG) {
-                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
-                                + " active " + active + " restoring " + restoreUid);
+                        Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
+                                + ", active=" + active + ", restoreUid=" + restoreUid);
                     }
                 } else {
                     mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
                     if (DEBUG) {
-                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
-                                + " active " + active + " delaying");
+                        Slog.d(TAG, "onAudioPlayerACTIVEStateChanged: " + "uid=" + uid
+                                + ", active=" + active + ", delaying");
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index f6a81d0..06f4f5e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,7 +16,6 @@
 
 package com.android.server.media;
 
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
@@ -138,23 +137,19 @@
         mAudioService = getAudioService();
         mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
         mAudioPlayerStateMonitor.registerListener(
-                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
-            @Override
-            public void onAudioPlayerStateChanged(
-                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
-                if (config == null || !config.isActive() || config.getPlayerType()
-                        == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
-                    return;
-                }
-                synchronized (mLock) {
-                    FullUserRecord user =
-                            getFullUserRecordLocked(UserHandle.getUserId(uid));
-                    if (user != null) {
-                        user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+                (config, isRemoved) -> {
+                    if (isRemoved || !config.isActive() || config.getPlayerType()
+                            == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+                        return;
                     }
-                }
-            }
-        }, null /* handler */);
+                    synchronized (mLock) {
+                        FullUserRecord user = getFullUserRecordLocked(
+                                UserHandle.getUserId(config.getClientUid()));
+                        if (user != null) {
+                            user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+                        }
+                    }
+                }, null /* handler */);
         mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
         mContentResolver = getContext().getContentResolver();
         mSettingsObserver = new SettingsObserver();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9162a97..7748ae4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -8062,19 +8062,6 @@
     }
 
     @Override
-    public boolean canMagnifyWindow(int windowType) {
-        switch (windowType) {
-            case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
-            case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG:
-            case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR:
-            case WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY: {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
     public boolean isTopLevelWindow(int windowType) {
         if (windowType >= WindowManager.LayoutParams.FIRST_SUB_WINDOW
                 && windowType <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index de4fd7cd..88d1e55 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -292,6 +292,8 @@
         public void setMagnificationSpecLocked(MagnificationSpec spec) {
             mMagnifedViewport.updateMagnificationSpecLocked(spec);
             mMagnifedViewport.recomputeBoundsLocked();
+
+            mService.applyMagnificationSpec(spec);
             mService.scheduleAnimationLocked();
         }
 
@@ -421,7 +423,7 @@
         public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
             MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
             if (spec != null && !spec.isNop()) {
-                if (!mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                if (!windowState.shouldMagnify()) {
                     return null;
                 }
             }
@@ -476,6 +478,7 @@
             private final ViewportWindow mWindow;
 
             private boolean mFullRedrawNeeded;
+            private int mTempLayer = 0;
 
             public MagnifiedViewport() {
                 mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
@@ -565,7 +568,7 @@
                     portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
                     windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
 
-                    if (mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                    if (windowState.shouldMagnify()) {
                         mMagnificationRegion.op(windowBounds, Region.Op.UNION);
                         mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
                     } else {
@@ -676,10 +679,12 @@
 
             private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
                 final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+                mTempLayer = 0;
                 dc.forAllWindows((w) -> {
                     if (w.isOnScreen() && w.isVisibleLw()
                             && !w.mWinAnimator.mEnterAnimationPending) {
-                        outWindows.put(w.mLayer, w);
+                        mTempLayer++;
+                        outWindows.put(mTempLayer, w);
                     }
                 }, false /* traverseTopToBottom */ );
             }
@@ -705,7 +710,7 @@
                     SurfaceControl surfaceControl = null;
                     try {
                         mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
-                        surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+                        surfaceControl = mService.getDefaultDisplayContentLocked().makeOverlay()
                                 .setName(SURFACE_TITLE)
                                 .setSize(mTempPoint.x, mTempPoint.y) // not a typo
                                 .setFormat(PixelFormat.TRANSLUCENT)
@@ -714,8 +719,6 @@
                         /* ignore */
                     }
                     mSurfaceControl = surfaceControl;
-                    mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
-                            .getLayerStack());
                     mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw(
                             TYPE_MAGNIFICATION_OVERLAY)
                             * WindowManagerService.TYPE_LAYER_MULTIPLIER);
@@ -1005,6 +1008,8 @@
 
         private final long mRecurringAccessibilityEventsIntervalMillis;
 
+        private int mTempLayer = 0;
+
         public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
                 WindowsForAccessibilityCallback callback) {
             mContext = windowManagerService.mContext;
@@ -1090,6 +1095,7 @@
                     if (isReportedWindowType(windowState.mAttrs.type)) {
                         // Add the window to the ones to be reported.
                         WindowInfo window = obtainPopulatedWindowInfo(windowState, boundsInScreen);
+                        window.layer = addedWindows.size();
                         addedWindows.add(window.token);
                         windows.add(window);
                         if (windowState.isFocused()) {
@@ -1323,9 +1329,10 @@
 
         private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
             final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+            mTempLayer = 0;
             dc.forAllWindows((w) -> {
                 if (w.isVisibleLw()) {
-                    outWindows.put(w.mLayer, w);
+                    outWindows.put(mTempLayer++, w);
                 }
             }, false /* traverseTopToBottom */ );
         }
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 2ef7f25..accfc65 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -253,7 +253,6 @@
 
     private void updateLayers() {
         mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
-        updateThumbnailLayer();
     }
 
     private void stepThumbnailAnimation(long currentTime) {
@@ -283,27 +282,12 @@
                 + "][" + tmpFloats[Matrix.MSKEW_X]
                 + "," + tmpFloats[Matrix.MSCALE_Y] + "]");
         thumbnail.setAlpha(thumbnailTransformation.getAlpha());
-        updateThumbnailLayer();
         thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
                 tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
         thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
     }
 
     /**
-     * Updates the thumbnail layer z order to just above the highest animation layer if changed
-     */
-    void updateThumbnailLayer() {
-        if (thumbnail != null) {
-            final int layer = mAppToken.getHighestAnimLayer();
-            if (DEBUG_LAYERS) Slog.v(TAG,
-                    "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
-            thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
-                    - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
-            mThumbnailLayer = layer;
-        }
-    }
-
-    /**
      * Sometimes we need to synchronize the first frame of animation with some external event, e.g.
      * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
      * and keep producing the first frame of the animation.
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index 9729e50..f19cd0f 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -30,7 +30,6 @@
 import android.util.Slog;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 
 /**
  * Four black surfaces put together to make a black frame.
@@ -42,22 +41,22 @@
         final int layer;
         final SurfaceControl surface;
 
-        BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b, int layerStack)
-                throws OutOfResourcesException {
+        BlackSurface(int layer,
+                int l, int t, int r, int b, DisplayContent dc) throws OutOfResourcesException {
             left = l;
             top = t;
             this.layer = layer;
             int w = r-l;
             int h = b-t;
 
-            surface = new SurfaceControl.Builder(session)
+            surface = dc.makeOverlay()
                     .setName("BlackSurface")
                     .setSize(w, h)
                     .setColorLayer(true)
+                    .setParent(null) // TODO: Work-around for b/69259549
                     .build();
 
             surface.setAlpha(1);
-            surface.setLayerStack(layerStack);
             surface.setLayer(layer);
             surface.show();
             if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
@@ -114,30 +113,32 @@
         }
     }
 
-    public BlackFrame(SurfaceSession session, Rect outer, Rect inner, int layer, int layerStack,
+    public BlackFrame(Rect outer, Rect inner, int layer, DisplayContent dc,
             boolean forceDefaultOrientation) throws OutOfResourcesException {
         boolean success = false;
 
         mForceDefaultOrientation = forceDefaultOrientation;
 
+        // TODO: Why do we use 4 surfaces instead of just one big one behind the screenshot?
+        // b/68253229
         mOuterRect = new Rect(outer);
         mInnerRect = new Rect(inner);
         try {
             if (outer.top < inner.top) {
-                mBlackSurfaces[0] = new BlackSurface(session, layer,
-                        outer.left, outer.top, inner.right, inner.top, layerStack);
+                mBlackSurfaces[0] = new BlackSurface(layer,
+                        outer.left, outer.top, inner.right, inner.top, dc);
             }
             if (outer.left < inner.left) {
-                mBlackSurfaces[1] = new BlackSurface(session, layer,
-                        outer.left, inner.top, inner.left, outer.bottom, layerStack);
+                mBlackSurfaces[1] = new BlackSurface(layer,
+                        outer.left, inner.top, inner.left, outer.bottom, dc);
             }
             if (outer.bottom > inner.bottom) {
-                mBlackSurfaces[2] = new BlackSurface(session, layer,
-                        inner.left, inner.bottom, outer.right, outer.bottom, layerStack);
+                mBlackSurfaces[2] = new BlackSurface(layer,
+                        inner.left, inner.bottom, outer.right, outer.bottom, dc);
             }
             if (outer.right > inner.right) {
-                mBlackSurfaces[3] = new BlackSurface(session, layer,
-                        inner.right, outer.top, outer.right, inner.bottom, layerStack);
+                mBlackSurfaces[3] = new BlackSurface(layer,
+                        inner.right, outer.top, outer.right, inner.bottom, dc);
             }
             success = true;
         } finally {
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index 2d5d1b2..2a216ab 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -33,7 +33,6 @@
 import android.view.Surface;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 
 class CircularDisplayMask {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "CircularDisplayMask" : TAG_WM;
@@ -54,8 +53,10 @@
     private boolean mDimensionsUnequal = false;
     private int mMaskThickness;
 
-    public CircularDisplayMask(Display display, SurfaceSession session, int zOrder,
+    public CircularDisplayMask(DisplayContent dc, int zOrder,
             int screenOffset, int maskThickness) {
+        final Display display = dc.getDisplay();
+
         mScreenSize = new Point();
         display.getSize(mScreenSize);
         if (mScreenSize.x != mScreenSize.y + screenOffset) {
@@ -66,7 +67,7 @@
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("CircularDisplayMask")
                     .setSize(mScreenSize.x, mScreenSize.y) // not a typo
                     .setFormat(PixelFormat.TRANSLUCENT)
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index cc94807..b534b8a 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -375,6 +375,10 @@
         return toString();
     }
 
+    boolean isAlwaysOnTop() {
+        return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
+    }
+
     abstract protected int getChildCount();
 
     abstract protected E getChildAt(int index);
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
deleted file mode 100644
index 8fb2be8..0000000
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.util.Slog;
-import android.view.DisplayInfo;
-import android.view.SurfaceControl;
-
-import java.io.PrintWriter;
-
-public class DimLayer {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "DimLayer" : TAG_WM;
-    private final WindowManagerService mService;
-
-    /** Actual surface that dims */
-    private SurfaceControl mDimSurface;
-
-    /** Last value passed to mDimSurface.setAlpha() */
-    private float mAlpha = 0;
-
-    /** Last value passed to mDimSurface.setLayer() */
-    private int mLayer = -1;
-
-    /** Next values to pass to mDimSurface.setPosition() and mDimSurface.setSize() */
-    private final Rect mBounds = new Rect();
-
-    /** Last values passed to mDimSurface.setPosition() and mDimSurface.setSize() */
-    private final Rect mLastBounds = new Rect();
-
-    /** True after mDimSurface.show() has been called, false after mDimSurface.hide(). */
-    private boolean mShowing = false;
-
-    /** Value of mAlpha when beginning transition to mTargetAlpha */
-    private float mStartAlpha = 0;
-
-    /** Final value of mAlpha following transition */
-    private float mTargetAlpha = 0;
-
-    /** Time in units of SystemClock.uptimeMillis() at which the current transition started */
-    private long mStartTime;
-
-    /** Time in milliseconds to take to transition from mStartAlpha to mTargetAlpha */
-    private long mDuration;
-
-    private boolean mDestroyed = false;
-
-    private final int mDisplayId;
-
-
-    /** Interface implemented by users of the dim layer */
-    interface DimLayerUser {
-        /** Returns true if the  dim should be fullscreen. */
-        boolean dimFullscreen();
-        /** Returns the display info. of the dim layer user. */
-        DisplayInfo getDisplayInfo();
-        /** Returns true if the dim layer user is currently attached to a display */
-        boolean isAttachedToDisplay();
-        /** Gets the bounds of the dim layer user. */
-        void getDimBounds(Rect outBounds);
-        /** Returns the layer to place a dim layer. */
-        default int getLayerForDim(WindowStateAnimator animator, int layerOffset,
-                int defaultLayer) {
-            return defaultLayer;
-        }
-
-        String toShortString();
-    }
-    /** The user of this dim layer. */
-    private final DimLayerUser mUser;
-
-    private final String mName;
-
-    DimLayer(WindowManagerService service, DimLayerUser user, int displayId, String name) {
-        mUser = user;
-        mDisplayId = displayId;
-        mService = service;
-        mName = name;
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "Ctor: displayId=" + displayId);
-    }
-
-    private void constructSurface(WindowManagerService service) {
-        service.openSurfaceTransaction();
-        try {
-            mDimSurface = new SurfaceControl.Builder(service.mFxSession)
-                    .setName(mName)
-                    .setSize(16, 16)
-                    .setColorLayer(true)
-                    .build();
-
-            if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
-                    "  DIM " + mDimSurface + ": CREATE");
-            mDimSurface.setLayerStack(mDisplayId);
-            adjustBounds();
-            adjustAlpha(mAlpha);
-            adjustLayer(mLayer);
-        } catch (Exception e) {
-            Slog.e(TAG_WM, "Exception creating Dim surface", e);
-        } finally {
-            service.closeSurfaceTransaction("DimLayer.constructSurface");
-        }
-    }
-
-    /** Return true if dim layer is showing */
-    boolean isDimming() {
-        return mTargetAlpha != 0;
-    }
-
-    /** Return true if in a transition period */
-    boolean isAnimating() {
-        return mTargetAlpha != mAlpha;
-    }
-
-    float getTargetAlpha() {
-        return mTargetAlpha;
-    }
-
-    void setLayer(int layer) {
-        if (mLayer == layer) {
-            return;
-        }
-        mLayer = layer;
-        adjustLayer(layer);
-    }
-
-    private void adjustLayer(int layer) {
-        if (mDimSurface != null) {
-            mDimSurface.setLayer(layer);
-        }
-    }
-
-    int getLayer() {
-        return mLayer;
-    }
-
-    private void setAlpha(float alpha) {
-        if (mAlpha == alpha) {
-            return;
-        }
-        mAlpha = alpha;
-        adjustAlpha(alpha);
-    }
-
-    private void adjustAlpha(float alpha) {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha alpha=" + alpha);
-        try {
-            if (mDimSurface != null) {
-                mDimSurface.setAlpha(alpha);
-            }
-            if (alpha == 0 && mShowing) {
-                if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha hiding");
-                if (mDimSurface != null) {
-                    mDimSurface.hide();
-                    mShowing = false;
-                }
-            } else if (alpha > 0 && !mShowing) {
-                if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha showing");
-                if (mDimSurface != null) {
-                    mDimSurface.show();
-                    mShowing = true;
-                }
-            }
-        } catch (RuntimeException e) {
-            Slog.w(TAG, "Failure setting alpha immediately", e);
-        }
-    }
-
-    /**
-     * NOTE: Must be called with Surface transaction open.
-     */
-    private void adjustBounds() {
-        if (mUser.dimFullscreen()) {
-            getBoundsForFullscreen(mBounds);
-        }
-
-        if (mDimSurface != null) {
-            mDimSurface.setPosition(mBounds.left, mBounds.top);
-            mDimSurface.setSize(mBounds.width(), mBounds.height());
-            if (DEBUG_DIM_LAYER) Slog.v(TAG,
-                    "adjustBounds user=" + mUser.toShortString() + " mBounds=" + mBounds);
-        }
-
-        mLastBounds.set(mBounds);
-    }
-
-    private void getBoundsForFullscreen(Rect outBounds) {
-        final int dw, dh;
-        final float xPos, yPos;
-        // Set surface size to screen size.
-        final DisplayInfo info = mUser.getDisplayInfo();
-        // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose
-        // a corner.
-        dw = (int) (info.logicalWidth * 1.5);
-        dh = (int) (info.logicalHeight * 1.5);
-        // back off position so 1/4 of Surface is before and 1/4 is after.
-        xPos = -1 * dw / 6;
-        yPos = -1 * dh / 6;
-        outBounds.set((int) xPos, (int) yPos, (int) xPos + dw, (int) yPos + dh);
-    }
-
-    void setBoundsForFullscreen() {
-        getBoundsForFullscreen(mBounds);
-        setBounds(mBounds);
-    }
-
-    /** @param bounds The new bounds to set */
-    void setBounds(Rect bounds) {
-        mBounds.set(bounds);
-        if (isDimming() && !mLastBounds.equals(bounds)) {
-            try {
-                mService.openSurfaceTransaction();
-                adjustBounds();
-            } catch (RuntimeException e) {
-                Slog.w(TAG, "Failure setting size", e);
-            } finally {
-                mService.closeSurfaceTransaction("DimLayer.setBounds");
-            }
-        }
-    }
-
-    /**
-     * @param duration The time to test.
-     * @return True if the duration would lead to an earlier end to the current animation.
-     */
-    private boolean durationEndsEarlier(long duration) {
-        return SystemClock.uptimeMillis() + duration < mStartTime + mDuration;
-    }
-
-    /** Jump to the end of the animation.
-     * NOTE: Must be called with Surface transaction open. */
-    void show() {
-        if (isAnimating()) {
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: immediate");
-            show(mLayer, mTargetAlpha, 0);
-        }
-    }
-
-    /**
-     * Begin an animation to a new dim value.
-     * NOTE: Must be called with Surface transaction open.
-     *
-     * @param layer The layer to set the surface to.
-     * @param alpha The dim value to end at.
-     * @param duration How long to take to get there in milliseconds.
-     */
-    void show(int layer, float alpha, long duration) {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: layer=" + layer + " alpha=" + alpha
-                + " duration=" + duration + ", mDestroyed=" + mDestroyed);
-        if (mDestroyed) {
-            Slog.e(TAG, "show: no Surface");
-            // Make sure isAnimating() returns false.
-            mTargetAlpha = mAlpha = 0;
-            return;
-        }
-
-        if (mDimSurface == null) {
-            constructSurface(mService);
-        }
-
-        if (!mLastBounds.equals(mBounds)) {
-            adjustBounds();
-        }
-        setLayer(layer);
-
-        long curTime = SystemClock.uptimeMillis();
-        final boolean animating = isAnimating();
-        if ((animating && (mTargetAlpha != alpha || durationEndsEarlier(duration)))
-                || (!animating && mAlpha != alpha)) {
-            if (duration <= 0) {
-                // No animation required, just set values.
-                setAlpha(alpha);
-            } else {
-                // Start or continue animation with new parameters.
-                mStartAlpha = mAlpha;
-                mStartTime = curTime;
-                mDuration = duration;
-            }
-        }
-        mTargetAlpha = alpha;
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime="
-                + mStartTime + " mTargetAlpha=" + mTargetAlpha);
-    }
-
-    /** Immediate hide.
-     * NOTE: Must be called with Surface transaction open. */
-    void hide() {
-        if (mShowing) {
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: immediate");
-            hide(0);
-        }
-    }
-
-    /**
-     * Gradually fade to transparent.
-     * NOTE: Must be called with Surface transaction open.
-     *
-     * @param duration Time to fade in milliseconds.
-     */
-    void hide(long duration) {
-        if (mShowing && (mTargetAlpha != 0 || durationEndsEarlier(duration))) {
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: duration=" + duration);
-            show(mLayer, 0, duration);
-        }
-    }
-
-    /**
-     * Advance the dimming per the last #show(int, float, long) call.
-     * NOTE: Must be called with Surface transaction open.
-     *
-     * @return True if animation is still required after this step.
-     */
-    boolean stepAnimation() {
-        if (mDestroyed) {
-            Slog.e(TAG, "stepAnimation: surface destroyed");
-            // Ensure that isAnimating() returns false;
-            mTargetAlpha = mAlpha = 0;
-            return false;
-        }
-        if (isAnimating()) {
-            final long curTime = SystemClock.uptimeMillis();
-            final float alphaDelta = mTargetAlpha - mStartAlpha;
-            float alpha = mStartAlpha + alphaDelta * (curTime - mStartTime) / mDuration;
-            if (alphaDelta > 0 && alpha > mTargetAlpha ||
-                    alphaDelta < 0 && alpha < mTargetAlpha) {
-                // Don't exceed limits.
-                alpha = mTargetAlpha;
-            }
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "stepAnimation: curTime=" + curTime + " alpha=" + alpha);
-            setAlpha(alpha);
-        }
-
-        return isAnimating();
-    }
-
-    /** Cleanup */
-    void destroySurface() {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "destroySurface.");
-        if (mDimSurface != null) {
-            mDimSurface.destroy();
-            mDimSurface = null;
-        }
-        mDestroyed = true;
-    }
-
-    public void printTo(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("mDimSurface="); pw.print(mDimSurface);
-                pw.print(" mLayer="); pw.print(mLayer);
-                pw.print(" mAlpha="); pw.println(mAlpha);
-        pw.print(prefix); pw.print("mLastBounds="); pw.print(mLastBounds.toShortString());
-                pw.print(" mBounds="); pw.println(mBounds.toShortString());
-        pw.print(prefix); pw.print("Last animation: ");
-                pw.print(" mDuration="); pw.print(mDuration);
-                pw.print(" mStartTime="); pw.print(mStartTime);
-                pw.print(" curTime="); pw.println(SystemClock.uptimeMillis());
-        pw.print(prefix); pw.print(" mStartAlpha="); pw.print(mStartAlpha);
-                pw.print(" mTargetAlpha="); pw.println(mTargetAlpha);
-    }
-}
diff --git a/services/core/java/com/android/server/wm/DimLayerController.java b/services/core/java/com/android/server/wm/DimLayerController.java
deleted file mode 100644
index 6f9e45a..0000000
--- a/services/core/java/com/android/server/wm/DimLayerController.java
+++ /dev/null
@@ -1,403 +0,0 @@
-package com.android.server.wm;
-
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
-
-import android.graphics.Rect;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.util.TypedValue;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.DimLayer.DimLayerUser;
-
-import java.io.PrintWriter;
-
-/**
- * Centralizes the control of dim layers used for
- * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
- * as well as other use cases (such as dimming above a dead window).
- */
-class DimLayerController {
-    private static final String TAG_LOCAL = "DimLayerController";
-    private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
-
-    /** Amount of time in milliseconds to animate the dim surface from one value to another,
-     * when no window animation is driving it. */
-    private static final int DEFAULT_DIM_DURATION = 200;
-
-    /**
-     * The default amount of dim applied over a dead window
-     */
-    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
-
-    // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this
-    // instead of creating a new object per fullscreen task on a display.
-    private DimLayer mSharedFullScreenDimLayer;
-
-    private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>();
-
-    private DisplayContent mDisplayContent;
-
-    private Rect mTmpBounds = new Rect();
-
-    DimLayerController(DisplayContent displayContent) {
-        mDisplayContent = displayContent;
-    }
-
-    /** Updates the dim layer bounds, recreating it if needed. */
-    void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
-        final DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
-        final boolean previousFullscreen = state.dimLayer != null
-                && state.dimLayer == mSharedFullScreenDimLayer;
-        DimLayer newDimLayer;
-        final int displayId = mDisplayContent.getDisplayId();
-        if (dimLayerUser.dimFullscreen()) {
-            if (previousFullscreen && mSharedFullScreenDimLayer != null) {
-                // Update the bounds for fullscreen in case of rotation.
-                mSharedFullScreenDimLayer.setBoundsForFullscreen();
-                return;
-            }
-            // Use shared fullscreen dim layer
-            newDimLayer = mSharedFullScreenDimLayer;
-            if (newDimLayer == null) {
-                if (state.dimLayer != null) {
-                    // Re-purpose the previous dim layer.
-                    newDimLayer = state.dimLayer;
-                } else {
-                    // Create new full screen dim layer.
-                    newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
-                            getDimLayerTag(dimLayerUser));
-                }
-                dimLayerUser.getDimBounds(mTmpBounds);
-                newDimLayer.setBounds(mTmpBounds);
-                mSharedFullScreenDimLayer = newDimLayer;
-            } else if (state.dimLayer != null) {
-                state.dimLayer.destroySurface();
-            }
-        } else {
-            newDimLayer = (state.dimLayer == null || previousFullscreen)
-                    ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
-                            getDimLayerTag(dimLayerUser))
-                    : state.dimLayer;
-            dimLayerUser.getDimBounds(mTmpBounds);
-            newDimLayer.setBounds(mTmpBounds);
-        }
-        state.dimLayer = newDimLayer;
-    }
-
-    private static String getDimLayerTag(DimLayerUser dimLayerUser) {
-        return TAG_LOCAL + "/" + dimLayerUser.toShortString();
-    }
-
-    private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
-                + dimLayerUser.toShortString());
-        DimLayerState state = mState.get(dimLayerUser);
-        if (state == null) {
-            state = new DimLayerState();
-            mState.put(dimLayerUser, state);
-        }
-        return state;
-    }
-
-    private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        if (state == null) {
-            if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
-                    + dimLayerUser.toShortString());
-            return;
-        }
-        state.continueDimming = true;
-    }
-
-    boolean isDimming() {
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            DimLayerState state = mState.valueAt(i);
-            if (state.dimLayer != null && state.dimLayer.isDimming()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void resetDimming() {
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            mState.valueAt(i).continueDimming = false;
-        }
-    }
-
-    private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        return state != null && state.continueDimming;
-    }
-
-    void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
-            WindowStateAnimator newWinAnimator, boolean aboveApp) {
-        // Only set dim params on the highest dimmed layer.
-        // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
-        DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
-        state.dimAbove = aboveApp;
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
-                + " dimLayerUser=" + dimLayerUser.toShortString()
-                + " newWinAnimator=" + newWinAnimator
-                + " state.animator=" + state.animator);
-        if (newWinAnimator.getShown() && (state.animator == null
-                || !state.animator.getShown()
-                || state.animator.mAnimLayer <= newWinAnimator.mAnimLayer)) {
-            state.animator = newWinAnimator;
-            if (state.animator.mWin.mAppToken == null && !dimLayerUser.dimFullscreen()) {
-                // Dim should cover the entire screen for system windows.
-                mDisplayContent.getLogicalDisplayRect(mTmpBounds);
-            } else {
-                dimLayerUser.getDimBounds(mTmpBounds);
-            }
-            state.dimLayer.setBounds(mTmpBounds);
-        }
-    }
-
-    void stopDimmingIfNeeded() {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded, mState.size()=" + mState.size());
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i);
-            stopDimmingIfNeeded(dimLayerUser);
-        }
-    }
-
-    private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
-        // No need to check if state is null, we know the key has a value.
-        DimLayerState state = mState.get(dimLayerUser);
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
-                + " dimLayerUser=" + dimLayerUser.toShortString()
-                + " state.continueDimming=" + state.continueDimming
-                + " state.dimLayer.isDimming=" + state.dimLayer.isDimming());
-        if (state.animator != null && state.animator.mWin.mWillReplaceWindow) {
-            return;
-        }
-
-        if (!state.continueDimming && state.dimLayer.isDimming()) {
-            state.animator = null;
-            dimLayerUser.getDimBounds(mTmpBounds);
-            state.dimLayer.setBounds(mTmpBounds);
-        }
-    }
-
-    boolean animateDimLayers() {
-        int fullScreen = -1;
-        int fullScreenAndDimming = -1;
-        int topFullScreenUserLayer = 0;
-        boolean result = false;
-
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            final DimLayer.DimLayerUser user = mState.keyAt(i);
-            final DimLayerState state = mState.valueAt(i);
-
-            if (!user.isAttachedToDisplay()) {
-                // Leaked dim user that is no longer attached to the display. Go ahead and clean it
-                // clean-up and log what happened.
-                // TODO: This is a work around for b/34395537 as the dim user should have cleaned-up
-                // it self when it was detached from the display. Need to investigate how the dim
-                // user is leaking...
-                //Slog.wtfStack(TAG_WM, "Leaked dim user=" + user.toShortString()
-                //        + " state=" + state);
-                Slog.w(TAG_WM, "Leaked dim user=" + user.toShortString() + " state=" + state);
-                removeDimLayerUser(user);
-                continue;
-            }
-
-            // We have to check that we are actually the shared fullscreen layer
-            // for this path. If we began as non fullscreen and became fullscreen
-            // (e.g. Docked stack closing), then we may not be the shared layer
-            // and we have to make sure we always animate the layer.
-            if (user.dimFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) {
-                fullScreen = i;
-                if (!state.continueDimming) {
-                    continue;
-                }
-
-                // When choosing which user to assign the shared fullscreen layer to
-                // we need to look at Z-order.
-                if (topFullScreenUserLayer == 0 ||
-                        (state.animator != null && state.animator.mAnimLayer > topFullScreenUserLayer)) {
-                    fullScreenAndDimming = i;
-                    if (state.animator != null) {
-                        topFullScreenUserLayer = state.animator.mAnimLayer;
-                    }
-                }
-            } else {
-                // We always want to animate the non fullscreen windows, they don't share their
-                // dim layers.
-                result |= animateDimLayers(user);
-            }
-        }
-        // For the shared, full screen dim layer, we prefer the animation that is causing it to
-        // appear.
-        if (fullScreenAndDimming != -1) {
-            result |= animateDimLayers(mState.keyAt(fullScreenAndDimming));
-        } else if (fullScreen != -1) {
-            // If there is no animation for the full screen dim layer to appear, we can use any of
-            // the animators that will cause it to disappear.
-            result |= animateDimLayers(mState.keyAt(fullScreen));
-        }
-        return result;
-    }
-
-    private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
-                + " dimLayerUser=" + dimLayerUser.toShortString()
-                + " state.animator=" + state.animator
-                + " state.continueDimming=" + state.continueDimming);
-        final int dimLayer;
-        final float dimAmount;
-        if (state.animator == null) {
-            dimLayer = state.dimLayer.getLayer();
-            dimAmount = 0;
-        } else {
-            if (state.dimAbove) {
-                dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
-                dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
-            } else {
-                dimLayer = dimLayerUser.getLayerForDim(state.animator, LAYER_OFFSET_DIM,
-                        state.animator.mAnimLayer - LAYER_OFFSET_DIM);
-                dimAmount = state.animator.mWin.mAttrs.dimAmount;
-            }
-        }
-        final float targetAlpha = state.dimLayer.getTargetAlpha();
-        if (targetAlpha != dimAmount) {
-            if (state.animator == null) {
-                state.dimLayer.hide(DEFAULT_DIM_DURATION);
-            } else {
-                long duration = (state.animator.mAnimating && state.animator.mAnimation != null)
-                        ? state.animator.mAnimation.computeDurationHint()
-                        : DEFAULT_DIM_DURATION;
-                if (targetAlpha > dimAmount) {
-                    duration = getDimLayerFadeDuration(duration);
-                }
-                state.dimLayer.show(dimLayer, dimAmount, duration);
-
-                // If we showed a dim layer, make sure to redo the layout because some things depend
-                // on whether a dim layer is showing or not.
-                if (targetAlpha == 0) {
-                    mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
-                    mDisplayContent.setLayoutNeeded();
-                }
-            }
-        } else if (state.dimLayer.getLayer() != dimLayer) {
-            state.dimLayer.setLayer(dimLayer);
-        }
-        if (state.dimLayer.isAnimating()) {
-            if (!mDisplayContent.okToAnimate()) {
-                // Jump to the end of the animation.
-                state.dimLayer.show();
-            } else {
-                return state.dimLayer.stepAnimation();
-            }
-        }
-        return false;
-    }
-
-    boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
-        DimLayerState state = mState.get(dimLayerUser);
-        return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
-    }
-
-    private long getDimLayerFadeDuration(long duration) {
-        TypedValue tv = new TypedValue();
-        mDisplayContent.mService.mContext.getResources().getValue(
-                com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
-        if (tv.type == TypedValue.TYPE_FRACTION) {
-            duration = (long) tv.getFraction(duration, duration);
-        } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
-            duration = tv.data;
-        }
-        return duration;
-    }
-
-    void close() {
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            DimLayerState state = mState.valueAt(i);
-            state.dimLayer.destroySurface();
-        }
-        mState.clear();
-        mSharedFullScreenDimLayer = null;
-    }
-
-    void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        if (state != null) {
-            // Destroy the surface, unless it's the shared fullscreen dim.
-            if (state.dimLayer != mSharedFullScreenDimLayer) {
-                state.dimLayer.destroySurface();
-            }
-            mState.remove(dimLayerUser);
-        }
-        if (mState.isEmpty()) {
-            mSharedFullScreenDimLayer = null;
-        }
-    }
-
-    @VisibleForTesting
-    boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
-        return mState.containsKey(dimLayerUser);
-    }
-
-    @VisibleForTesting
-    boolean hasSharedFullScreenDimLayer() {
-        return mSharedFullScreenDimLayer != null;
-    }
-
-    void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
-        applyDim(dimLayerUser, animator, false /* aboveApp */);
-    }
-
-    void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
-        applyDim(dimLayerUser, animator, true /* aboveApp */);
-    }
-
-    void applyDim(
-            DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
-        if (dimLayerUser == null) {
-            Slog.e(TAG, "Trying to apply dim layer for: " + this
-                    + ", but no dim layer user found.");
-            return;
-        }
-        if (!getContinueDimming(dimLayerUser)) {
-            setContinueDimming(dimLayerUser);
-            if (!isDimming(dimLayerUser, animator)) {
-                if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
-                startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
-            }
-        }
-    }
-
-    private static class DimLayerState {
-        // The particular window requesting a dim layer. If null, hide dimLayer.
-        WindowStateAnimator animator;
-        // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
-        // end then stop any dimming.
-        boolean continueDimming;
-        DimLayer dimLayer;
-        boolean dimAbove;
-    }
-
-    void dump(String prefix, PrintWriter pw) {
-        pw.println(prefix + "DimLayerController");
-        final String doubleSpace = "  ";
-        final String prefixPlusDoubleSpace = prefix + doubleSpace;
-
-        for (int i = 0, n = mState.size(); i < n; i++) {
-            pw.println(prefixPlusDoubleSpace + mState.keyAt(i).toShortString());
-            DimLayerState state = mState.valueAt(i);
-            pw.println(prefixPlusDoubleSpace + doubleSpace + "dimLayer="
-                    + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" : state.dimLayer)
-                    + ", animator=" + state.animator + ", continueDimming=" + state.continueDimming);
-            if (state.dimLayer != null) {
-                state.dimLayer.printTo(prefixPlusDoubleSpace + doubleSpace, pw);
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
new file mode 100644
index 0000000..9fe16ae
--- /dev/null
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -0,0 +1,195 @@
+/*
+ * 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.util.ArrayMap;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.graphics.Rect;
+
+/**
+ * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
+ * black layers of varying opacity at various Z-levels which create the effect of a Dim.
+ */
+class Dimmer {
+    private static final String TAG = "WindowManager";
+
+    private class DimState {
+        SurfaceControl mSurfaceControl;
+        boolean mDimming;
+
+        /**
+         * Used for Dims not assosciated with a WindowContainer. See {@link Dimmer#dimAbove} for
+         * details on Dim lifecycle.
+         */
+        boolean mDontReset;
+
+        DimState(SurfaceControl ctl) {
+            mSurfaceControl = ctl;
+            mDimming = true;
+        }
+    };
+
+    private ArrayMap<WindowContainer, DimState> mDimLayerUsers = new ArrayMap<>();
+
+    /**
+     * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
+     * host, some controller of it, or one of the hosts children.
+     */
+    private WindowContainer mHost;
+
+    Dimmer(WindowContainer host) {
+        mHost = host;
+    }
+
+    SurfaceControl makeDimLayer() {
+        final SurfaceControl control = mHost.makeChildSurface(null)
+                .setParent(mHost.getSurfaceControl())
+                .setColorLayer(true)
+                .setName("Dim Layer for - " + mHost.getName())
+                .build();
+        return control;
+    }
+
+    /**
+     * Retreive the DimState for a given child of the host.
+     */
+    DimState getDimState(WindowContainer container) {
+        DimState state = mDimLayerUsers.get(container);
+        if (state == null) {
+            final SurfaceControl ctl = makeDimLayer();
+            state = new DimState(ctl);
+            /**
+             * See documentation on {@link #dimAbove} to understand lifecycle management of Dim's
+             * via state resetting for Dim's with containers.
+             */
+            if (container == null) {
+                state.mDontReset = true;
+            }
+            mDimLayerUsers.put(container, state);
+        }
+        return state;
+    }
+
+    private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer,
+            float alpha) {
+        final DimState d = getDimState(container);
+        t.show(d.mSurfaceControl);
+        if (container != null) {
+            t.setRelativeLayer(d.mSurfaceControl,
+                    container.getSurfaceControl(), relativeLayer);
+        } else {
+            t.setLayer(d.mSurfaceControl, Integer.MAX_VALUE);
+        }
+        t.setAlpha(d.mSurfaceControl, alpha);
+
+        d.mDimming = true;
+    }
+
+    /**
+     * Finish a dim started by dimAbove in the case there was no call to dimAbove.
+     *
+     * @param t A Transaction in which to finish the dim.
+     */
+    void stopDim(SurfaceControl.Transaction t) {
+        DimState d = getDimState(null);
+        t.hide(d.mSurfaceControl);
+        d.mDontReset = false;
+    }
+    /**
+     * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
+     * remove this effect. If the Dim can be assosciated with a particular child of the host
+     * consider using the other variant of dimAbove which ties the Dim lifetime to the child
+     * lifetime more explicitly.
+     *
+     * @param t A transaction in which to apply the Dim.
+     * @param alpha The alpha at which to Dim.
+     */
+    void dimAbove(SurfaceControl.Transaction t, float alpha) {
+        dim(t, null, 1, alpha);
+    }
+
+    /**
+     * Place a dim above the given container, which should be a child of the host container.
+     * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
+     * and the child should call dimAbove again to request the Dim to continue.
+     *
+     * @param t A transaction in which to apply the Dim.
+     * @param container The container which to dim above. Should be a child of our host.
+     * @param alpha The alpha at which to Dim.
+     */
+    void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
+        dim(t, container, 1, alpha);
+    }
+
+    /**
+     * Like {@link #dimAbove} but places the dim below the given container.
+     *
+     * @param t A transaction in which to apply the Dim.
+     * @param container The container which to dim below. Should be a child of our host.
+     * @param alpha The alpha at which to Dim.
+     */
+
+    void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
+        dim(t, container, -1, alpha);
+    }
+
+    /**
+     * Mark all dims as pending completion on the next call to {@link #updateDims}
+     *
+     * This is intended for us by the host container, to be called at the beginning of
+     * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
+     * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
+     * a chance to request dims to continue.
+     */
+    void resetDimStates() {
+        for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
+            final DimState state = mDimLayerUsers.valueAt(i);
+            if (state.mDontReset == false) {
+                state.mDimming = false;
+            }
+        }
+    }
+
+    /**
+     * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
+     * described in {@link #resetDimStates}.
+     *
+     * @param t A transaction in which to update the dims.
+     * @param bounds The bounds at which to dim.
+     * @return true if any Dims were updated.
+     */
+    boolean updateDims(SurfaceControl.Transaction t, Rect bounds) {
+        boolean didSomething = false;
+        for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
+            DimState state = mDimLayerUsers.valueAt(i);
+            // TODO: We want to animate the addition and removal of Dim's instead of immediately
+            // acting. When we do this we need to take care to account for the "Replacing Windows"
+            // case (and seamless dim transfer).
+            if (state.mDimming == false) {
+                mDimLayerUsers.removeAt(i);
+                state.mSurfaceControl.destroy();
+            } else {
+                didSomething = true;
+                // TODO: Once we use geometry from hierarchy this falls away.
+                t.setSize(state.mSurfaceControl, bounds.width(), bounds.height());
+                t.setPosition(state.mSurfaceControl, bounds.left, bounds.top);
+            }
+        }
+        return didSomething;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4d839d0..17312b2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -142,8 +142,10 @@
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.InputDevice;
+import android.view.MagnificationSpec;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.view.WindowManagerPolicy;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -321,8 +323,6 @@
     final DockedStackDividerController mDividerControllerLocked;
     final PinnedStackController mPinnedStackControllerLocked;
 
-    DimLayerController mDimLayerController;
-
     final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
 
     private boolean mHaveBootMsg = false;
@@ -346,10 +346,37 @@
     // {@code false} if this display is in the processing of being created.
     private boolean mDisplayReady = false;
 
-    private final WindowLayersController mLayersController;
     WallpaperController mWallpaperController;
     int mInputMethodAnimLayerAdjustment;
 
+    private final SurfaceSession mSession = new SurfaceSession();
+
+    /**
+     * We organize all top-level Surfaces in to the following layers.
+     * mOverlayLayer contains a few Surfaces which are always on top of others
+     * and omitted from Screen-Magnification ({@link WindowState#isScreenOverlay})
+     * {@link #mWindowingLayer} contains everything else.
+     */
+    private SurfaceControl mOverlayLayer;
+
+    /**
+     * See {@link #mOverlayLayer}
+     */
+    private SurfaceControl mWindowingLayer;
+
+    /**
+     * Specifies the size of the surfaces in {@link #mOverlayLayer} and {@link #mWindowingLayer}.
+     * <p>
+     * For these surfaces currently we use a surface based on the larger of width or height so we
+     * don't have to resize when rotating the display.
+     */
+    private int mSurfaceSize;
+
+    /**
+     * A list of surfaces to be destroyed after {@link #mPendingTransaction} is applied.
+     */
+    private final ArrayList<SurfaceControl> mPendingDestroyingSurfaces = new ArrayList<>();
+
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
         if (winAnimator.hasSurface()) {
@@ -503,9 +530,6 @@
         return true;
     };
 
-    private final Consumer<WindowState> mPrepareWindowSurfaces =
-            w -> w.mWinAnimator.prepareSurfaceLocked(true);
-
     private final Consumer<WindowState> mPerformLayout = w -> {
         // Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
         // wasting time and funky changes while a window is animating away.
@@ -558,12 +582,6 @@
                     w.updateLastInsetValues();
                 }
 
-                // Window frames may have changed. Update dim layer with the new bounds.
-                final Task task = w.getTask();
-                if (task != null) {
-                    mDimLayerController.updateDimLayer(task);
-                }
-
                 if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.mFrame
                         + " mContainingFrame=" + w.mContainingFrame
                         + " mDisplayFrame=" + w.mDisplayFrame);
@@ -657,8 +675,6 @@
             }
         }
 
-        w.applyDimLayerIfNeeded();
-
         if (isDefaultDisplay && obscuredChanged && w.isVisibleLw()
                 && mWallpaperController.isWallpaperTarget(w)) {
             // This is the wallpaper target and its obscured state changed... make sure the
@@ -741,13 +757,11 @@
      * initialize direct children.
      * @param display May not be null.
      * @param service You know.
-     * @param layersController window layer controller used to assign layer to the windows on this
-     *                         display.
      * @param wallpaperController wallpaper windows controller used to adjust the positioning of the
      *                            wallpaper windows in the window list.
      */
     DisplayContent(Display display, WindowManagerService service,
-            WindowLayersController layersController, WallpaperController wallpaperController) {
+            WallpaperController wallpaperController) {
         if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
             throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
                     + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
@@ -756,7 +770,6 @@
 
         mDisplay = display;
         mDisplayId = display.getDisplayId();
-        mLayersController = layersController;
         mWallpaperController = wallpaperController;
         display.getDisplayInfo(mDisplayInfo);
         display.getMetrics(mDisplayMetrics);
@@ -766,7 +779,22 @@
         initializeDisplayBaseInfo();
         mDividerControllerLocked = new DockedStackDividerController(service, this);
         mPinnedStackControllerLocked = new PinnedStackController(service, this);
-        mDimLayerController = new DimLayerController(this);
+
+        mSurfaceSize = Math.max(mBaseDisplayHeight, mBaseDisplayWidth);
+
+        final SurfaceControl.Builder b = mService.makeSurfaceBuilder(mSession)
+                .setSize(mSurfaceSize, mSurfaceSize)
+                .setOpaque(true);
+        mWindowingLayer = b.setName("Display Root").build();
+        mOverlayLayer = b.setName("Display Overlays").build();
+
+        getPendingTransaction().setLayer(mWindowingLayer, 0)
+                .setLayerStack(mWindowingLayer, mDisplayId)
+                .show(mWindowingLayer)
+                .setLayer(mOverlayLayer, 1)
+                .setLayerStack(mOverlayLayer, mDisplayId)
+                .show(mOverlayLayer);
+        getPendingTransaction().apply();
 
         // These are the only direct children we should ever have and they are permanent.
         super.addChild(mBelowAppWindowsContainers, null);
@@ -1030,11 +1058,7 @@
 
         setLayoutNeeded();
         final int[] anim = new int[2];
-        if (isDimming()) {
-            anim[0] = anim[1] = 0;
-        } else {
-            mService.mPolicy.selectRotationAnimationLw(anim);
-        }
+        mService.mPolicy.selectRotationAnimationLw(anim);
 
         if (!rotateSeamlessly) {
             mService.startFreezingDisplayLocked(inTransaction, anim[0], anim[1], this);
@@ -1071,8 +1095,7 @@
             //       it doesn't support hardware OpenGL emulation yet.
             if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
                     && screenRotationAnimation.hasScreenshot()) {
-                if (screenRotationAnimation.setRotationInTransaction(
-                        rotation, mService.mFxSession,
+                if (screenRotationAnimation.setRotationInTransaction(rotation,
                         MAX_ANIMATION_DURATION, mService.getTransitionAnimationScaleLocked(),
                         mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight)) {
                     mService.scheduleAnimationLocked();
@@ -1907,22 +1930,6 @@
         }
     }
 
-    boolean animateDimLayers() {
-        return mDimLayerController.animateDimLayers();
-    }
-
-    private void resetDimming() {
-        mDimLayerController.resetDimming();
-    }
-
-    boolean isDimming() {
-        return mDimLayerController.isDimming();
-    }
-
-    private void stopDimmingIfNeeded() {
-        mDimLayerController.stopDimmingIfNeeded();
-    }
-
     @Override
     void removeIfPossible() {
         if (isAnimating()) {
@@ -1938,7 +1945,6 @@
         try {
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
-            mDimLayerController.close();
             if (mService.canDispatchPointerEvents()) {
                 if (mTapDetector != null) {
                     mService.unregisterPointerEventListener(mTapDetector);
@@ -1947,6 +1953,9 @@
                     mService.unregisterPointerEventListener(mService.mMousePositionTracker);
                 }
             }
+            // The pending transaction won't be applied so we should
+            // just clean up any surfaces pending destruction.
+            onPendingTransactionApplied();
         } finally {
             mRemovingDisplay = false;
         }
@@ -2228,8 +2237,7 @@
                 token.dump(pw, "    ");
             }
         }
-        pw.println();
-        mDimLayerController.dump(prefix, pw);
+
         pw.println();
 
         // Dump stack references
@@ -2342,10 +2350,16 @@
 
     /** Updates the layer assignment of windows on this display. */
     void assignWindowLayers(boolean setLayoutNeeded) {
-        mLayersController.assignWindowLayers(this);
+        assignChildLayers(getPendingTransaction());
         if (setLayoutNeeded) {
             setLayoutNeeded();
         }
+
+        // We accumlate the layer changes in-to "getPendingTransaction()" but we defer
+        // the application of this transaction until the animation pass triggers
+        // prepareSurfaces. This allows us to synchronize Z-ordering changes with
+        // the hiding and showing of surfaces.
+        scheduleAnimation();
     }
 
     // TODO: This should probably be called any time a visual change is made to the hierarchy like
@@ -2701,10 +2715,6 @@
         }
     }
 
-    void prepareWindowSurfaces() {
-        forAllWindows(mPrepareWindowSurfaces, false /* traverseTopToBottom */);
-    }
-
     boolean inputMethodClientHasFocus(IInputMethodClient client) {
         final WindowState imFocus = computeImeTarget(false /* updateImeTarget */);
         if (imFocus == null) {
@@ -2846,7 +2856,6 @@
         } while (pendingLayoutChanges != 0);
 
         mTmpApplySurfaceChangesTransactionState.reset();
-        resetDimming();
 
         mTmpRecoveringMemory = recoveringMemory;
         forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
@@ -2857,8 +2866,6 @@
                 mTmpApplySurfaceChangesTransactionState.preferredModeId,
                 true /* inTraversal, must call performTraversalInTrans... below */);
 
-        stopDimmingIfNeeded();
-
         final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
         if (wallpaperVisible != mLastWallpaperVisible) {
             mLastWallpaperVisible = wallpaperVisible;
@@ -3062,13 +3069,6 @@
                 // Include this window.
 
                 final WindowStateAnimator winAnim = w.mWinAnimator;
-                int layer = winAnim.mSurfaceController.getLayer();
-                if (mScreenshotApplicationState.maxLayer < layer) {
-                    mScreenshotApplicationState.maxLayer = layer;
-                }
-                if (mScreenshotApplicationState.minLayer > layer) {
-                    mScreenshotApplicationState.minLayer = layer;
-                }
 
                 // Don't include wallpaper in bounds calculation
                 if (!w.mIsWallpaper && !mutableIncludeFullDisplay.value) {
@@ -3112,8 +3112,6 @@
 
             final WindowState appWin = mScreenshotApplicationState.appWin;
             final boolean screenshotReady = mScreenshotApplicationState.screenshotReady;
-            final int maxLayer = mScreenshotApplicationState.maxLayer;
-            final int minLayer = mScreenshotApplicationState.minLayer;
 
             if (appToken != null && appWin == null) {
                 // Can't find a window to snapshot.
@@ -3134,11 +3132,6 @@
             // because we don't want to release the mWindowMap lock until the screenshot is
             // taken.
 
-            if (maxLayer == 0) {
-                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
-                        + ": returning null maxLayer=" + maxLayer);
-                return null;
-            }
 
             if (!mutableIncludeFullDisplay.value) {
                 // Constrain frame to the screen size.
@@ -3183,8 +3176,6 @@
             convertCropForSurfaceFlinger(crop, rot, dw, dh);
 
             if (DEBUG_SCREENSHOT) {
-                Slog.i(TAG_WM, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
-                        + maxLayer + " appToken=" + appToken);
                 forAllWindows(w -> {
                     final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController;
                     Slog.i(TAG_WM, w + ": " + w.mLayer
@@ -3206,11 +3197,13 @@
             SurfaceControl.openTransaction();
             SurfaceControl.closeTransactionSync();
 
-            bitmap = screenshoter.screenshot(crop, width, height, minLayer, maxLayer,
+            // TODO(b/68392460): We should screenshot Task controls directly
+            // but it's difficult at the moment as the Task doesn't have the
+            // correct size set.
+            bitmap = screenshoter.screenshot(crop, width, height, 0, 1,
                     inRotation, rot);
             if (bitmap == null) {
-                Slog.w(TAG_WM, "Screenshot failure taking screenshot for (" + dw + "x" + dh
-                        + ") to layer " + maxLayer);
+                Slog.w(TAG_WM, "Failed to take screenshot");
                 return null;
             }
         }
@@ -3366,6 +3359,10 @@
      * I.e Activities.
      */
     private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
+        /**
+         * A control placed at the appropriate level for transitions to occur.
+         */
+        SurfaceControl mAnimationLayer = null;
 
         // Cached reference to some special stacks we tend to get a lot so we don't need to loop
         // through the list to find them.
@@ -3677,6 +3674,50 @@
             // to prevent freezing/unfreezing the display too early.
             return mLastOrientation;
         }
+
+        @Override
+        void assignChildLayers(SurfaceControl.Transaction t) {
+            final int NORMAL_STACK_STATE = 0;
+            final int BOOSTED_STATE = 1;
+            final int ALWAYS_ON_TOP_STATE = 2;
+
+            // 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);
+                }
+                // 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);
+                }
+            }
+        }
+
+        @Override
+        void onParentSet() {
+            super.onParentSet();
+            if (getParent() != null) {
+                mAnimationLayer = makeSurface().build();
+            } else {
+                mAnimationLayer.destroy();
+                mAnimationLayer = null;
+            }
+        }
     }
 
     /**
@@ -3760,4 +3801,119 @@
         E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
                 boolean useIdentityTransform, int rotation);
     }
+
+    SurfaceControl.Builder makeSurface(SurfaceSession s) {
+        return mService.makeSurfaceBuilder(s)
+                .setParent(mWindowingLayer);
+    }
+
+    @Override
+    SurfaceSession getSession() {
+        return mSession;
+    }
+
+    @Override
+    SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+        SurfaceSession s = child != null ? child.getSession() : getSession();
+        final SurfaceControl.Builder b = mService.makeSurfaceBuilder(s);
+        b.setSize(mSurfaceSize, mSurfaceSize);
+
+        if (child == null) {
+            return b;
+        }
+
+        b.setName(child.getName());
+        if (child.isScreenOverlay()) {
+            return b.setParent(mOverlayLayer);
+        } else {
+            return b.setParent(mWindowingLayer);
+        }
+    }
+
+    /**
+     * The makeSurface variants are for use by the window-container
+     * hierarchy. makeOverlay here is a function for various non windowing
+     * overlays like the ScreenRotation screenshot, the Strict Mode Flash
+     * and other potpourii.
+     */
+    SurfaceControl.Builder makeOverlay() {
+        return mService.makeSurfaceBuilder(mSession)
+            .setParent(mOverlayLayer);
+    }
+
+    void applyMagnificationSpec(MagnificationSpec spec) {
+        applyMagnificationSpec(getPendingTransaction(), spec);
+        getPendingTransaction().apply();
+    }
+
+    @Override
+    void onParentSet() {
+        // Since we are the top of the SurfaceControl hierarchy here
+        // we create the root surfaces explicitly rather than chaining
+        // up as the default implementation in onParentSet does. So we
+        // explicitly do NOT call super here.
+    }
+
+    @Override
+    void assignChildLayers(SurfaceControl.Transaction t) {
+        t.setLayer(mOverlayLayer, 1)
+                .setLayer(mWindowingLayer, 0);
+
+        // These are layers as children of "mWindowingLayer"
+        mBelowAppWindowsContainers.assignLayer(t, 0);
+        mTaskStackContainers.assignLayer(t, 1);
+        mAboveAppWindowsContainers.assignLayer(t, 2);
+
+        WindowState imeTarget = mService.mInputMethodTarget;
+        if (imeTarget == null || imeTarget.inSplitScreenWindowingMode()) {
+            // In split-screen windowing mode we can't layer the
+            // IME relative to the IME target because it needs to
+            // go over the docked divider, so instead we place it on top
+            // of everything and use relative layering of windows which need
+            // to go above it (see special logic in WindowState#assignLayer)
+            mImeWindowsContainers.assignLayer(t, 3);
+        } else {
+            t.setRelativeLayer(mImeWindowsContainers.getSurfaceControl(),
+                    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);
+        }
+
+        // Above we have assigned layers to our children, now we ask them to assign
+        // layers to their children.
+        mBelowAppWindowsContainers.assignChildLayers(t);
+        mTaskStackContainers.assignChildLayers(t);
+        mAboveAppWindowsContainers.assignChildLayers(t);
+        mImeWindowsContainers.assignChildLayers(t);
+    }
+
+    /**
+     * Here we satisfy an unfortunate special case of the IME in split-screen mode. Imagine
+     * that the IME target is one of the docked applications. We'd like the docked divider to be
+     * above both of the applications, and we'd like the IME to be above the docked divider.
+     * However we need child windows of the applications to be above the IME (Text drag handles).
+     * This is a non-strictly hierarcical layering and we need to break out of the Z ordering
+     * somehow. We do this by relatively ordering children of the target to the IME in cooperation
+     * with {@link #WindowState#assignLayer}
+     */
+    void assignRelativeLayerForImeTargetChild(SurfaceControl.Transaction t, WindowContainer child) {
+        t.setRelativeLayer(child.getSurfaceControl(), mImeWindowsContainers.getSurfaceControl(), 1);
+    }
+
+    @Override
+    void destroyAfterPendingTransaction(SurfaceControl surface) {
+        mPendingDestroyingSurfaces.add(surface);
+    }
+
+    /**
+     * Destroys any surfaces that have been put into the pending list with
+     * {@link #destroyAfterTransaction}.
+     */
+    void onPendingTransactionApplied() {
+        for (int i = mPendingDestroyingSurfaces.size() - 1; i >= 0; i--) {
+            mPendingDestroyingSurfaces.get(i).destroy();
+        }
+        mPendingDestroyingSurfaces.clear();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index d79ba89..8308417 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -54,7 +54,6 @@
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DockedDividerUtils;
 import com.android.server.LocalServices;
-import com.android.server.wm.DimLayer.DimLayerUser;
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
@@ -62,7 +61,7 @@
 /**
  * Keeps information about the docked stack divider.
  */
-public class DockedStackDividerController implements DimLayerUser {
+public class DockedStackDividerController {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;
 
@@ -114,7 +113,6 @@
     private boolean mLastVisibility = false;
     private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
             = new RemoteCallbackList<>();
-    private final DimLayer mDimLayer;
 
     private boolean mMinimizedDock;
     private int mOriginalDockedSide = DOCKED_INVALID;
@@ -141,13 +139,12 @@
     private boolean mImeHideRequested;
     private final Rect mLastDimLayerRect = new Rect();
     private float mLastDimLayerAlpha;
+    private TaskStack mDimmedStack;
 
     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
         mDisplayContent = displayContent;
         final Context context = service.mContext;
-        mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(),
-                "DockedStackDim");
         mMinimizedDockInterpolator = AnimationUtils.loadInterpolator(
                 context, android.R.interpolator.fast_out_slow_in);
         loadDimens();
@@ -463,6 +460,11 @@
         }
         mOriginalDockedSide = DOCKED_INVALID;
         setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
+
+        if (mDimmedStack != null) {
+            mDimmedStack.stopDimming();
+            mDimmedStack = null;
+        }
     }
 
     /**
@@ -564,34 +566,12 @@
         final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
         if (visibleAndValid) {
-            stack.getDimBounds(mTmpRect);
-            if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
-                if (!mLastDimLayerRect.equals(mTmpRect) || mLastDimLayerAlpha != alpha) {
-                    try {
-                        // TODO: This should use the regular animation transaction - here and below
-                        mService.openSurfaceTransaction();
-                        mDimLayer.setBounds(mTmpRect);
-                        mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
-                    } finally {
-                        mService.closeSurfaceTransaction("setResizeDimLayer");
-                    }
-                }
-                mLastDimLayerRect.set(mTmpRect);
-                mLastDimLayerAlpha = alpha;
-            } else {
-                visibleAndValid = false;
-            }
+            mDimmedStack = stack;
+            stack.dim(alpha);
         }
-        if (!visibleAndValid) {
-            if (mLastDimLayerAlpha != 0f) {
-                try {
-                    mService.openSurfaceTransaction();
-                    mDimLayer.hide();
-                } finally {
-                    mService.closeSurfaceTransaction("setResizeDimLayer");
-                }
-            }
-            mLastDimLayerAlpha = 0f;
+        if (!visibleAndValid && stack != null) {
+            mDimmedStack = null;
+            stack.stopDimming();
         }
     }
 
@@ -829,12 +809,8 @@
             return animateForMinimizedDockedStack(now);
         } else if (mAnimatingForIme) {
             return animateForIme(now);
-        } else {
-            if (mDimLayer != null && mDimLayer.isDimming()) {
-                mDimLayer.setLayer(getResizeDimLayer());
-            }
-            return false;
         }
+        return false;
     }
 
     private boolean animateForIme(long now) {
@@ -942,27 +918,6 @@
                 + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST);
     }
 
-    @Override
-    public boolean dimFullscreen() {
-        return false;
-    }
-
-    @Override
-    public DisplayInfo getDisplayInfo() {
-        return mDisplayContent.getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return mDisplayContent != null;
-    }
-
-    @Override
-    public void getDimBounds(Rect outBounds) {
-        // This dim layer user doesn't need this.
-    }
-
-    @Override
     public String toShortString() {
         return TAG;
     }
@@ -977,10 +932,6 @@
         pw.println(prefix + "  mMinimizedDock=" + mMinimizedDock);
         pw.println(prefix + "  mAdjustedForIme=" + mAdjustedForIme);
         pw.println(prefix + "  mAdjustedForDivider=" + mAdjustedForDivider);
-        if (mDimLayer.isDimming()) {
-            pw.println(prefix + "  Dim layer is dimming: ");
-            mDimLayer.printTo(prefix + "    ", pw);
-        }
     }
 
     void writeToProto(ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
index 8bec8d7..fddf6ca 100644
--- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -49,19 +49,19 @@
     private int mRotation;
     private boolean mVisible;
 
-    public EmulatorDisplayOverlay(Context context, Display display, SurfaceSession session,
+    public EmulatorDisplayOverlay(Context context, DisplayContent dc,
             int zOrder) {
+        final Display display = dc.getDisplay();
         mScreenSize = new Point();
         display.getSize(mScreenSize);
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("EmulatorDisplayOverlay")
                     .setSize(mScreenSize.x, mScreenSize.y)
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .build();
-            ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(zOrder);
             ctrl.setPosition(0, 0);
             ctrl.show();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index f541926..43dfccc 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -138,7 +138,6 @@
     ParcelFileDescriptor mSurfaceTraceFd;
     RemoteEventTrace mRemoteEventTrace;
 
-    private final WindowLayersController mLayersController;
     final WallpaperController mWallpaperController;
 
     private final Handler mHandler;
@@ -163,7 +162,6 @@
     RootWindowContainer(WindowManagerService service) {
         mService = service;
         mHandler = new MyHandler(service.mH.getLooper());
-        mLayersController = new WindowLayersController(mService);
         mWallpaperController = new WallpaperController(mService);
     }
 
@@ -231,7 +229,7 @@
     }
 
     private DisplayContent createDisplayContent(final Display display) {
-        final DisplayContent dc = new DisplayContent(display, mService, mLayersController,
+        final DisplayContent dc = new DisplayContent(display, mService,
                 mWallpaperController);
         final int displayId = display.getDisplayId();
 
@@ -1103,4 +1101,9 @@
     String getName() {
         return "ROOT";
     }
+
+    @Override
+    void scheduleAnimation() {
+        mService.scheduleAnimationLocked();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 3350fea..70bf15c 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -225,7 +225,7 @@
     }
 
     public ScreenRotationAnimation(Context context, DisplayContent displayContent,
-            SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation,
+            boolean inTransaction, boolean forceDefaultOrientation,
             boolean isSecure, WindowManagerService service) {
         mService = service;
         mContext = context;
@@ -269,7 +269,7 @@
 
         try {
             try {
-                mSurfaceControl = new SurfaceControl.Builder(session)
+                mSurfaceControl = displayContent.makeOverlay()
                         .setName("ScreenshotSurface")
                         .setSize(mWidth, mHeight)
                         .setSecure(isSecure)
@@ -281,7 +281,6 @@
                 // TODO(multidisplay): we should use the proper display
                 SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
                         SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);
-                mSurfaceControl.setLayerStack(display.getLayerStack());
                 mSurfaceControl.setLayer(SCREEN_FREEZE_LAYER_SCREENSHOT);
                 mSurfaceControl.setAlpha(0);
                 mSurfaceControl.show();
@@ -370,11 +369,11 @@
     }
 
     // Must be called while in a transaction.
-    public boolean setRotationInTransaction(int rotation, SurfaceSession session,
+    public boolean setRotationInTransaction(int rotation,
             long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) {
         setRotationInTransaction(rotation);
         if (TWO_PHASE_ANIMATION) {
-            return startAnimation(session, maxAnimationDuration, animationScale,
+            return startAnimation(maxAnimationDuration, animationScale,
                     finalWidth, finalHeight, false, 0, 0);
         }
 
@@ -385,7 +384,7 @@
     /**
      * Returns true if animating.
      */
-    private boolean startAnimation(SurfaceSession session, long maxAnimationDuration,
+    private boolean startAnimation(long maxAnimationDuration,
             float animationScale, int finalWidth, int finalHeight, boolean dismissing,
             int exitAnim, int enterAnim) {
         if (mSurfaceControl == null) {
@@ -561,8 +560,8 @@
                 Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
                         mOriginalWidth*2, mOriginalHeight*2);
                 Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
-                mCustomBlackFrame = new BlackFrame(session, outer, inner,
-                        SCREEN_FREEZE_LAYER_CUSTOM, layerStack, false);
+                mCustomBlackFrame = new BlackFrame(outer, inner,
+                        SCREEN_FREEZE_LAYER_CUSTOM, mDisplayContent, false);
                 mCustomBlackFrame.setMatrix(mFrameInitialMatrix);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
@@ -601,8 +600,8 @@
                             mOriginalWidth*2, mOriginalHeight*2);
                     inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
                 }
-                mExitingBlackFrame = new BlackFrame(session, outer, inner,
-                        SCREEN_FREEZE_LAYER_EXIT, layerStack, mForceDefaultOrientation);
+                mExitingBlackFrame = new BlackFrame(outer, inner,
+                        SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation);
                 mExitingBlackFrame.setMatrix(mFrameInitialMatrix);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
@@ -624,8 +623,8 @@
                 Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
                         finalWidth*2, finalHeight*2);
                 Rect inner = new Rect(0, 0, finalWidth, finalHeight);
-                mEnteringBlackFrame = new BlackFrame(session, outer, inner,
-                        SCREEN_FREEZE_LAYER_ENTER, layerStack, false);
+                mEnteringBlackFrame = new BlackFrame(outer, inner,
+                        SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
@@ -642,7 +641,7 @@
     /**
      * Returns true if animating.
      */
-    public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
+    public boolean dismiss(long maxAnimationDuration,
             float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
         if (DEBUG_STATE) Slog.v(TAG, "Dismiss!");
         if (mSurfaceControl == null) {
@@ -650,7 +649,7 @@
             return false;
         }
         if (!mStarted) {
-            startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight,
+            startAnimation(maxAnimationDuration, animationScale, finalWidth, finalHeight,
                     true, exitAnim, enterAnim);
         }
         if (!mStarted) {
diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java
index eb8ee69..f51a6a9 100644
--- a/services/core/java/com/android/server/wm/StrictModeFlash.java
+++ b/services/core/java/com/android/server/wm/StrictModeFlash.java
@@ -41,15 +41,14 @@
     private boolean mDrawNeeded;
     private final int mThickness = 20;
 
-    public StrictModeFlash(Display display, SurfaceSession session) {
+    public StrictModeFlash(DisplayContent dc) {
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("StrictModeFlash")
                     .setSize(1, 1)
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .build();
-            ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101);  // one more than Watermark? arbitrary.
             ctrl.setPosition(0, 0);
             ctrl.show();
diff --git a/services/core/java/com/android/server/wm/SurfaceBuilderFactory.java b/services/core/java/com/android/server/wm/SurfaceBuilderFactory.java
new file mode 100644
index 0000000..5390e5a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SurfaceBuilderFactory.java
@@ -0,0 +1,25 @@
+/*
+ * 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.SurfaceSession;
+import android.view.SurfaceControl;
+
+interface SurfaceBuilderFactory {
+    SurfaceControl.Builder make(SurfaceSession s);
+};
+
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 13435d7..f70845e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -51,7 +51,7 @@
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
-class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser {
+class Task extends WindowContainer<AppWindowToken> {
     static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
     // Return value from {@link setBounds} indicating no change was made to the Task bounds.
     private static final int BOUNDS_CHANGE_NONE = 0;
@@ -105,6 +105,9 @@
     // stack moves and we in fact do so when moving from full screen to pinned.
     private boolean mPreserveNonFloatingState = false;
 
+    private Dimmer mDimmer = new Dimmer(this);
+    private final Rect mTmpDimBoundsRect = new Rect();
+
     Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
             int resizeMode, boolean supportsPictureInPicture, TaskDescription taskDescription,
             TaskWindowContainerController controller) {
@@ -188,12 +191,6 @@
         EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeTask");
         mDeferRemoval = false;
 
-        // Make sure to remove dim layer user first before removing task its from parent.
-        DisplayContent content = getDisplayContent();
-        if (content != null) {
-            content.mDimLayerController.removeDimLayerUser(this);
-        }
-
         super.removeImmediately();
     }
 
@@ -237,6 +234,8 @@
 
     @Override
     void onParentSet() {
+        super.onParentSet();
+
         // Update task bounds if needed.
         updateDisplayInfo(getDisplayContent());
 
@@ -312,9 +311,7 @@
         mBounds.set(bounds);
 
         mRotation = rotation;
-        if (displayContent != null) {
-            displayContent.mDimLayerController.updateDimLayer(this);
-        }
+
         onOverrideConfigurationChanged(overrideConfig);
         return boundsChange;
     }
@@ -482,7 +479,6 @@
     }
 
     /** Bounds of the task to be used for dimming, as well as touch related tests. */
-    @Override
     public void getDimBounds(Rect out) {
         final DisplayContent displayContent = mStack.getDisplayContent();
         // It doesn't matter if we in particular are part of the resize, since we couldn't have
@@ -634,23 +630,6 @@
         return null;
     }
 
-    @Override
-    public boolean dimFullscreen() {
-        return isFullscreen();
-    }
-
-    @Override
-    public int getLayerForDim(WindowStateAnimator animator, int layerOffset, int defaultLayer) {
-        // If the dim layer is for a starting window, move the dim layer back in the z-order behind
-        // the lowest activity window to ensure it does not occlude the main window if it is
-        // translucent
-        final AppWindowToken appToken = animator.mWin.mAppToken;
-        if (animator.mAttrType == TYPE_APPLICATION_STARTING && hasChild(appToken) ) {
-            return Math.min(defaultLayer, appToken.getLowestAnimLayer() - layerOffset);
-        }
-        return defaultLayer;
-    }
-
     boolean isFullscreen() {
         if (useCurrentBounds()) {
             return mFillsParent;
@@ -661,16 +640,6 @@
         return true;
     }
 
-    @Override
-    public DisplayInfo getDisplayInfo() {
-        return getDisplayContent().getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return getDisplayContent() != null;
-    }
-
     void forceWindowsScaleable(boolean force) {
         mService.openSurfaceTransaction();
         try {
@@ -718,9 +687,18 @@
         mPreserveNonFloatingState = false;
     }
 
+    Dimmer getDimmer() {
+        return mDimmer;
+    }
+
     @Override
-    public String toShortString() {
-        return "Task=" + mTaskId;
+    void prepareSurfaces() {
+        mDimmer.resetDimStates();
+        super.prepareSurfaces();
+        getDimBounds(mTmpDimBoundsRect);
+        if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+            scheduleAnimation();
+        }
     }
 
     @CallSuper
@@ -757,4 +735,8 @@
             wtoken.dump(pw, triplePrefix);
         }
     }
+
+    String toShortString() {
+        return "Task=" + mTaskId;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 12f6b5a..5d4ba09 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -59,7 +59,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-class TaskPositioner implements DimLayer.DimLayerUser {
+class TaskPositioner {
     private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
     private static final String TAG_LOCAL = "TaskPositioner";
     private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
@@ -99,9 +99,6 @@
     private WindowPositionerEventReceiver mInputEventReceiver;
     private Display mDisplay;
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
-    private DimLayer mDimLayer;
-    @CtrlType
-    private int mCurrentDimSide;
     private Rect mTmpRect = new Rect();
     private int mSideMargin;
     private int mMinVisibleWidth;
@@ -207,15 +204,6 @@
                             mService.mActivityManager.resizeTask(
                                     mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
                         }
-
-                        if (mCurrentDimSide != CTRL_NONE) {
-                            final int createMode = mCurrentDimSide == CTRL_LEFT
-                                    ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
-                                    : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-                            mService.mActivityManager.setTaskWindowingModeSplitScreenPrimary(
-                                    mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
-                                    null /* initialBounds */);
-                        }
                     } catch(RemoteException e) {}
 
                     // Post back to WM to handle clean-ups. We still need the input
@@ -243,7 +231,9 @@
     /**
      * @param display The Display that the window being dragged is on.
      */
-    void register(Display display) {
+    void register(DisplayContent displayContent) {
+        final Display display = displayContent.getDisplay();
+
         if (DEBUG_TASK_POSITIONING) {
             Slog.d(TAG, "Registering task positioner");
         }
@@ -305,7 +295,6 @@
         }
         mService.pauseRotationLocked();
 
-        mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL);
         mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
@@ -336,12 +325,6 @@
         mDragWindowHandle = null;
         mDragApplicationHandle = null;
         mDisplay = null;
-
-        if (mDimLayer != null) {
-            mDimLayer.destroySurface();
-            mDimLayer = null;
-        }
-        mCurrentDimSide = CTRL_NONE;
         mDragEnded = true;
 
         // Resume rotations after a drag.
@@ -434,7 +417,6 @@
         }
 
         updateWindowDragBounds(nX, nY, mTmpRect);
-        updateDimLayerVisibility(nX);
         return false;
     }
 
@@ -621,88 +603,6 @@
                 "updateWindowDragBounds: " + mWindowDragBounds);
     }
 
-    private void updateDimLayerVisibility(int x) {
-        @CtrlType
-        int dimSide = getDimSide(x);
-        if (dimSide == mCurrentDimSide) {
-            return;
-        }
-
-        mCurrentDimSide = dimSide;
-
-        if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility");
-        mService.openSurfaceTransaction();
-        if (mCurrentDimSide == CTRL_NONE) {
-            mDimLayer.hide();
-        } else {
-            showDimLayer();
-        }
-        mService.closeSurfaceTransaction("updateDimLayerVisibility");
-    }
-
-    /**
-     * Returns the side of the screen the dim layer should be shown.
-     * @param x horizontal coordinate used to determine if the dim layer should be shown
-     * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the
-     * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer
-     * shouldn't be shown.
-     */
-    private int getDimSide(int x) {
-        if (!mTask.mStack.inFreeformWindowingMode()
-                || !mTask.mStack.fillsParent()
-                || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
-            return CTRL_NONE;
-        }
-
-        mTask.mStack.getDimBounds(mTmpRect);
-        if (x - mSideMargin <= mTmpRect.left) {
-            return CTRL_LEFT;
-        }
-        if (x + mSideMargin >= mTmpRect.right) {
-            return CTRL_RIGHT;
-        }
-
-        return CTRL_NONE;
-    }
-
-    private void showDimLayer() {
-        mTask.mStack.getDimBounds(mTmpRect);
-        if (mCurrentDimSide == CTRL_LEFT) {
-            mTmpRect.right = mTmpRect.centerX();
-        } else if (mCurrentDimSide == CTRL_RIGHT) {
-            mTmpRect.left = mTmpRect.centerX();
-        }
-
-        mDimLayer.setBounds(mTmpRect);
-        mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA,
-                RESIZING_HINT_DURATION_MS);
-    }
-
-    @Override /** {@link DimLayer.DimLayerUser} */
-    public boolean dimFullscreen() {
-        return isFullscreen();
-    }
-
-    boolean isFullscreen() {
-        return false;
-    }
-
-    @Override /** {@link DimLayer.DimLayerUser} */
-    public DisplayInfo getDisplayInfo() {
-        return mTask.mStack.getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return mTask != null && mTask.getDisplayContent() != null;
-    }
-
-    @Override
-    public void getDimBounds(Rect out) {
-        // This dim layer user doesn't need this.
-    }
-
-    @Override
     public String toShortString() {
         return TAG;
     }
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 053fb47..f9062a8 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -55,6 +55,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
 import android.view.Surface;
+import android.view.SurfaceControl;
 
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
@@ -63,7 +64,7 @@
 
 import java.io.PrintWriter;
 
-public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLayerUser,
+public class TaskStack extends WindowContainer<Task> implements
         BoundsAnimationTarget {
     /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
      * restrict IME adjustment so that a min portion of top stack remains visible.*/
@@ -108,8 +109,8 @@
     /** Density as of last time {@link #mBounds} was set. */
     private int mDensity;
 
-    /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */
-    private DimLayer mAnimationBackgroundSurface;
+    private SurfaceControl mAnimationBackgroundSurface;
+    private boolean mAnimationBackgroundSurfaceIsShown = false;
 
     /** The particular window with an Animation with non-zero background color. */
     private WindowStateAnimator mAnimationBackgroundAnimator;
@@ -149,6 +150,13 @@
 
     Rect mPreAnimationBounds = new Rect();
 
+    private Dimmer mDimmer = new Dimmer(this);
+
+    /**
+     * For {@link #prepareSurfaces}.
+     */
+    final Rect mTmpDimBoundsRect = new Rect();
+
     TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
         mService = service;
         mStackId = stackId;
@@ -245,6 +253,35 @@
         }
     }
 
+    private void setAnimationBackgroundBounds(Rect bounds) {
+        if (mAnimationBackgroundSurface == null) {
+            return;
+        }
+        getPendingTransaction().setSize(mAnimationBackgroundSurface, bounds.width(), bounds.height())
+                .setPosition(mAnimationBackgroundSurface, 0, 0);
+        scheduleAnimation();
+    }
+
+    private void hideAnimationSurface() {
+        if (mAnimationBackgroundSurface == null) {
+            return;
+        }
+        getPendingTransaction().hide(mAnimationBackgroundSurface);
+        mAnimationBackgroundSurfaceIsShown = false;
+        scheduleAnimation();
+    }
+
+    private void showAnimationSurface(float alpha) {
+        if (mAnimationBackgroundSurface == null) {
+            return;
+        }
+        getPendingTransaction().setLayer(mAnimationBackgroundSurface, Integer.MIN_VALUE)
+                .setAlpha(mAnimationBackgroundSurface, alpha)
+                .show(mAnimationBackgroundSurface);
+        mAnimationBackgroundSurfaceIsShown = true;
+        scheduleAnimation();
+    }
+
     private boolean setBounds(Rect bounds) {
         boolean oldFullscreen = mFillsParent;
         int rotation = Surface.ROTATION_0;
@@ -267,10 +304,7 @@
             return false;
         }
 
-        if (mDisplayContent != null) {
-            mDisplayContent.mDimLayerController.updateDimLayer(this);
-            mAnimationBackgroundSurface.setBounds(bounds);
-        }
+        setAnimationBackgroundBounds(bounds);
 
         mBounds.set(bounds);
         mRotation = rotation;
@@ -368,7 +402,6 @@
     }
 
     /** Bounds of the stack with other system factors taken into consideration. */
-    @Override
     public void getDimBounds(Rect out) {
         getBounds(out);
     }
@@ -700,9 +733,12 @@
         }
 
         mDisplayContent = dc;
-        mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(),
-                "animation background stackId=" + mStackId);
+
         updateBoundsForWindowModeChange();
+        mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer(true)
+            .setName("animation background stackId=" + mStackId)
+            .build();
+
         super.onDisplayChanged(dc);
     }
 
@@ -914,16 +950,16 @@
 
     @Override
     void onParentSet() {
+        super.onParentSet();
+
         if (getParent() != null || mDisplayContent == null) {
             return;
         }
 
-        // Looks like the stack was removed from the display. Go ahead and clean things up.
-        mDisplayContent.mDimLayerController.removeDimLayerUser(this);
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
 
         if (mAnimationBackgroundSurface != null) {
-            mAnimationBackgroundSurface.destroySurface();
+            mAnimationBackgroundSurface.destroy();
             mAnimationBackgroundSurface = null;
         }
 
@@ -933,9 +969,7 @@
 
     void resetAnimationBackgroundAnimator() {
         mAnimationBackgroundAnimator = null;
-        if (mAnimationBackgroundSurface != null) {
-            mAnimationBackgroundSurface.hide();
-        }
+        hideAnimationSurface();
     }
 
     void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
@@ -944,8 +978,7 @@
                 || animLayer < mAnimationBackgroundAnimator.mAnimLayer) {
             mAnimationBackgroundAnimator = winAnimator;
             animLayer = mDisplayContent.getLayerForAnimationBackground(winAnimator);
-            mAnimationBackgroundSurface.show(animLayer - LAYER_OFFSET_DIM,
-                    ((color >> 24) & 0xff) / 255f, 0);
+            showAnimationSurface(((color >> 24) & 0xff) / 255f);
         }
     }
 
@@ -1250,7 +1283,7 @@
         }
         proto.write(FILLS_PARENT, mFillsParent);
         mBounds.writeToProto(proto, BOUNDS);
-        proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurface.isDimming());
+        proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurfaceIsShown);
         proto.end(token);
     }
 
@@ -1273,9 +1306,8 @@
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
             mChildren.get(taskNdx).dump(prefix + "  ", pw);
         }
-        if (mAnimationBackgroundSurface.isDimming()) {
-            pw.println(prefix + "mWindowAnimationBackgroundSurface:");
-            mAnimationBackgroundSurface.printTo(prefix + "  ", pw);
+        if (mAnimationBackgroundSurfaceIsShown) {
+            pw.println(prefix + "mWindowAnimationBackgroundSurface is shown");
         }
         if (!mExitingAppTokens.isEmpty()) {
             pw.println();
@@ -1299,11 +1331,6 @@
     }
 
     @Override
-    public boolean dimFullscreen() {
-        return !isActivityTypeStandard() || fillsParent();
-    }
-
-    @Override
     boolean fillsParent() {
         if (useCurrentBounds()) {
             return mFillsParent;
@@ -1315,16 +1342,6 @@
     }
 
     @Override
-    public DisplayInfo getDisplayInfo() {
-        return mDisplayContent.getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return mDisplayContent != null;
-    }
-
-    @Override
     public String toString() {
         return "{stackId=" + mStackId + " tasks=" + mChildren + "}";
     }
@@ -1333,7 +1350,6 @@
         return toShortString();
     }
 
-    @Override
     public String toShortString() {
         return "Stack=" + mStackId;
     }
@@ -1691,4 +1707,32 @@
                 || activityType == ACTIVITY_TYPE_RECENTS
                 || activityType == ACTIVITY_TYPE_ASSISTANT;
     }
+
+    Dimmer getDimmer() {
+        return mDimmer;
+    }
+
+    @Override
+    void prepareSurfaces() {
+        mDimmer.resetDimStates();
+        super.prepareSurfaces();
+        getDimBounds(mTmpDimBoundsRect);
+        if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+            scheduleAnimation();
+        }
+    }
+
+    public DisplayInfo getDisplayInfo() {
+        return mDisplayContent.getDisplayInfo();
+    }
+
+    void dim(float alpha) {
+        mDimmer.dimAbove(getPendingTransaction(), alpha);
+        scheduleAnimation();
+    }
+
+    void stopDimming() {
+        mDimmer.stopDim(getPendingTransaction());
+        scheduleAnimation();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java
index d97aaac..9216b66 100644
--- a/services/core/java/com/android/server/wm/Watermark.java
+++ b/services/core/java/com/android/server/wm/Watermark.java
@@ -53,7 +53,7 @@
     private int mLastDH;
     private boolean mDrawNeeded;
 
-    Watermark(Display display, DisplayMetrics dm, SurfaceSession session, String[] tokens) {
+    Watermark(DisplayContent dc, DisplayMetrics dm, String[] tokens) {
         if (false) {
             Log.i(TAG_WM, "*********************** WATERMARK");
             for (int i=0; i<tokens.length; i++) {
@@ -61,7 +61,7 @@
             }
         }
 
-        mDisplay = display;
+        mDisplay = dc.getDisplay();
         mTokens = tokens;
 
         StringBuilder builder = new StringBuilder(32);
@@ -114,7 +114,7 @@
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("WatermarkSurface")
                     .setSize(1, 1)
                     .setFormat(PixelFormat.TRANSLUCENT)
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 1912095..20bade67 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -200,7 +200,7 @@
                     ++mAnimTransactionSequence;
                     dc.updateWindowsForAnimator(this);
                     dc.updateWallpaperForAnimator(this);
-                    dc.prepareWindowSurfaces();
+                    dc.prepareSurfaces();
                 }
 
                 for (int i = 0; i < numDisplays; i++) {
@@ -214,8 +214,6 @@
                     if (screenRotationAnimation != null) {
                         screenRotationAnimation.updateSurfacesInTransaction();
                     }
-
-                    orAnimating(dc.animateDimLayers());
                     orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
                     //TODO (multidisplay): Magnification is supported only for the default display.
                     if (accessibilityController != null && dc.isDefaultDisplay) {
@@ -237,6 +235,13 @@
                 if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
             }
 
+            final int numDisplays = mDisplayContentsAnimators.size();
+            for (int i = 0; i < numDisplays; i++) {
+                final int displayId = mDisplayContentsAnimators.keyAt(i);
+                final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+                dc.onPendingTransactionApplied();
+            }
+
             boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
             boolean doRequest = false;
             if (mBulkUpdateParams != 0) {
@@ -271,6 +276,7 @@
             mService.destroyPreservedSurfaceLocked();
             mService.mWindowPlacerLocked.destroyPendingSurfaces();
 
+
             if (DEBUG_WINDOW_TRACE) {
                 Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
                         + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8f4b897..a5e6288 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -21,9 +21,13 @@
 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 android.view.SurfaceControl.Transaction;
 
 import android.annotation.CallSuper;
 import android.content.res.Configuration;
+import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.util.Pools;
 
 import android.util.proto.ProtoOutputStream;
@@ -64,7 +68,14 @@
             new Pools.SynchronizedPool<>(3);
 
     // The owner/creator for this container. No controller if null.
-    private WindowContainerController mController;
+     WindowContainerController mController;
+
+    protected SurfaceControl mSurfaceControl;
+
+    /**
+     * Applied as part of the animation pass in "prepareSurfaces".
+     */
+    private Transaction mPendingTransaction = new Transaction();
 
     @Override
     final protected WindowContainer getParent() {
@@ -101,7 +112,22 @@
      * Supposed to be overridden and contain actions that should be executed after parent was set.
      */
     void onParentSet() {
-        // Do nothing by default.
+        if (mParent == null) {
+            return;
+        }
+        if (mSurfaceControl == null) {
+            // If we don't yet have a surface, but we now have a parent, we should
+            // build a surface.
+            mSurfaceControl = makeSurface().build();
+            getPendingTransaction().show(mSurfaceControl);
+        } else {
+            // If we have a surface but a new parent, we just need to perform a reparent.
+            getPendingTransaction().reparent(mSurfaceControl, mParent.mSurfaceControl.getHandle());
+        }
+
+        // Either way we need to ask the parent to assign us a Z-order.
+        mParent.assignChildLayers();
+        scheduleAnimation();
     }
 
     // Temp. holders for a chain of containers we are currently processing.
@@ -188,6 +214,11 @@
             mChildren.remove(child);
         }
 
+        if (mSurfaceControl != null) {
+            destroyAfterPendingTransaction(mSurfaceControl);
+            mSurfaceControl = null;
+        }
+
         if (mParent != null) {
             mParent.removeChild(this);
         }
@@ -195,6 +226,7 @@
         if (mController != null) {
             setController(null);
         }
+
     }
 
     /**
@@ -407,7 +439,7 @@
     }
 
     /**
-a     * Returns whether this child is on top of the window hierarchy.
+     * @return Whether this child is on top of the window hierarchy.
      */
     boolean isOnTop() {
         return getParent().getTopChild() == this && getParent().isOnTop();
@@ -673,6 +705,103 @@
         mController = controller;
     }
 
+    SurfaceControl.Builder makeSurface() {
+        final WindowContainer p = getParent();
+        return p.makeChildSurface(this);
+    }
+
+    /**
+     * @param child The WindowContainer this child surface is for, or null if the Surface
+     *              is not assosciated with a WindowContainer (e.g. a surface used for Dimming).
+     */
+    SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+        final WindowContainer p = getParent();
+        // Give the parent a chance to set properties. In hierarchy v1 we rely
+        // on this to set full-screen dimensions on all our Surface-less Layers.
+        final SurfaceControl.Builder b = p.makeChildSurface(child);
+        if (child != null && child.isScreenOverlay()) {
+            // If it's a screen overlay it's been promoted in the hierarchy (wrt to the
+            // WindowContainer hierarchy vs the SurfaceControl hierarchy)
+            // and we shouldn't set ourselves as the parent.
+            return b;
+        } else {
+            return b.setParent(mSurfaceControl);
+        }
+    }
+
+    /**
+     * There are various layers which require promotion from the WindowContainer
+     * hierarchy to the Overlay layer described in {@link DisplayContent}. See {@link WindowState}
+     * for the particular usage.
+     *
+     * TODO: Perhaps this should be eliminated, either through modifying
+     * the window container hierarchy or through modifying the way we express these overlay
+     * Surfaces (for example, the Magnification Overlay could be implemented like the Strict-mode
+     * Flash and not actually use a WindowState).
+     */
+    boolean isScreenOverlay() {
+        return false;
+    }
+
+    /**
+     * @return Whether this WindowContainer should be magnified by the accessibility magnifier.
+     */
+    boolean shouldMagnify() {
+        for (int i = 0; i < mChildren.size(); i++) {
+            if (!mChildren.get(i).shouldMagnify()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    SurfaceSession getSession() {
+        if (getParent() != null) {
+            return getParent().getSession();
+        }
+        return null;
+    }
+
+    void assignLayer(Transaction t, int layer) {
+        if (mSurfaceControl != null) {
+            t.setLayer(mSurfaceControl, layer);
+        }
+    }
+
+    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++;
+            }
+            boosting = true;
+        }
+    }
+
+    void assignChildLayers() {
+        assignChildLayers(getPendingTransaction());
+    }
+
+    boolean needsZBoost() {
+        for (int i = 0; i < mChildren.size(); i++) {
+            if (mChildren.get(i).needsZBoost()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Write to a protocol buffer output stream. Protocol buffer message definition is at
      * {@link com.android.server.wm.proto.WindowContainerProto}.
@@ -719,4 +848,59 @@
             mConsumerWrapperPool.release(this);
         }
     }
+
+    // TODO(b/68336570): Should this really be on WindowContainer since it
+    // can only be used on the top-level nodes that aren't animated?
+    // (otherwise we would be fighting other callers of setMatrix).
+    void applyMagnificationSpec(Transaction t, MagnificationSpec spec) {
+        if (shouldMagnify()) {
+            t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
+                    .setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
+        } else {
+            for (int i = 0; i < mChildren.size(); i++) {
+                mChildren.get(i).applyMagnificationSpec(t, spec);
+            }
+        }
+    }
+
+    /**
+     * TODO: Once we totally eliminate global transaction we will pass transaction in here
+     * rather than merging to global.
+     */
+    void prepareSurfaces() {
+        SurfaceControl.mergeToGlobalTransaction(getPendingTransaction());
+        for (int i = 0; i < mChildren.size(); i++) {
+            mChildren.get(i).prepareSurfaces();
+        }
+    }
+
+    /**
+     * Trigger a call to prepareSurfaces from the animation thread, such that
+     * mPendingTransaction will be applied.
+     */
+    void scheduleAnimation() {
+        if (mParent != null) {
+            mParent.scheduleAnimation();
+        }
+    }
+
+    SurfaceControl getSurfaceControl() {
+        return mSurfaceControl;
+    }
+
+    /**
+     * Destroy a given surface after executing mPendingTransaction. This is
+     * largely a workaround for destroy not being part of transactions
+     * rather than an intentional design, so please take care when
+     * expanding use.
+     */
+    void destroyAfterPendingTransaction(SurfaceControl surface) {
+        if (mParent != null) {
+            mParent.destroyAfterPendingTransaction(surface);
+        }
+    }
+    
+    Transaction getPendingTransaction() {
+        return mPendingTransaction;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
deleted file mode 100644
index 7caf2fe..0000000
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.util.Slog;
-
-import java.util.ArrayDeque;
-import java.util.function.Consumer;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
-import static com.android.server.wm.WindowManagerService.WINDOW_LAYER_MULTIPLIER;
-
-/**
- * Controller for assigning layers to windows on the display.
- *
- * This class encapsulates general algorithm for assigning layers and special rules that we need to
- * apply on top. The general algorithm goes through windows from bottom to the top and the higher
- * the window is, the higher layer is assigned. The final layer is equal to base layer +
- * adjustment from the order. This means that the window list is assumed to be ordered roughly by
- * the base layer (there are exceptions, e.g. due to keyguard and wallpaper and they need to be
- * handled with care, because they break the algorithm).
- *
- * On top of the general algorithm we add special rules, that govern such amazing things as:
- * <li>IME (which has higher base layer, but will be positioned above application windows)</li>
- * <li>docked/pinned windows (that need to be lifted above other application windows, including
- * animations)
- * <li>dock divider (which needs to live above applications, but below IME)</li>
- * <li>replaced windows, which need to live above their normal level, because they anticipate
- * an animation</li>.
- */
-class WindowLayersController {
-    private final WindowManagerService mService;
-
-    WindowLayersController(WindowManagerService service) {
-        mService = service;
-    }
-
-    private ArrayDeque<WindowState> mPinnedWindows = new ArrayDeque<>();
-    private ArrayDeque<WindowState> mDockedWindows = new ArrayDeque<>();
-    private ArrayDeque<WindowState> mAssistantWindows = new ArrayDeque<>();
-    private ArrayDeque<WindowState> mInputMethodWindows = new ArrayDeque<>();
-    private WindowState mDockDivider = null;
-    private ArrayDeque<WindowState> mReplacingWindows = new ArrayDeque<>();
-    private int mCurBaseLayer;
-    private int mCurLayer;
-    private boolean mAnyLayerChanged;
-    private int mHighestApplicationLayer;
-    private int mHighestDockedAffectedLayer;
-    private int mHighestLayerInImeTargetBaseLayer;
-    private WindowState mImeTarget;
-    private boolean mAboveImeTarget;
-    private ArrayDeque<WindowState> mAboveImeTargetAppWindows = new ArrayDeque();
-
-    private final Consumer<WindowState> mAssignWindowLayersConsumer = w -> {
-        boolean layerChanged = false;
-
-        int oldLayer = w.mLayer;
-        if (w.mBaseLayer == mCurBaseLayer) {
-            mCurLayer += WINDOW_LAYER_MULTIPLIER;
-        } else {
-            mCurBaseLayer = mCurLayer = w.mBaseLayer;
-        }
-        assignAnimLayer(w, mCurLayer);
-
-        // TODO: Preserved old behavior of code here but not sure comparing oldLayer to
-        // mAnimLayer and mLayer makes sense...though the worst case would be unintentional
-        // layer reassignment.
-        if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
-            layerChanged = true;
-            mAnyLayerChanged = true;
-        }
-
-        if (w.mAppToken != null) {
-            mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
-                    w.mWinAnimator.mAnimLayer);
-        }
-        if (mImeTarget != null && w.mBaseLayer == mImeTarget.mBaseLayer) {
-            mHighestLayerInImeTargetBaseLayer = Math.max(mHighestLayerInImeTargetBaseLayer,
-                    w.mWinAnimator.mAnimLayer);
-        }
-        if (w.getAppToken() != null && w.inSplitScreenSecondaryWindowingMode()) {
-            mHighestDockedAffectedLayer = Math.max(mHighestDockedAffectedLayer,
-                    w.mWinAnimator.mAnimLayer);
-        }
-
-        collectSpecialWindows(w);
-
-        if (layerChanged) {
-            w.scheduleAnimationIfDimming();
-        }
-    };
-
-    final void assignWindowLayers(DisplayContent dc) {
-        if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based",
-                new RuntimeException("here").fillInStackTrace());
-
-        reset();
-        dc.forAllWindows(mAssignWindowLayersConsumer, false /* traverseTopToBottom */);
-
-        adjustSpecialWindows();
-
-        //TODO (multidisplay): Magnification is supported only for the default display.
-        if (mService.mAccessibilityController != null && mAnyLayerChanged
-                && dc.getDisplayId() == DEFAULT_DISPLAY) {
-            mService.mAccessibilityController.onWindowLayersChangedLocked();
-        }
-
-        if (DEBUG_LAYERS) logDebugLayers(dc);
-    }
-
-    private void logDebugLayers(DisplayContent dc) {
-        dc.forAllWindows((w) -> {
-            final WindowStateAnimator winAnimator = w.mWinAnimator;
-            Slog.v(TAG_WM, "Assign layer " + w + ": " + "mBase=" + w.mBaseLayer
-                    + " mLayer=" + w.mLayer + (w.mAppToken == null
-                    ? "" : " mAppLayer=" + w.mAppToken.getAnimLayerAdjustment())
-                    + " =mAnimLayer=" + winAnimator.mAnimLayer);
-        }, false /* traverseTopToBottom */);
-    }
-
-    private void reset() {
-        mPinnedWindows.clear();
-        mInputMethodWindows.clear();
-        mDockedWindows.clear();
-        mAssistantWindows.clear();
-        mReplacingWindows.clear();
-        mDockDivider = null;
-
-        mCurBaseLayer = 0;
-        mCurLayer = 0;
-        mAnyLayerChanged = false;
-
-        mHighestApplicationLayer = 0;
-        mHighestDockedAffectedLayer = 0;
-        mHighestLayerInImeTargetBaseLayer = (mImeTarget != null) ? mImeTarget.mBaseLayer : 0;
-        mImeTarget = mService.mInputMethodTarget;
-        mAboveImeTarget = false;
-        mAboveImeTargetAppWindows.clear();
-    }
-
-    private void collectSpecialWindows(WindowState w) {
-        if (w.mAttrs.type == TYPE_DOCK_DIVIDER) {
-            mDockDivider = w;
-            return;
-        }
-        if (w.mWillReplaceWindow) {
-            mReplacingWindows.add(w);
-        }
-        if (w.mIsImWindow) {
-            mInputMethodWindows.add(w);
-            return;
-        }
-        if (mImeTarget != null) {
-            if (w.getParentWindow() == mImeTarget && w.mSubLayer > 0) {
-                // Child windows of the ime target with a positive sub-layer should be placed above
-                // the IME.
-                mAboveImeTargetAppWindows.add(w);
-            } else if (mAboveImeTarget && w.mAppToken != null) {
-                // windows of apps above the IME target should be placed above the IME.
-                mAboveImeTargetAppWindows.add(w);
-            }
-            if (w == mImeTarget) {
-                mAboveImeTarget = true;
-            }
-        }
-
-        final int windowingMode = w.getWindowingMode();
-        if (windowingMode == WINDOWING_MODE_PINNED) {
-            mPinnedWindows.add(w);
-        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            mDockedWindows.add(w);
-        }
-        if (w.isActivityTypeAssistant()) {
-            mAssistantWindows.add(w);
-        }
-    }
-
-    private void adjustSpecialWindows() {
-        // The following adjustments are beyond the highest docked-affected layer
-        int layer = mHighestDockedAffectedLayer +  TYPE_LAYER_OFFSET;
-
-        // Adjust the docked stack windows and dock divider above only the windows that are affected
-        // by the docked stack. When this happens, also boost the assistant window layers, otherwise
-        // the docked stack windows & divider would be promoted above the assistant.
-        if (!mDockedWindows.isEmpty() && mHighestDockedAffectedLayer > 0) {
-            while (!mDockedWindows.isEmpty()) {
-                final WindowState window = mDockedWindows.remove();
-                layer = assignAndIncreaseLayerIfNeeded(window, layer);
-            }
-
-            layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer);
-
-            while (!mAssistantWindows.isEmpty()) {
-                final WindowState window = mAssistantWindows.remove();
-                if (window.mLayer > mHighestDockedAffectedLayer) {
-                    layer = assignAndIncreaseLayerIfNeeded(window, layer);
-                }
-            }
-        }
-
-        // The following adjustments are beyond the highest app layer or boosted layer
-        layer = Math.max(layer, mHighestApplicationLayer + WINDOW_LAYER_MULTIPLIER);
-
-        // We know that we will be animating a relaunching window in the near future, which will
-        // receive a z-order increase. We want the replaced window to immediately receive the same
-        // treatment, e.g. to be above the dock divider.
-        while (!mReplacingWindows.isEmpty()) {
-            layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer);
-        }
-
-        while (!mPinnedWindows.isEmpty()) {
-            layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer);
-        }
-
-        // Make sure IME is the highest window in the base layer of it's target.
-        if (mImeTarget != null) {
-            if (mImeTarget.mAppToken == null) {
-                // For non-app ime targets adjust the layer we start from to match what we found
-                // when assigning layers. Otherwise, just use the highest app layer we have some far.
-                layer = mHighestLayerInImeTargetBaseLayer + WINDOW_LAYER_MULTIPLIER;
-            }
-
-            while (!mInputMethodWindows.isEmpty()) {
-                layer = assignAndIncreaseLayerIfNeeded(mInputMethodWindows.remove(), layer);
-            }
-
-            // Adjust app windows the should be displayed above the IME since they are above the IME
-            // target.
-            while (!mAboveImeTargetAppWindows.isEmpty()) {
-                layer = assignAndIncreaseLayerIfNeeded(mAboveImeTargetAppWindows.remove(), layer);
-            }
-        }
-
-    }
-
-    private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) {
-        if (win != null) {
-            assignAnimLayer(win, layer);
-            // Make sure we leave space in-between normal windows for dims and such.
-            layer += WINDOW_LAYER_MULTIPLIER;
-        }
-        return layer;
-    }
-
-    private void assignAnimLayer(WindowState w, int layer) {
-        w.mLayer = layer;
-        w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment()
-                + w.getSpecialWindowAnimLayerAdjustment();
-        if (w.mAppToken != null) {
-            w.mAppToken.mAppAnimator.updateThumbnailLayer();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ce34306..4656539 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -529,7 +529,6 @@
 
     AccessibilityController mAccessibilityController;
 
-    final SurfaceSession mFxSession;
     Watermark mWatermark;
     StrictModeFlash mStrictModeFlash;
     CircularDisplayMask mCircularDisplayMask;
@@ -804,6 +803,13 @@
     static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
             new WindowManagerThreadPriorityBooster();
 
+    class DefaultSurfaceBuilderFactory implements SurfaceBuilderFactory {
+        public SurfaceControl.Builder make(SurfaceSession s) {
+            return new SurfaceControl.Builder(s);
+        }
+    };
+    SurfaceBuilderFactory mSurfaceBuilderFactory = new DefaultSurfaceBuilderFactory();
+
     static void boostPriorityForLockedSection() {
         sThreadPriorityBooster.boost();
     }
@@ -992,7 +998,6 @@
             mPointerEventDispatcher = null;
         }
 
-        mFxSession = new SurfaceSession();
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
         mDisplays = mDisplayManager.getDisplays();
         for (Display display : mDisplays) {
@@ -2478,11 +2483,8 @@
                 mWaitingForConfig = true;
                 displayContent.setLayoutNeeded();
                 int anim[] = new int[2];
-                if (displayContent.isDimming()) {
-                    anim[0] = anim[1] = 0;
-                } else {
-                    mPolicy.selectRotationAnimationLw(anim);
-                }
+                mPolicy.selectRotationAnimationLw(anim);
+
                 startFreezingDisplayLocked(false, anim[0], anim[1], displayContent);
                 config = new Configuration(mTempConfiguration);
             }
@@ -3592,8 +3594,7 @@
                                 com.android.internal.R.dimen.circular_display_mask_thickness);
 
                         mCircularDisplayMask = new CircularDisplayMask(
-                                getDefaultDisplayContentLocked().getDisplay(),
-                                mFxSession,
+                                getDefaultDisplayContentLocked(),
                                 mPolicy.getWindowLayerFromTypeLw(
                                         WindowManager.LayoutParams.TYPE_POINTER)
                                         * TYPE_LAYER_MULTIPLIER + 10, screenOffset, maskThickness);
@@ -3621,8 +3622,7 @@
                 if (mEmulatorDisplayOverlay == null) {
                     mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(
                             mContext,
-                            getDefaultDisplayContentLocked().getDisplay(),
-                            mFxSession,
+                            getDefaultDisplayContentLocked(),
                             mPolicy.getWindowLayerFromTypeLw(
                                     WindowManager.LayoutParams.TYPE_POINTER)
                                     * TYPE_LAYER_MULTIPLIER + 10);
@@ -3670,7 +3670,7 @@
                 // TODO(multi-display): support multiple displays
                 if (mStrictModeFlash == null) {
                     mStrictModeFlash = new StrictModeFlash(
-                            getDefaultDisplayContentLocked().getDisplay(), mFxSession);
+                            getDefaultDisplayContentLocked());
                 }
                 mStrictModeFlash.setVisibility(on);
             } finally {
@@ -4522,7 +4522,7 @@
 
         Display display = displayContent.getDisplay();
         mTaskPositioner = new TaskPositioner(this);
-        mTaskPositioner.register(display);
+        mTaskPositioner.register(displayContent);
         mInputMonitor.updateInputWindowsLw(true /*force*/);
 
         // We need to grab the touch focus so that the touch events during the
@@ -5892,7 +5892,7 @@
 
             displayContent.updateDisplayInfo();
             screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
-                    mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure,
+                    inTransaction, mPolicy.isDefaultOrientationForced(), isSecure,
                     this);
             mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId,
                     screenRotationAnimation);
@@ -5952,11 +5952,10 @@
             // TODO(multidisplay): rotation on main screen only.
             DisplayInfo displayInfo = displayContent.getDisplayInfo();
             // Get rotation animation again, with new top window
-            boolean isDimming = displayContent.isDimming();
-            if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, isDimming)) {
+            if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, false)) {
                 mExitAnimId = mEnterAnimId = 0;
             }
-            if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
+            if (screenRotationAnimation.dismiss(MAX_ANIMATION_DURATION,
                     getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
                         displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
                 scheduleAnimationLocked();
@@ -6040,8 +6039,8 @@
                 if (toks != null && toks.length > 0) {
                     // TODO(multi-display): Show watermarks on secondary displays.
                     final DisplayContent displayContent = getDefaultDisplayContentLocked();
-                    mWatermark = new Watermark(displayContent.getDisplay(),
-                            displayContent.mRealDisplayMetrics, mFxSession, toks);
+                    mWatermark = new Watermark(displayContent, displayContent.mRealDisplayMetrics,
+                            toks);
                 }
             }
         } catch (FileNotFoundException e) {
@@ -7557,4 +7556,13 @@
             w.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
         }, false /* traverseTopToBottom */);
     }
+
+    public void applyMagnificationSpec(MagnificationSpec spec) {
+        getDefaultDisplayContentLocked().applyMagnificationSpec(spec);
+    }
+
+    SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
+        return mSurfaceBuilderFactory.make(s);
+    }
 }
+
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6b1932d..52b7a25 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -23,6 +23,7 @@
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+import static android.view.SurfaceControl.Transaction;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
@@ -41,6 +42,7 @@
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
@@ -54,6 +56,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
@@ -147,6 +152,8 @@
 import android.view.InputChannel;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.WindowInfo;
@@ -602,6 +609,14 @@
                 };
             };
 
+    /**
+     * Indicates whether we have requested a Dim (in the sense of {@link Dimmer}) from our host
+     * container.
+     */
+    private boolean mIsDimming = false;
+
+    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
+
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
@@ -2034,23 +2049,6 @@
         return isVisibleOrAdding();
     }
 
-    void scheduleAnimationIfDimming() {
-        final DisplayContent dc = getDisplayContent();
-        if (dc == null) {
-            return;
-        }
-
-        // If layout is currently deferred, we want to hold of with updating the layers.
-        if (mService.mWindowPlacerLocked.isLayoutDeferred()) {
-            return;
-        }
-        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-        if (dimLayerUser != null && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator)) {
-            // Force an animation pass just to update the mDimLayer layer.
-            mService.scheduleAnimationLocked();
-        }
-    }
-
     private final class DeadWindowEventReceiver extends InputEventReceiver {
         DeadWindowEventReceiver(InputChannel inputChannel) {
             super(inputChannel, mService.mH.getLooper());
@@ -2106,31 +2104,12 @@
         mInputWindowHandle.inputChannel = null;
     }
 
-    void applyDimLayerIfNeeded() {
-        // When the app is terminated (eg. from Recents), the task might have already been
-        // removed with the window pending removal. Don't apply dim in such cases, as there
-        // will be no more updateDimLayer() calls, which leaves the dimlayer invalid.
-        final AppWindowToken token = mAppToken;
-        if (token != null && token.removed) {
-            return;
-        }
-
-        final DisplayContent dc = getDisplayContent();
-        if (!mAnimatingExit && mAppDied) {
-            // If app died visible, apply a dim over the window to indicate that it's inactive
-            dc.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
-        } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
-                && dc != null && !mAnimatingExit && isVisible()) {
-            dc.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
-        }
-    }
-
-    private DimLayer.DimLayerUser getDimLayerUser() {
+    private Dimmer getDimmer() {
         Task task = getTask();
         if (task != null) {
-            return task;
+            return task.getDimmer();
         }
-        return getStack();
+        return getStack().getDimmer();
     }
 
     /** Returns true if the replacement window was removed. */
@@ -2152,9 +2131,6 @@
 
     private void removeReplacedWindow() {
         if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + this);
-        if (isDimming()) {
-            transferDimToReplacement();
-        }
         mWillReplaceWindow = false;
         mAnimateReplacingWindow = false;
         mReplacingRemoveRequested = false;
@@ -2217,11 +2193,11 @@
             // need to intercept touches outside of that window. The dim layer user
             // associated with the window (task or stack) will give us the good bounds, as
             // they would be used to display the dim layer.
-            final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-            if (dimLayerUser != null) {
-                dimLayerUser.getDimBounds(mTmpRect);
+            final Task task = getTask();
+            if (task != null) {
+                task.getDimBounds(mTmpRect);
             } else {
-                getVisibleBounds(mTmpRect);
+                getStack().getDimBounds(mTmpRect);
             }
             if (inFreeformWindowingMode()) {
                 // For freeform windows we the touch region to include the whole surface for the
@@ -2729,14 +2705,6 @@
         return displayContent.isDefaultDisplay;
     }
 
-    @Override
-    public boolean isDimming() {
-        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-        final DisplayContent dc = getDisplayContent();
-        return dimLayerUser != null && dc != null
-                && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator);
-    }
-
     void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
         mShowToOwnerOnly = showToOwnerOnly;
     }
@@ -3071,7 +3039,7 @@
         if (task == null) {
             return false;
         }
-        if (!inSplitScreenWindowingMode()) {
+        if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()) {
             return false;
         }
         if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
@@ -3591,15 +3559,6 @@
         return winY;
     }
 
-    private void transferDimToReplacement() {
-        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-        final DisplayContent dc = getDisplayContent();
-        if (dimLayerUser != null && dc != null) {
-            dc.mDimLayerController.applyDim(dimLayerUser,
-                    mReplacementWindow.mWinAnimator, (mAttrs.flags & FLAG_DIM_BEHIND) != 0);
-        }
-    }
-
     // During activity relaunch due to resize, we sometimes use window replacement
     // for only child windows (as the main window is handled by window preservation)
     // and the big surface.
@@ -4388,4 +4347,80 @@
             return false;
         }
     }
+
+    @Override
+    boolean shouldMagnify() {
+        if (mAttrs.type == TYPE_INPUT_METHOD ||
+                mAttrs.type == TYPE_INPUT_METHOD_DIALOG) {
+            return false;
+        } else if (isScreenOverlay()) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    boolean isScreenOverlay() {
+        // It's tempting to wonder: Have we forgotten the rounded corners overlay?
+        // worry not: it's a fake TYPE_NAVIGATION_BAR.
+        if (mAttrs.type == TYPE_MAGNIFICATION_OVERLAY ||
+                mAttrs.type == TYPE_NAVIGATION_BAR ||
+                mAttrs.type == TYPE_STATUS_BAR) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    SurfaceSession getSession() {
+        if (mSession.mSurfaceSession != null) {
+            return mSession.mSurfaceSession;
+        } else {
+            return getParent().getSession();
+        }
+    }
+
+    @Override
+    boolean needsZBoost() {
+        return getAnimLayerAdjustment() > 0 || mWillReplaceWindow;
+    }
+
+    @Override
+    SurfaceControl.Builder makeSurface() {
+        return mToken.makeChildSurface(this);
+    }
+
+
+    @Override
+    void prepareSurfaces() {
+        mIsDimming = false;
+        if (!mAnimatingExit && mAppDied) {
+            mIsDimming = true;
+            getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
+        } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
+                && !mAnimatingExit && isVisible()) {
+            mIsDimming = true;
+            getDimmer().dimBelow(getPendingTransaction(), this, mAttrs.dimAmount);
+        }
+
+        mWinAnimator.prepareSurfaceLocked(true);
+        super.prepareSurfaces();
+    }
+
+    @Override
+    void assignLayer(Transaction t, int layer) {
+        // See comment in assignRelativeLayerForImeTargetChild
+        if (!isChildWindow()
+                || (mService.mInputMethodTarget != getParentWindow())
+                || !inSplitScreenWindowingMode()) {
+            super.assignLayer(t, layer);
+            return;
+        }
+        getDisplayContent().assignRelativeLayerForImeTargetChild(t, this);
+    }
+
+    @Override
+    public boolean isDimming() {
+        return mIsDimming;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 86397ae..840cc40 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -60,7 +60,6 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
-import android.view.MagnificationSpec;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -559,7 +558,10 @@
         }
         if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, "SET FREEZE LAYER", false);
         if (mSurfaceController != null) {
-            mSurfaceController.setLayer(mAnimLayer + 1);
+            // Our SurfaceControl is always at layer 0 within the parent Surface managed by
+            // window-state. We want this old Surface to stay on top of the new one
+            // until we do the swap, so we place it at layer 1.
+            mSurfaceController.mSurfaceControl.setLayer(1);
         }
         mDestroyPreservedSurfaceUponRedraw = true;
         mSurfaceDestroyDeferred = true;
@@ -730,7 +732,6 @@
         try {
             mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, false);
             mSurfaceController.setLayerStackInTransaction(getLayerStack());
-            mSurfaceController.setLayer(mAnimLayer);
         } finally {
             mService.closeSurfaceTransaction("createSurfaceLocked");
         }
@@ -867,22 +868,6 @@
         mPendingDestroySurface = null;
     }
 
-    void applyMagnificationSpec(MagnificationSpec spec, Matrix transform) {
-        final int surfaceInsetLeft = mWin.mAttrs.surfaceInsets.left;
-        final int surfaceInsetTop = mWin.mAttrs.surfaceInsets.top;
-
-        if (spec != null && !spec.isNop()) {
-            float scale = spec.scale;
-            transform.postScale(scale, scale);
-            transform.postTranslate(spec.offsetX, spec.offsetY);
-
-            // As we are scaling the whole surface, to keep the content
-            // in the same position we will also have to scale the surfaceInsets.
-            transform.postTranslate(-(surfaceInsetLeft*scale - surfaceInsetLeft),
-                    -(surfaceInsetTop*scale - surfaceInsetTop));
-        }
-    }
-
     void computeShownFrameLocked() {
         final boolean selfTransformation = mHasLocalTransformation;
         Transformation attachedTransformation =
@@ -969,11 +954,6 @@
                 tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
             }
 
-            MagnificationSpec spec = getMagnificationSpec();
-            if (spec != null) {
-                applyMagnificationSpec(spec, tmpMatrix);
-            }
-
             // "convert" it into SurfaceFlinger's format
             // (a 2x2 matrix + an offset)
             // Here we must not transform the position of the surface
@@ -1057,49 +1037,16 @@
                 TAG, "computeShownFrameLocked: " + this +
                 " not attached, mAlpha=" + mAlpha);
 
-        MagnificationSpec spec = getMagnificationSpec();
-        if (spec != null) {
-            final Rect frame = mWin.mFrame;
-            final float tmpFloats[] = mService.mTmpFloats;
-            final Matrix tmpMatrix = mWin.mTmpMatrix;
-
-            tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale);
-            tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
-
-            applyMagnificationSpec(spec, tmpMatrix);
-
-            tmpMatrix.getValues(tmpFloats);
-
-            mHaveMatrix = true;
-            mDsDx = tmpFloats[Matrix.MSCALE_X];
-            mDtDx = tmpFloats[Matrix.MSKEW_Y];
-            mDtDy = tmpFloats[Matrix.MSKEW_X];
-            mDsDy = tmpFloats[Matrix.MSCALE_Y];
-            float x = tmpFloats[Matrix.MTRANS_X];
-            float y = tmpFloats[Matrix.MTRANS_Y];
-            mWin.mShownPosition.set(Math.round(x), Math.round(y));
-
-            mShownAlpha = mAlpha;
-        } else {
-            mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
-            if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
-                mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
-            }
-            mShownAlpha = mAlpha;
-            mHaveMatrix = false;
-            mDsDx = mWin.mGlobalScale;
-            mDtDx = 0;
-            mDtDy = 0;
-            mDsDy = mWin.mGlobalScale;
+        mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
+        if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
+            mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
         }
-    }
-
-    private MagnificationSpec getMagnificationSpec() {
-        //TODO (multidisplay): Magnification is supported only for the default display.
-        if (mService.mAccessibilityController != null && mWin.getDisplayId() == DEFAULT_DISPLAY) {
-            return mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin);
-        }
-        return null;
+        mShownAlpha = mAlpha;
+        mHaveMatrix = false;
+        mDsDx = mWin.mGlobalScale;
+        mDtDx = 0;
+        mDtDy = 0;
+        mDsDy = mWin.mGlobalScale;
     }
 
     /**
@@ -1140,26 +1087,6 @@
             w.expandForSurfaceInsets(finalClipRect);
         }
 
-        // We may be applying a magnification spec to all windows,
-        // simulating a transformation in screen space, in which case
-        // we need to transform all other screen space values...including
-        // the final crop. This is kind of messed up and we should look
-        // in to actually transforming screen-space via a parent-layer.
-        // b/38322835
-        MagnificationSpec spec = getMagnificationSpec();
-        if (spec != null && !spec.isNop()) {
-            Matrix transform = mWin.mTmpMatrix;
-            RectF finalCrop = mService.mTmpRectF;
-            transform.reset();
-            transform.postScale(spec.scale, spec.scale);
-            transform.postTranslate(-spec.offsetX, -spec.offsetY);
-            transform.mapRect(finalCrop);
-            finalClipRect.top = (int) finalCrop.top;
-            finalClipRect.left = (int) finalCrop.left;
-            finalClipRect.right = (int) finalCrop.right;
-            finalClipRect.bottom = (int) finalCrop.bottom;
-        }
-
         return true;
     }
 
@@ -1517,7 +1444,6 @@
             mReportSurfaceResized = true;
             mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                     WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
-            w.applyDimLayerIfNeeded();
         }
     }
 
@@ -1615,7 +1541,6 @@
                         mDtDy * w.mHScale * mExtraHScale,
                         mDsDy * w.mVScale * mExtraVScale,
                         recoveringMemory);
-            mSurfaceController.setLayer(mAnimLayer);
 
             if (prepared && mDrawState == HAS_DRAWN) {
                 if (mLastHidden) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index a214523..6746754 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -53,7 +53,7 @@
 
     final WindowStateAnimator mAnimator;
 
-    private SurfaceControlWithBackground mSurfaceControl;
+    SurfaceControlWithBackground mSurfaceControl;
 
     // Should only be set from within setShown().
     private boolean mSurfaceShown = false;
@@ -101,7 +101,8 @@
         mWindowSession = win.mSession;
 
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
-        final SurfaceControl.Builder b = new SurfaceControl.Builder(s)
+        final SurfaceControl.Builder b = win.makeSurface()
+                .setParent(win.getSurfaceControl())
                 .setName(name)
                 .setSize(w, h)
                 .setFormat(format)
@@ -245,25 +246,6 @@
         }
     }
 
-    void setLayer(int layer) {
-        if (mSurfaceControl != null) {
-            mService.openSurfaceTransaction();
-            try {
-                if (mAnimator.mWin.usesRelativeZOrdering()) {
-                    mSurfaceControl.setRelativeLayer(
-                            mAnimator.mWin.getParentWindow()
-                            .mWinAnimator.mSurfaceController.mSurfaceControl,
-                            -1);
-                } else {
-                    mSurfaceLayer = layer;
-                    mSurfaceControl.setLayer(layer);
-                }
-            } finally {
-                mService.closeSurfaceTransaction("setLayer");
-            }
-        }
-    }
-
     void setLayerStackInTransaction(int layerStack) {
         if (mSurfaceControl != null) {
             mSurfaceControl.setLayerStack(layerStack);
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index cd5e475..5081868 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -704,7 +704,7 @@
 
             // Create a new surface for the thumbnail
             WindowState window = appToken.findMainWindow();
-            final SurfaceControl surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+            final SurfaceControl surfaceControl = appToken.makeSurface()
                     .setName("thumbnail anim")
                     .setSize(dirty.width(), dirty.height())
                     .setFormat(PixelFormat.TRANSLUCENT)
@@ -712,7 +712,6 @@
                             window != null ? window.mOwnerUid : Binder.getCallingUid())
                     .build();
 
-            surfaceControl.setLayerStack(display.getLayerStack());
             if (SHOW_TRANSACTIONS) {
                 Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
             }
@@ -750,10 +749,13 @@
             anim.restrictDuration(MAX_ANIMATION_DURATION);
             anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
 
-            openingAppAnimator.updateThumbnailLayer();
             openingAppAnimator.thumbnail = surfaceControl;
             openingAppAnimator.thumbnailAnimation = anim;
             mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
+
+            // We parent the thumbnail to the app token, and just place it
+            // on top of anything else in the app token.
+            surfaceControl.setLayer(Integer.MAX_VALUE);
         } catch (Surface.OutOfResourcesException e) {
             Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
                     + dirty.width() + " h=" + dirty.height(), e);
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index f1e76ab..9a5ebed 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -57,6 +57,7 @@
     <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
 
     <!-- Uses API introduced in O (26) -->
     <uses-sdk android:minSdkVersion="1"
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 05c4853..2224de5 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -31,6 +31,7 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Base64;
+import android.webkit.UserPackage;
 import android.webkit.WebViewFactory;
 import android.webkit.WebViewProviderInfo;
 import android.webkit.WebViewProviderResponse;
@@ -1628,10 +1629,10 @@
         newSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
         PackageInfo currentSdkPackage = createPackageInfo("currentTargetSdkPackage",
             true /* enabled */, true /* valid */, true /* installed */);
-        currentSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1+1;
+        currentSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK;
         PackageInfo oldSdkPackage = createPackageInfo("oldTargetSdkPackage",
             true /* enabled */, true /* valid */, true /* installed */);
-        oldSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+        oldSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK - 1;
 
         WebViewProviderInfo newSdkProviderInfo =
                 new WebViewProviderInfo(newSdkPackage.packageName, "", true, false, null);
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
index 6060881..b55c79b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -129,6 +129,9 @@
             controller.removeStartingWindow();
             waitUntilHandlersIdle();
             assertNoStartingWindow(controller.getAppWindowToken(mDisplayContent));
+
+            controller.getAppWindowToken(mDisplayContent).getParent().getParent().removeImmediately();
+            mDisplayContent.onPendingTransactionApplied();
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index bb88264..d9ab5c8 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -51,71 +51,77 @@
 @RunWith(AndroidJUnit4.class)
 public class AppWindowTokenTests extends WindowTestsBase {
 
+    TaskStack mStack;
+    Task mTask;
+    WindowTestUtils.TestAppWindowToken mToken;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mStack = createTaskStackOnDisplay(mDisplayContent);
+        mTask = createTaskInStack(mStack, 0 /* userId */);
+        mToken = new WindowTestUtils.TestAppWindowToken(mDisplayContent);
+
+        mTask.addChild(mToken, 0);
+    }
+
     @Test
     @Presubmit
     public void testAddWindow_Order() throws Exception {
-        final WindowTestUtils.TestAppWindowToken token =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
+        assertEquals(0, mToken.getWindowsCount());
 
-        assertEquals(0, token.getWindowsCount());
-
-        final WindowState win1 = createWindow(null, TYPE_APPLICATION, token, "win1");
-        final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, token,
+        final WindowState win1 = createWindow(null, TYPE_APPLICATION, mToken, "win1");
+        final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, mToken,
                 "startingWin");
-        final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, token, "baseWin");
-        final WindowState win4 = createWindow(null, TYPE_APPLICATION, token, "win4");
+        final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, mToken, "baseWin");
+        final WindowState win4 = createWindow(null, TYPE_APPLICATION, mToken, "win4");
 
         // Should not contain the windows that were added above.
-        assertEquals(4, token.getWindowsCount());
-        assertTrue(token.hasWindow(win1));
-        assertTrue(token.hasWindow(startingWin));
-        assertTrue(token.hasWindow(baseWin));
-        assertTrue(token.hasWindow(win4));
+        assertEquals(4, mToken.getWindowsCount());
+        assertTrue(mToken.hasWindow(win1));
+        assertTrue(mToken.hasWindow(startingWin));
+        assertTrue(mToken.hasWindow(baseWin));
+        assertTrue(mToken.hasWindow(win4));
 
         // The starting window should be on-top of all other windows.
-        assertEquals(startingWin, token.getLastChild());
+        assertEquals(startingWin, mToken.getLastChild());
 
         // The base application window should be below all other windows.
-        assertEquals(baseWin, token.getFirstChild());
-        token.removeImmediately();
+        assertEquals(baseWin, mToken.getFirstChild());
+        mToken.removeImmediately();
     }
 
     @Test
     @Presubmit
     public void testFindMainWindow() throws Exception {
-        final WindowTestUtils.TestAppWindowToken token =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
+        assertNull(mToken.findMainWindow());
 
-        assertNull(token.findMainWindow());
-
-        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, token, "window1");
-        final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, token, "window11");
-        final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, token, "window12");
-        assertEquals(window1, token.findMainWindow());
+        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mToken, "window1");
+        final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, mToken, "window11");
+        final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, mToken, "window12");
+        assertEquals(window1, mToken.findMainWindow());
         window1.mAnimatingExit = true;
-        assertEquals(window1, token.findMainWindow());
-        final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, token, "window2");
-        assertEquals(window2, token.findMainWindow());
-        token.removeImmediately();
+        assertEquals(window1, mToken.findMainWindow());
+        final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, mToken, "window2");
+        assertEquals(window2, mToken.findMainWindow());
+        mToken.removeImmediately();
     }
 
     @Test
     @Presubmit
     public void testGetTopFullscreenWindow() throws Exception {
-        final WindowTestUtils.TestAppWindowToken token =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
+        assertNull(mToken.getTopFullscreenWindow());
 
-        assertNull(token.getTopFullscreenWindow());
-
-        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, token, "window1");
-        final WindowState window11 = createWindow(null, TYPE_APPLICATION, token, "window11");
-        final WindowState window12 = createWindow(null, TYPE_APPLICATION, token, "window12");
-        assertEquals(window12, token.getTopFullscreenWindow());
+        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mToken, "window1");
+        final WindowState window11 = createWindow(null, TYPE_APPLICATION, mToken, "window11");
+        final WindowState window12 = createWindow(null, TYPE_APPLICATION, mToken, "window12");
+        assertEquals(window12, mToken.getTopFullscreenWindow());
         window12.mAttrs.width = 500;
-        assertEquals(window11, token.getTopFullscreenWindow());
+        assertEquals(window11, mToken.getTopFullscreenWindow());
         window11.mAttrs.width = 500;
-        assertEquals(window1, token.getTopFullscreenWindow());
-        token.removeImmediately();
+        assertEquals(window1, mToken.getTopFullscreenWindow());
+        mToken.removeImmediately();
     }
 
     @Test
@@ -124,27 +130,21 @@
         sWm.mDisplayReady = true;
         sWm.mDisplayEnabled = true;
 
-        // Create an app window with token on a display.
-        final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
-        final Task task = createTaskInStack(stack, 0 /* userId */);
-        final WindowTestUtils.TestAppWindowToken appWindowToken =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
-        task.addChild(appWindowToken, 0);
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("AppWindow");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, appWindowToken);
-        appWindowToken.addWindow(appWindow);
+        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken);
+        mToken.addWindow(appWindow);
 
         // Set initial orientation and update.
-        appWindowToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
         sWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null,
                 mDisplayContent.getDisplayId());
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation());
         appWindow.resizeReported = false;
 
         // Update the orientation to perform 180 degree rotation and check that resize was reported.
-        appWindowToken.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+        mToken.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
         sWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null,
                 mDisplayContent.getDisplayId());
         sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
@@ -159,18 +159,11 @@
         sWm.mDisplayReady = true;
         sWm.mDisplayEnabled = true;
 
-        // Create an app window with token on a display.
-        final DisplayContent defaultDisplayContent = sWm.getDefaultDisplayContentLocked();
-        final TaskStack stack = createTaskStackOnDisplay(defaultDisplayContent);
-        final Task task = createTaskInStack(stack, 0 /* userId */);
-        final WindowTestUtils.TestAppWindowToken appWindowToken =
-                new WindowTestUtils.TestAppWindowToken(defaultDisplayContent);
-        task.addChild(appWindowToken, 0);
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("AppWindow");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, appWindowToken);
-        appWindowToken.addWindow(appWindow);
+        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken);
+        mToken.addWindow(appWindow);
 
         // Set initial orientation and update.
         performRotation(Surface.ROTATION_90);
@@ -193,53 +186,49 @@
     @Test
     @Presubmit
     public void testGetOrientation() throws Exception {
-        final WindowTestUtils.TestAppWindowToken token =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
-        token.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
-        token.setFillsParent(false);
+        mToken.setFillsParent(false);
         // Can specify orientation if app doesn't fill parent.
-        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation());
 
-        token.setFillsParent(true);
-        token.hidden = true;
-        token.sendingToBottom = true;
+        mToken.setFillsParent(true);
+        mToken.hidden = true;
+        mToken.sendingToBottom = true;
         // Can not specify orientation if app isn't visible even though it fills parent.
-        assertEquals(SCREEN_ORIENTATION_UNSET, token.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation());
         // Can specify orientation if the current orientation candidate is orientation behind.
-        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation(SCREEN_ORIENTATION_BEHIND));
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation(SCREEN_ORIENTATION_BEHIND));
 
-        token.sendingToBottom = false;
-        token.setIsOnTop(true);
-        // Allow for token to provide orientation hidden if on top and not being sent to bottom.
-        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation());
+        mToken.sendingToBottom = false;
+        mToken.setIsOnTop(true);
+        // Allow for mToken to provide orientation hidden if on top and not being sent to bottom.
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation());
     }
 
     @Test
     @Presubmit
     public void testKeyguardFlagsDuringRelaunch() throws Exception {
-        final WindowTestUtils.TestAppWindowToken token =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.flags |= FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD;
         attrs.setTitle("AppWindow");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, token);
+        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken);
 
         // Add window with show when locked flag
-        token.addWindow(appWindow);
-        assertTrue(token.containsShowWhenLockedWindow() && token.containsDismissKeyguardWindow());
+        mToken.addWindow(appWindow);
+        assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow());
 
         // Start relaunching
-        token.startRelaunching();
-        assertTrue(token.containsShowWhenLockedWindow() && token.containsDismissKeyguardWindow());
+        mToken.startRelaunching();
+        assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow());
 
         // Remove window and make sure that we still report back flag
-        token.removeChild(appWindow);
-        assertTrue(token.containsShowWhenLockedWindow() && token.containsDismissKeyguardWindow());
+        mToken.removeChild(appWindow);
+        assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow());
 
         // Finish relaunching and ensure flag is now not reported
-        token.finishRelaunching();
-        assertFalse(token.containsShowWhenLockedWindow() || token.containsDismissKeyguardWindow());
+        mToken.finishRelaunching();
+        assertFalse(mToken.containsShowWhenLockedWindow() || mToken.containsDismissKeyguardWindow());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/DimLayerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimLayerControllerTests.java
deleted file mode 100644
index c3a471a..0000000
--- a/services/tests/servicestests/src/com/android/server/wm/DimLayerControllerTests.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.hardware.display.DisplayManagerGlobal;
-import android.platform.test.annotations.Presubmit;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for the {@link DimLayerController} class.
- *
- * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.wm.DimLayerControllerTests
- */
-@SmallTest
-@Presubmit
-@org.junit.runner.RunWith(AndroidJUnit4.class)
-public class DimLayerControllerTests extends WindowTestsBase {
-
-    /**
-     * This tests if shared fullscreen dim layer is added when stack is added to display
-     * and is removed when the only stack on the display is removed.
-     */
-    @Test
-    public void testSharedFullScreenDimLayer() throws Exception {
-        // Create a display.
-        final DisplayContent dc = createNewDisplay();
-        assertFalse(dc.mDimLayerController.hasSharedFullScreenDimLayer());
-
-        // Add stack with activity.
-        final TaskStack stack = createTaskStackOnDisplay(dc);
-        assertTrue(dc.mDimLayerController.hasDimLayerUser(stack));
-        assertTrue(dc.mDimLayerController.hasSharedFullScreenDimLayer());
-
-        // Remove the only stack on the display and check if the shared dim layer clears.
-        stack.removeImmediately();
-        assertFalse(dc.mDimLayerController.hasDimLayerUser(stack));
-        assertFalse(dc.mDimLayerController.hasSharedFullScreenDimLayer());
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
new file mode 100644
index 0000000..f069d49
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
@@ -0,0 +1,207 @@
+/*
+ * 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 java.util.HashMap;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+
+/**
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.DimmerTests;
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DimmerTests extends WindowTestsBase {
+    private class TestWindowContainer extends WindowContainer<TestWindowContainer> {
+        final SurfaceControl mControl = mock(SurfaceControl.class);
+        final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class);
+
+        @Override
+        SurfaceControl getSurfaceControl() {
+            return mControl;
+        }
+
+        @Override
+        SurfaceControl.Transaction getPendingTransaction() {
+            return mTransaction;
+        }
+    }
+
+    private class MockSurfaceBuildingContainer extends WindowContainer<TestWindowContainer> {
+        final SurfaceSession mSession = new SurfaceSession();
+        SurfaceControl mBuiltSurface = null;
+        final SurfaceControl mHostControl = mock(SurfaceControl.class);
+        final SurfaceControl.Transaction mHostTransaction = mock(SurfaceControl.Transaction.class);
+
+        class MockSurfaceBuilder extends SurfaceControl.Builder {
+            MockSurfaceBuilder(SurfaceSession ss) {
+                super(ss);
+            }
+
+            @Override
+            public SurfaceControl build() {
+                SurfaceControl sc = mock(SurfaceControl.class);
+                mBuiltSurface = sc;
+                return sc;
+            }
+        }
+
+        @Override
+        SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+            return new MockSurfaceBuilder(mSession);
+        }
+
+        @Override
+        SurfaceControl getSurfaceControl() {
+            return mHostControl;
+        }
+
+        @Override
+        SurfaceControl.Transaction getPendingTransaction() {
+            return mHostTransaction;
+        }
+    }
+
+    MockSurfaceBuildingContainer mHost;
+    Dimmer mDimmer;
+    SurfaceControl.Transaction mTransaction;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mHost = new MockSurfaceBuildingContainer();
+        mTransaction = mock(SurfaceControl.Transaction.class);
+        mDimmer = new Dimmer(mHost);
+    }
+
+    @Test
+    public void testDimAboveNoChildCreatesSurface() throws Exception {
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, alpha);
+        assertNotNull("Dimmer should have created a surface", mHost.mBuiltSurface);
+
+        verify(mTransaction).setAlpha(mHost.mBuiltSurface, alpha);
+        verify(mTransaction).show(mHost.mBuiltSurface);
+        verify(mTransaction).setLayer(mHost.mBuiltSurface, Integer.MAX_VALUE);
+    }
+
+    @Test
+    public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() throws Exception {
+        float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, alpha);
+        final SurfaceControl firstSurface = mHost.mBuiltSurface;
+
+        alpha = 0.9f;
+        mDimmer.dimAbove(mTransaction, alpha);
+
+        assertEquals(firstSurface, mHost.mBuiltSurface);
+        verify(mTransaction).setAlpha(firstSurface, 0.9f);
+    }
+
+    @Test
+    public void testUpdateDimsAppliesSize() throws Exception {
+        mDimmer.dimAbove(mTransaction, 0.8f);
+
+        int width = 100;
+        int height = 300;
+        Rect bounds = new Rect(0, 0, width, height);
+        mDimmer.updateDims(mTransaction, bounds);
+        verify(mTransaction).setSize(mHost.mBuiltSurface, width, height);
+    }
+
+    @Test
+    public void testDimAboveNoChildNotReset() throws Exception {
+        mDimmer.dimAbove(mTransaction, 0.8f);
+        mDimmer.resetDimStates();
+
+        mDimmer.updateDims(mTransaction, new Rect());
+        verify(mHost.mBuiltSurface, never()).destroy();
+    }
+
+    @Test
+    public void testDimAboveWithChildCreatesSurfaceAboveChild() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
+        assertNotNull("Dimmer should have created a surface", mHost.mBuiltSurface);
+
+        verify(mTransaction).setAlpha(mHost.mBuiltSurface, alpha);
+        verify(mTransaction).show(mHost.mBuiltSurface);
+        verify(mTransaction).setRelativeLayer(mHost.mBuiltSurface, child.mControl, 1);
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimBelow(mTransaction, child, alpha);
+        assertNotNull("Dimmer should have created a surface", mHost.mBuiltSurface);
+
+        verify(mTransaction).setAlpha(mHost.mBuiltSurface, alpha);
+        verify(mTransaction).show(mHost.mBuiltSurface);
+        verify(mTransaction).setRelativeLayer(mHost.mBuiltSurface, child.mControl, -1);
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceDestroyedWhenReset() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
+        mDimmer.resetDimStates();
+        mDimmer.updateDims(mTransaction, new Rect());
+        verify(mHost.mBuiltSurface).destroy();
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
+        mDimmer.resetDimStates();
+        mDimmer.dimAbove(mTransaction, child, alpha);
+
+        mDimmer.updateDims(mTransaction, new Rect());
+        verify(mHost.mBuiltSurface, never()).destroy();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
index 887def7..873a01b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
@@ -66,7 +66,7 @@
         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
 
         mPositioner = new TaskPositioner(sWm);
-        mPositioner.register(display);
+        mPositioner.register(mDisplayContent);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
index b846fd0..0ef78f4 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
@@ -105,14 +105,10 @@
         final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
         assertEquals(stack, task.mStack);
-        assertTrue(mDisplayContent.mDimLayerController.hasDimLayerUser(stack));
-        assertTrue(mDisplayContent.mDimLayerController.hasDimLayerUser(task));
 
         // Remove stack and check if its child is also removed.
         stack.removeImmediately();
         assertNull(stack.getDisplayContent());
         assertNull(task.mStack);
-        assertFalse(mDisplayContent.mDimLayerController.hasDimLayerUser(stack));
-        assertFalse(mDisplayContent.mDimLayerController.hasDimLayerUser(task));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 53d0bfb..a45695f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -568,11 +568,6 @@
     }
 
     @Override
-    public boolean canMagnifyWindow(int windowType) {
-        return false;
-    }
-
-    @Override
     public boolean isTopLevelWindow(int windowType) {
         return false;
     }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java
deleted file mode 100644
index 3c3514f..0000000
--- a/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-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;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
-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;
-
-/**
- * Tests for the {@link WindowLayersController} class.
- *
- * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.wm.WindowLayersControllerTests
- */
-@SmallTest
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class WindowLayersControllerTests extends WindowTestsBase {
-
-    @Test
-    public void testAssignWindowLayers_ForImeWithNoTarget() throws Exception {
-        sWm.mInputMethodTarget = null;
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        // The Ime has an higher base layer than app windows and lower base layer than system
-        // windows, so it should be above app windows and below system windows if there isn't an IME
-        // target.
-        assertWindowLayerGreaterThan(mImeWindow, mChildAppWindowAbove);
-        assertWindowLayerGreaterThan(mImeWindow, mAppWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mDockedDividerWindow);
-        assertWindowLayerGreaterThan(mNavBarWindow, mImeWindow);
-        assertWindowLayerGreaterThan(mStatusBarWindow, mImeWindow);
-
-        // And, IME dialogs should always have an higher layer than the IME.
-        assertWindowLayerGreaterThan(mImeDialogWindow, mImeWindow);
-    }
-
-    @Test
-    public void testAssignWindowLayers_ForImeWithAppTarget() throws Exception {
-        final WindowState imeAppTarget =
-                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
-        sWm.mInputMethodTarget = imeAppTarget;
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        // Ime should be above all app windows and below system windows if it is targeting an app
-        // window.
-        assertWindowLayerGreaterThan(mImeWindow, imeAppTarget);
-        assertWindowLayerGreaterThan(mImeWindow, mChildAppWindowAbove);
-        assertWindowLayerGreaterThan(mImeWindow, mAppWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mDockedDividerWindow);
-        assertWindowLayerGreaterThan(mNavBarWindow, mImeWindow);
-        assertWindowLayerGreaterThan(mStatusBarWindow, mImeWindow);
-
-        // And, IME dialogs should always have an higher layer than the IME.
-        assertWindowLayerGreaterThan(mImeDialogWindow, mImeWindow);
-    }
-
-    @Test
-    public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() throws Exception {
-        final WindowState imeAppTarget =
-                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
-        final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget,
-                TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken,
-                "imeAppTargetChildAboveWindow");
-        final WindowState imeAppTargetChildBelowWindow = createWindow(imeAppTarget,
-                TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken,
-                "imeAppTargetChildBelowWindow");
-
-        sWm.mInputMethodTarget = imeAppTarget;
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        // Ime should be above all app windows except for child windows that are z-ordered above it
-        // and below system windows if it is targeting an app window.
-        assertWindowLayerGreaterThan(mImeWindow, imeAppTarget);
-        assertWindowLayerGreaterThan(imeAppTargetChildAboveWindow, mImeWindow);
-        assertWindowLayerGreaterThan(mImeWindow, imeAppTargetChildBelowWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mChildAppWindowAbove);
-        assertWindowLayerGreaterThan(mImeWindow, mAppWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mDockedDividerWindow);
-        assertWindowLayerGreaterThan(mNavBarWindow, mImeWindow);
-        assertWindowLayerGreaterThan(mStatusBarWindow, mImeWindow);
-
-        // And, IME dialogs should always have an higher layer than the IME.
-        assertWindowLayerGreaterThan(mImeDialogWindow, mImeWindow);
-    }
-
-    @Test
-    public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() throws Exception {
-        final WindowState appBelowImeTarget =
-                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appBelowImeTarget");
-        final WindowState imeAppTarget =
-                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
-        final WindowState appAboveImeTarget =
-                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appAboveImeTarget");
-
-        sWm.mInputMethodTarget = imeAppTarget;
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        // Ime should be above all app windows except for non-fullscreen app window above it and
-        // below system windows if it is targeting an app window.
-        assertWindowLayerGreaterThan(mImeWindow, imeAppTarget);
-        assertWindowLayerGreaterThan(mImeWindow, appBelowImeTarget);
-        assertWindowLayerGreaterThan(appAboveImeTarget, mImeWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mChildAppWindowAbove);
-        assertWindowLayerGreaterThan(mImeWindow, mAppWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mDockedDividerWindow);
-        assertWindowLayerGreaterThan(mNavBarWindow, mImeWindow);
-        assertWindowLayerGreaterThan(mStatusBarWindow, mImeWindow);
-
-        // And, IME dialogs should always have an higher layer than the IME.
-        assertWindowLayerGreaterThan(mImeDialogWindow, mImeWindow);
-    }
-
-    @Test
-    public void testAssignWindowLayers_ForImeNonAppImeTarget() throws Exception {
-        final WindowState imeSystemOverlayTarget = createWindow(null, TYPE_SYSTEM_OVERLAY,
-                mDisplayContent, "imeSystemOverlayTarget",
-                true /* ownerCanAddInternalSystemWindow */);
-
-        sWm.mInputMethodTarget = imeSystemOverlayTarget;
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        // The IME target base layer is higher than all window except for the nav bar window, so the
-        // IME should be above all windows except for the nav bar.
-        assertWindowLayerGreaterThan(mImeWindow, imeSystemOverlayTarget);
-        assertWindowLayerGreaterThan(mImeWindow, mChildAppWindowAbove);
-        assertWindowLayerGreaterThan(mImeWindow, mAppWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mDockedDividerWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mStatusBarWindow);
-        assertWindowLayerGreaterThan(mNavBarWindow, mImeWindow);
-
-        // And, IME dialogs should always have an higher layer than the IME.
-        assertWindowLayerGreaterThan(mImeDialogWindow, mImeWindow);
-    }
-
-    @Test
-    public void testStackLayers() throws Exception {
-        WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED,
-                ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, "pinnedStackWindow");
-        WindowState dockedStackWindow = createWindowOnStack(null,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
-                mDisplayContent, "dockedStackWindow");
-        WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN,
-                ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
-                mDisplayContent, "assistantStackWindow");
-
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        assertWindowLayerGreaterThan(dockedStackWindow, mAppWindow);
-        assertWindowLayerGreaterThan(assistantStackWindow, dockedStackWindow);
-        assertWindowLayerGreaterThan(pinnedStackWindow, assistantStackWindow);
-    }
-
-    private void assertWindowLayerGreaterThan(WindowState first, WindowState second)
-            throws Exception {
-        assertGreaterThan(first.mWinAnimator.mAnimLayer, second.mWinAnimator.mAnimLayer);
-    }
-
-}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 0980f7e..4c5e291 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -73,7 +73,6 @@
     private static boolean sOneTimeSetupDone = false;
     DisplayContent mDisplayContent;
     DisplayInfo mDisplayInfo = new DisplayInfo();
-    WindowLayersController mLayersController;
     WindowState mWallpaperWindow;
     WindowState mImeWindow;
     WindowState mImeDialogWindow;
@@ -98,8 +97,9 @@
 
         final Context context = InstrumentationRegistry.getTargetContext();
         AttributeCache.init(context);
+
         sWm = TestWindowManagerPolicy.getWindowManagerService(context);
-        mLayersController = new WindowLayersController(sWm);
+        beforeCreateDisplay();
 
         context.getDisplay().getDisplayInfo(mDisplayInfo);
         mDisplayContent = createNewDisplay();
@@ -126,6 +126,10 @@
         waitUntilHandlersIdle();
     }
 
+    void beforeCreateDisplay() {
+        // Called before display is created.
+    }
+
     @After
     public void tearDown() throws Exception {
         final LinkedList<WindowState> nonCommonWindows = new LinkedList();
@@ -149,6 +153,14 @@
         waitUntilHandlersIdle();
     }
 
+    /**
+     * @return A SurfaceBuilderFactory to inject in to the WindowManagerService during
+     *         set-up (or null).
+     */
+    SurfaceBuilderFactory getSurfaceBuilderFactory() {
+        return null;
+    }
+
     private WindowState createCommonWindow(WindowState parent, int type, String name) {
         final WindowState win = createWindow(parent, type, name);
         mCommonWindows.add(win);
@@ -162,6 +174,11 @@
         Assert.assertTrue("Excepted " + first + " to be greater than " + second, first > second);
     }
 
+    /** Asserts that the first entry is greater than the second entry. */
+    void assertLessThan(int first, int second) throws Exception {
+        Assert.assertTrue("Excepted " + first + " to be less than " + second, first < second);
+    }
+
     /**
      * Waits until the main handler for WM has processed all messages.
      */
@@ -264,7 +281,7 @@
         final int displayId = sNextDisplayId++;
         final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
                 mDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
-        return new DisplayContent(display, sWm, mLayersController, new WallpaperController(sWm));
+        return new DisplayContent(display, sWm, new WallpaperController(sWm));
     }
 
     /** Creates a {@link com.android.server.wm.WindowTestUtils.TestWindowState} */
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
index 692e08b..7219104 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
@@ -94,43 +94,6 @@
         assertEquals(null, dc.getWindowToken(token.token));
     }
 
-    @Test
-    public void testAdjustAnimLayer() throws Exception {
-        final WindowTestUtils.TestWindowToken token =
-                new WindowTestUtils.TestWindowToken(0, mDisplayContent);
-        final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1");
-        final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, token, "window11");
-        final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, token, "window12");
-        final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2");
-        final WindowState window3 = createWindow(null, TYPE_APPLICATION, token, "window3");
-
-        window2.mLayer = 100;
-        window3.mLayer = 200;
-
-        // We assign layers once, to get the base values computed by
-        // the controller.
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        final int window1StartLayer = window1.mWinAnimator.mAnimLayer;
-        final int window11StartLayer = window11.mWinAnimator.mAnimLayer;
-        final int window12StartLayer = window12.mWinAnimator.mAnimLayer;
-        final int window2StartLayer = window2.mWinAnimator.mAnimLayer;
-        final int window3StartLayer = window3.mWinAnimator.mAnimLayer;
-
-        // Then we set an adjustment, and assign them again, they should
-        // be offset.
-        int adj = token.adj = 50;
-        mLayersController.assignWindowLayers(mDisplayContent);
-        final int highestLayer = token.getHighestAnimLayer();
-
-        assertEquals(window1StartLayer + adj, window1.mWinAnimator.mAnimLayer);
-        assertEquals(window11StartLayer + adj, window11.mWinAnimator.mAnimLayer);
-        assertEquals(window12StartLayer + adj, window12.mWinAnimator.mAnimLayer);
-        assertEquals(window2StartLayer + adj, window2.mWinAnimator.mAnimLayer);
-        assertEquals(window3StartLayer + adj, window3.mWinAnimator.mAnimLayer);
-        assertEquals(window3StartLayer + adj, highestLayer);
-    }
-
     /**
      * Test that a window token isn't orphaned by the system when it is requested to be removed.
      * Tokens should only be removed from the system when all their windows are gone.
diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
new file mode 100644
index 0000000..f7c4b1f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+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;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+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;
+
+/**
+ * Tests for the {@link WindowLayersController} class.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.ZOrderingTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ZOrderingTests extends WindowTestsBase {
+
+    private class LayerRecordingTransaction extends SurfaceControl.Transaction {
+        HashMap<SurfaceControl, Integer> mLayersForControl = new HashMap();
+        HashMap<SurfaceControl, SurfaceControl> mRelativeLayersForControl = new HashMap();
+
+        @Override
+        public SurfaceControl.Transaction setLayer(SurfaceControl sc, int layer) {
+            mRelativeLayersForControl.remove(sc);
+            mLayersForControl.put(sc, layer);
+            return super.setLayer(sc, layer);
+        }
+
+        @Override
+        public SurfaceControl.Transaction setRelativeLayer(SurfaceControl sc,
+                SurfaceControl relativeTo,
+                int layer) {
+            mRelativeLayersForControl.put(sc, relativeTo);
+            mLayersForControl.put(sc, layer);
+            return super.setRelativeLayer(sc, relativeTo, layer);
+        }
+
+        int getLayer(SurfaceControl sc) {
+            return mLayersForControl.get(sc);
+        }
+
+        SurfaceControl getRelativeLayer(SurfaceControl sc) {
+            return mRelativeLayersForControl.get(sc);
+        }
+    };
+
+    // We have WM use our Hierarchy recording subclass of SurfaceControl.Builder
+    // such that we can keep track of the parents of Surfaces as they are constructed.
+    private HashMap<SurfaceControl, SurfaceControl> mParentFor = new HashMap();
+
+    private class HierarchyRecorder extends SurfaceControl.Builder {
+        SurfaceControl mPendingParent;
+
+        HierarchyRecorder(SurfaceSession s) {
+            super(s);
+        }
+
+        public SurfaceControl.Builder setParent(SurfaceControl sc) {
+            mPendingParent = sc;
+            return super.setParent(sc);
+        }
+        public SurfaceControl build() {
+            SurfaceControl sc = super.build();
+            mParentFor.put(sc, mPendingParent);
+            mPendingParent = null;
+            return sc;
+        }
+    };
+
+    class HierarchyRecordingBuilderFactory implements SurfaceBuilderFactory {
+        public SurfaceControl.Builder make(SurfaceSession s) {
+            return new HierarchyRecorder(s);
+        }
+    };
+
+    private LayerRecordingTransaction mTransaction;
+
+    @Override
+    void beforeCreateDisplay() {
+        // We can't use @Before here because it may happen after WindowTestsBase @Before
+        // which is after construction of the DisplayContent, meaning the HierarchyRecorder
+        // would miss construction of the top-level layers.
+        mTransaction = new LayerRecordingTransaction();
+        sWm.mSurfaceBuilderFactory = new HierarchyRecordingBuilderFactory();
+    }
+
+    @After
+    public void after() {
+        mTransaction.close();
+        mParentFor.clear();
+    }
+
+    LinkedList<SurfaceControl> getAncestors(LayerRecordingTransaction t, SurfaceControl sc) {
+        LinkedList<SurfaceControl> p = new LinkedList();
+        SurfaceControl current = sc;
+        do {
+            p.addLast(current);
+
+            SurfaceControl rs = t.getRelativeLayer(current);
+            if (rs != null) {
+                current = rs;
+            } else {
+                current = mParentFor.get(current);
+            }
+        } while (current != null);
+        return p;
+    }
+
+    void assertZOrderGreaterThan(LayerRecordingTransaction t,
+            SurfaceControl left, SurfaceControl right) throws Exception {
+        final LinkedList<SurfaceControl> leftParentChain = getAncestors(t, left);
+        final LinkedList<SurfaceControl> rightParentChain = getAncestors(t, right);
+
+        SurfaceControl commonAncestor = null;
+        SurfaceControl leftTop = leftParentChain.peekLast();
+        SurfaceControl rightTop = rightParentChain.peekLast();
+        while (leftTop != null && rightTop != null && leftTop == rightTop) {
+            commonAncestor = leftParentChain.removeLast();
+            rightParentChain.removeLast();
+            leftTop = leftParentChain.peekLast();
+            rightTop = rightParentChain.peekLast();
+        }
+
+        if (rightTop == null) { // right is the parent of left.
+            assertGreaterThan(t.getLayer(leftTop), 0);
+        } else if (leftTop == null) { // left is the parent of right.
+            assertGreaterThan(0, t.getLayer(rightTop));
+        } else {
+            assertGreaterThan(t.getLayer(leftTop),
+                    t.getLayer(rightTop));
+        }
+    }
+
+    void assertWindowLayerGreaterThan(LayerRecordingTransaction t,
+            WindowState left, WindowState right) throws Exception {
+        assertZOrderGreaterThan(t, left.getSurfaceControl(), right.getSurfaceControl());
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithNoTarget() throws Exception {
+        sWm.mInputMethodTarget = null;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // The Ime has an higher base layer than app windows and lower base layer than system
+        // windows, so it should be above app windows and below system windows if there isn't an IME
+        // target.
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow);
+        assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithAppTarget() throws Exception {
+        final WindowState imeAppTarget =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
+        sWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // Ime should be above all app windows and below system windows if it is targeting an app
+        // window.
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() throws Exception {
+        final WindowState imeAppTarget =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
+        final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget,
+                TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken,
+                "imeAppTargetChildAboveWindow");
+        final WindowState imeAppTargetChildBelowWindow = createWindow(imeAppTarget,
+                TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken,
+                "imeAppTargetChildBelowWindow");
+
+        sWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // Ime should be above all app windows except for child windows that are z-ordered above it
+        // and below system windows if it is targeting an app window.
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget);
+        assertWindowLayerGreaterThan(mTransaction, imeAppTargetChildAboveWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() throws Exception {
+        final WindowState appBelowImeTarget =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appBelowImeTarget");
+        final WindowState imeAppTarget =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
+        final WindowState appAboveImeTarget =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appAboveImeTarget");
+
+        sWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // Ime should be above all app windows except for non-fullscreen app window above it and
+        // below system windows if it is targeting an app window.
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, appBelowImeTarget);
+        assertWindowLayerGreaterThan(mTransaction, appAboveImeTarget, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeNonAppImeTarget() throws Exception {
+        final WindowState imeSystemOverlayTarget = createWindow(null, TYPE_SYSTEM_OVERLAY,
+                mDisplayContent, "imeSystemOverlayTarget",
+                true /* ownerCanAddInternalSystemWindow */);
+
+        sWm.mInputMethodTarget = imeSystemOverlayTarget;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // The IME target base layer is higher than all window except for the nav bar window, so the
+        // IME should be above all windows except for the nav bar.
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeSystemOverlayTarget);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow);
+
+        // The IME has a higher base layer than the status bar so we may expect it to go
+        // above the status bar once they are both in the Non-App layer, as past versions of this
+        // test enforced. However this seems like the wrong behavior unless the status bar is the
+        // IME target.
+        assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForStatusBarImeTarget() throws Exception {
+        sWm.mInputMethodTarget = mStatusBarWindow;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mStatusBarWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testStackLayers() throws Exception {
+        final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED,
+                ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent,
+                "pinnedStackWindow");
+        final WindowState dockedStackWindow = createWindowOnStack(null,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
+                mDisplayContent, "dockedStackWindow");
+        final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
+                mDisplayContent, "assistantStackWindow");
+
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        assertWindowLayerGreaterThan(mTransaction, dockedStackWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, assistantStackWindow, dockedStackWindow);
+        assertWindowLayerGreaterThan(mTransaction, pinnedStackWindow, assistantStackWindow);
+    }
+}
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index c9f3199..fd42033 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -72,6 +72,9 @@
 
         self.ident = self.raw.replace(" deprecated ", " ")
 
+    def __hash__(self):
+        return hash(self.raw)
+
     def __repr__(self):
         return self.raw
 
@@ -110,6 +113,9 @@
             ident = ident[:ident.index(" throws ")]
         self.ident = ident
 
+    def __hash__(self):
+        return hash(self.raw)
+
     def __repr__(self):
         return self.raw
 
@@ -145,6 +151,9 @@
 
         self.name = self.fullname[self.fullname.rindex(".")+1:]
 
+    def __hash__(self):
+        return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
+
     def __repr__(self):
         return self.raw
 
@@ -256,6 +265,14 @@
     _fail(clazz, detail, True, rule, msg)
 
 
+noticed = {}
+
+def notice(clazz):
+    global noticed
+
+    noticed[clazz.fullname] = hash(clazz)
+
+
 def verify_constants(clazz):
     """All static final constants must be FOO_NAME style."""
     if re.match("android\.R\.[a-z]+", clazz.fullname): return
@@ -1203,6 +1220,9 @@
 
 def examine_clazz(clazz):
     """Find all style issues in the given class."""
+
+    notice(clazz)
+
     if clazz.pkg.name.startswith("java"): return
     if clazz.pkg.name.startswith("junit"): return
     if clazz.pkg.name.startswith("org.apache"): return
@@ -1258,10 +1278,11 @@
 
 def examine_stream(stream):
     """Find all style issues in the given API stream."""
-    global failures
+    global failures, noticed
     failures = {}
+    noticed = {}
     _parse_stream(stream, examine_clazz)
-    return failures
+    return (failures, noticed)
 
 
 def examine_api(api):
@@ -1338,6 +1359,8 @@
             help="Disable terminal colors")
     parser.add_argument("--allow-google", action='store_const', const=True,
             help="Allow references to Google")
+    parser.add_argument("--show-noticed", action='store_const', const=True,
+            help="Show API changes noticed")
     args = vars(parser.parse_args())
 
     if args['no_color']:
@@ -1350,16 +1373,21 @@
     previous_file = args['previous.txt']
 
     with current_file as f:
-        cur_fail = examine_stream(f)
+        cur_fail, cur_noticed = examine_stream(f)
     if not previous_file is None:
         with previous_file as f:
-            prev_fail = examine_stream(f)
+            prev_fail, prev_noticed = examine_stream(f)
 
         # ignore errors from previous API level
         for p in prev_fail:
             if p in cur_fail:
                 del cur_fail[p]
 
+        # ignore classes unchanged from previous API level
+        for k, v in prev_noticed.iteritems():
+            if k in cur_noticed and v == cur_noticed[k]:
+                del cur_noticed[k]
+
         """
         # NOTE: disabled because of memory pressure
         # look for compatibility issues
@@ -1371,6 +1399,12 @@
             print
         """
 
+    if args['show_noticed'] and len(cur_noticed) != 0:
+        print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
+        for f in sorted(cur_noticed.keys()):
+            print f
+        print
+
     if len(cur_fail) != 0:
         print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
         for f in sorted(cur_fail):
diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py
index dcb90e4..15d39fd 100755
--- a/tools/fonts/fontchain_linter.py
+++ b/tools/fonts/fontchain_linter.py
@@ -163,11 +163,14 @@
             'U+%04X was not found in %s' % (char, font))
 
 
-def assert_font_supports_none_of_chars(font, chars):
+def assert_font_supports_none_of_chars(font, chars, fallbackName):
     best_cmap = get_best_cmap(font)
     for char in chars:
-        assert char not in best_cmap, (
-            'U+%04X was found in %s' % (char, font))
+        if fallbackName:
+            assert char not in best_cmap, 'U+%04X was found in %s' % (char, font)
+        else:
+            assert char not in best_cmap, (
+                'U+%04X was found in %s in fallback %s' % (char, font, fallbackName))
 
 
 def assert_font_supports_all_sequences(font, sequences):
@@ -196,19 +199,21 @@
 
 
 class FontRecord(object):
-    def __init__(self, name, scripts, variant, weight, style, font):
+    def __init__(self, name, scripts, variant, weight, style, fallback_for, font):
         self.name = name
         self.scripts = scripts
         self.variant = variant
         self.weight = weight
         self.style = style
+        self.fallback_for = fallback_for
         self.font = font
 
 
 def parse_fonts_xml(fonts_xml_path):
-    global _script_to_font_map, _fallback_chain
+    global _script_to_font_map, _fallback_chains, _all_fonts
     _script_to_font_map = collections.defaultdict(set)
-    _fallback_chain = []
+    _fallback_chains = {}
+    _all_fonts = []
     tree = ElementTree.parse(fonts_xml_path)
     families = tree.findall('family')
     # Minikin supports up to 254 but users can place their own font at the first
@@ -225,10 +230,17 @@
                 'No variant expected for LGC font %s.' % name)
             assert langs is None, (
                 'No language expected for LGC fonts %s.' % name)
+            assert name not in _fallback_chains, 'Duplicated name entry %s' % name
+            _fallback_chains[name] = []
         else:
             assert variant in {None, 'elegant', 'compact'}, (
                 'Unexpected value for variant: %s' % variant)
 
+    for family in families:
+        name = family.get('name')
+        variant = family.get('variant')
+        langs = family.get('lang')
+
         if langs:
             langs = langs.split()
             scripts = {lang_to_script(lang) for lang in langs}
@@ -247,17 +259,36 @@
             assert style in {'normal', 'italic'}, (
                 'Unknown style "%s"' % style)
 
+            fallback_for = child.get('fallbackFor')
+
+            assert not name or not fallback_for, (
+                'name and fallbackFor cannot be present at the same time')
+            assert not fallback_for or fallback_for in _fallback_chains, (
+                'Unknown fallback name: %s' % fallback_for)
+
             index = child.get('index')
             if index:
                 index = int(index)
 
-            _fallback_chain.append(FontRecord(
+            record = FontRecord(
                 name,
                 frozenset(scripts),
                 variant,
                 weight,
                 style,
-                (font_file, index)))
+                fallback_for,
+                (font_file, index))
+
+            _all_fonts.append(record)
+
+            if not fallback_for:
+                if not name or name == 'sans-serif':
+                    for _, fallback in _fallback_chains.iteritems():
+                        fallback.append(record)
+                else:
+                    _fallback_chains[name].append(record)
+            else:
+                _fallback_chains[fallback_for].append(record)
 
             if name: # non-empty names are used for default LGC fonts
                 map_scripts = {'Latn', 'Grek', 'Cyrl'}
@@ -274,7 +305,7 @@
 
 def get_emoji_font():
     emoji_fonts = [
-        record.font for record in _fallback_chain
+        record.font for record in _all_fonts
         if 'Zsye' in record.scripts]
     assert len(emoji_fonts) == 1, 'There are %d emoji fonts.' % len(emoji_fonts)
     return emoji_fonts[0]
@@ -318,35 +349,36 @@
 
 def check_emoji_defaults(default_emoji):
     missing_text_chars = _emoji_properties['Emoji'] - default_emoji
-    emoji_font_seen = False
-    for record in _fallback_chain:
-        if 'Zsye' in record.scripts:
-            emoji_font_seen = True
-            # No need to check the emoji font
-            continue
-        # For later fonts, we only check them if they have a script
-        # defined, since the defined script may get them to a higher
-        # score even if they appear after the emoji font. However,
-        # we should skip checking the text symbols font, since
-        # symbol fonts should be able to override the emoji display
-        # style when 'Zsym' is explicitly specified by the user.
-        if emoji_font_seen and (not record.scripts or 'Zsym' in record.scripts):
-            continue
+    for name, fallback_chain in _fallback_chains.iteritems():
+        emoji_font_seen = False
+        for record in fallback_chain:
+            if 'Zsye' in record.scripts:
+                emoji_font_seen = True
+                # No need to check the emoji font
+                continue
+            # For later fonts, we only check them if they have a script
+            # defined, since the defined script may get them to a higher
+            # score even if they appear after the emoji font. However,
+            # we should skip checking the text symbols font, since
+            # symbol fonts should be able to override the emoji display
+            # style when 'Zsym' is explicitly specified by the user.
+            if emoji_font_seen and (not record.scripts or 'Zsym' in record.scripts):
+                continue
 
-        # Check default emoji-style characters
-        assert_font_supports_none_of_chars(record.font, sorted(default_emoji))
+            # Check default emoji-style characters
+            assert_font_supports_none_of_chars(record.font, sorted(default_emoji), name)
 
-        # Mark default text-style characters appearing in fonts above the emoji
-        # font as seen
-        if not emoji_font_seen:
-            missing_text_chars -= set(get_best_cmap(record.font))
+            # Mark default text-style characters appearing in fonts above the emoji
+            # font as seen
+            if not emoji_font_seen:
+                missing_text_chars -= set(get_best_cmap(record.font))
 
-    # Noto does not have monochrome glyphs for Unicode 7.0 wingdings and
-    # webdings yet.
-    missing_text_chars -= _chars_by_age['7.0']
-    assert missing_text_chars == set(), (
-        'Text style version of some emoji characters are missing: ' +
-            repr(missing_text_chars))
+        # Noto does not have monochrome glyphs for Unicode 7.0 wingdings and
+        # webdings yet.
+        missing_text_chars -= _chars_by_age['7.0']
+        assert missing_text_chars == set(), (
+            'Text style version of some emoji characters are missing: ' +
+                repr(missing_text_chars))
 
 
 # Setting reverse to true returns a dictionary that maps the values to sets of
@@ -626,8 +658,19 @@
     return all_emoji, default_emoji, equivalent_emoji
 
 
+def check_compact_only_fallback():
+    for name, fallback_chain in _fallback_chains.iteritems():
+        for record in fallback_chain:
+            if record.variant == 'compact':
+                same_script_elegants = [x for x in fallback_chain
+                    if x.scripts == record.scripts and x.variant == 'elegant']
+                assert same_script_elegants, (
+                    '%s must be in elegant of %s as fallback of "%s" too' % (
+                    record.font, record.scripts, record.fallback_for),)
+
+
 def check_vertical_metrics():
-    for record in _fallback_chain:
+    for record in _all_fonts:
         if record.name in ['sans-serif', 'sans-serif-condensed']:
             font = open_font(record.font)
             assert font['head'].yMax == 2163 and font['head'].yMin == -555, (
@@ -646,11 +689,12 @@
 def check_cjk_punctuation():
     cjk_scripts = {'Hans', 'Hant', 'Jpan', 'Kore'}
     cjk_punctuation = range(0x3000, 0x301F + 1)
-    for record in _fallback_chain:
-        if record.scripts.intersection(cjk_scripts):
-            # CJK font seen. Stop checking the rest of the fonts.
-            break
-        assert_font_supports_none_of_chars(record.font, cjk_punctuation)
+    for name, fallback_chain in _fallback_chains.iteritems():
+        for record in fallback_chain:
+            if record.scripts.intersection(cjk_scripts):
+                # CJK font seen. Stop checking the rest of the fonts.
+                break
+            assert_font_supports_none_of_chars(record.font, cjk_punctuation, name)
 
 
 def main():
@@ -661,6 +705,8 @@
     fonts_xml_path = path.join(target_out, 'etc', 'fonts.xml')
     parse_fonts_xml(fonts_xml_path)
 
+    check_compact_only_fallback()
+
     check_vertical_metrics()
 
     hyphens_dir = path.join(target_out, 'usr', 'hyphen-data')
diff --git a/vr/Android.mk b/vr/Android.mk
index 5b65d3f..73e9f23 100644
--- a/vr/Android.mk
+++ b/vr/Android.mk
@@ -18,6 +18,7 @@
 LOCAL_MODULE := libdvr_loader
 LOCAL_MODULE_OWNER := google
 LOCAL_SRC_FILES := dvr_library_loader.cpp
+LOCAL_CFLAGS := -Wall -Werror
 include $(BUILD_SHARED_LIBRARY)
 
 # Java platform library for vr stuff.