Merge "APK digest API and initial implementation."
diff --git a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
index 050fecd..d3938f4 100644
--- a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
@@ -17,7 +17,6 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.view.Display;
@@ -136,4 +135,22 @@
             }
         }
     }
+
+    @Test
+    public void getDisplayMetrics() {
+        ResourcesManager resourcesManager = ResourcesManager.getInstance();
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            // Invalidate cache.
+            resourcesManager.applyConfigurationToResourcesLocked(
+                    resourcesManager.getConfiguration(), null);
+            state.resumeTiming();
+
+            // Invoke twice for testing cache.
+            resourcesManager.getDisplayMetrics();
+            resourcesManager.getDisplayMetrics();
+        }
+    }
 }
diff --git a/cmds/idmap2/tests/ResultTests.cpp b/cmds/idmap2/tests/ResultTests.cpp
index cbced0a..f2f8854 100644
--- a/cmds/idmap2/tests/ResultTests.cpp
+++ b/cmds/idmap2/tests/ResultTests.cpp
@@ -260,7 +260,8 @@
 
 struct NoCopyContainer {
   uint32_t value;  // NOLINT(misc-non-private-member-variables-in-classes)
-  DISALLOW_COPY_AND_ASSIGN(NoCopyContainer);
+  NoCopyContainer(const NoCopyContainer&) = delete;
+  NoCopyContainer& operator=(const NoCopyContainer&) = delete;
 };
 
 Result<std::unique_ptr<NoCopyContainer>> CreateNoCopyContainer(bool succeed) {
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 9b684f1..dcfbd5e 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -411,7 +411,7 @@
 
 void ValueMetricProducer::resetBase() {
     for (auto& slice : mCurrentBaseInfo) {
-        for (auto& baseInfo : slice.second) {
+        for (auto& baseInfo : slice.second.baseInfos) {
             baseInfo.hasBase = false;
         }
     }
@@ -623,7 +623,7 @@
                 mMatchedMetricDimensionKeys.find(whatKey) != mMatchedMetricDimensionKeys.end();
         if (!presentInPulledData && whatKey.contains(mStateChangePrimaryKey.second)) {
             auto it = mCurrentBaseInfo.find(whatKey);
-            for (auto& baseInfo : it->second) {
+            for (auto& baseInfo : it->second.baseInfos) {
                 baseInfo.hasBase = false;
             }
         }
@@ -652,7 +652,7 @@
             (unsigned long)mCurrentSlicedBucket.size());
     if (verbose) {
         for (const auto& it : mCurrentSlicedBucket) {
-          for (const auto& interval : it.second) {
+          for (const auto& interval : it.second.intervals) {
               fprintf(out, "\t(what)%s\t(states)%s  (value)%s\n",
                       it.first.getDimensionKeyInWhat().toString().c_str(),
                       it.first.getStateValuesKey().toString().c_str(),
@@ -788,23 +788,23 @@
         return;
     }
 
-    vector<BaseInfo>& baseInfos = mCurrentBaseInfo[whatKey];
+    DimensionsInWhatInfo& dimensionsInWhatInfo = mCurrentBaseInfo[whatKey];
+    vector<BaseInfo>& baseInfos = dimensionsInWhatInfo.baseInfos;
     if (baseInfos.size() < mFieldMatchers.size()) {
         VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size());
         baseInfos.resize(mFieldMatchers.size());
     }
 
-    for (BaseInfo& baseInfo : baseInfos) {
-        if (!baseInfo.hasCurrentState) {
-            baseInfo.currentState = getUnknownStateKey();
-            baseInfo.hasCurrentState = true;
-        }
+    if (!dimensionsInWhatInfo.hasCurrentState) {
+        dimensionsInWhatInfo.currentState = getUnknownStateKey();
+        dimensionsInWhatInfo.hasCurrentState = true;
     }
 
     // We need to get the intervals stored with the previous state key so we can
     // close these value intervals.
-    const auto oldStateKey = baseInfos[0].currentState;
-    vector<Interval>& intervals = mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)];
+    const auto oldStateKey = dimensionsInWhatInfo.currentState;
+    vector<Interval>& intervals =
+            mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)].intervals;
     if (intervals.size() < mFieldMatchers.size()) {
         VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size());
         intervals.resize(mFieldMatchers.size());
@@ -818,14 +818,14 @@
     // Discussion here: http://ag/6124370.
     bool useAnomalyDetection = true;
 
+    dimensionsInWhatInfo.hasCurrentState = true;
+    dimensionsInWhatInfo.currentState = stateKey;
     for (int i = 0; i < (int)mFieldMatchers.size(); i++) {
         const Matcher& matcher = mFieldMatchers[i];
         BaseInfo& baseInfo = baseInfos[i];
         Interval& interval = intervals[i];
         interval.valueIndex = i;
         Value value;
-        baseInfo.hasCurrentState = true;
-        baseInfo.currentState = stateKey;
         if (!getDoubleOrLong(event, matcher, value)) {
             VLOG("Failed to get value %d from event %s", i, event.ToString().c_str());
             StatsdStats::getInstance().noteBadValueType(mMetricId);
@@ -990,7 +990,7 @@
         bool bucketHasData = false;
         // The current bucket is large enough to keep.
         for (const auto& slice : mCurrentSlicedBucket) {
-            ValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second);
+            PastValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second.intervals);
             bucket.mConditionTrueNs = conditionTrueDuration;
             // it will auto create new vector of ValuebucketInfo if the key is not found.
             if (bucket.valueIndex.size() > 0) {
@@ -1030,9 +1030,9 @@
     mCurrentBucketNum += numBucketsForward;
 }
 
-ValueBucket ValueMetricProducer::buildPartialBucket(int64_t bucketEndTime,
-                                                    const std::vector<Interval>& intervals) {
-    ValueBucket bucket;
+PastValueBucket ValueMetricProducer::buildPartialBucket(int64_t bucketEndTime,
+                                                        const std::vector<Interval>& intervals) {
+    PastValueBucket bucket;
     bucket.mBucketStartNs = mCurrentBucketStartTimeNs;
     bucket.mBucketEndNs = bucketEndTime;
     for (const auto& interval : intervals) {
@@ -1059,7 +1059,7 @@
     // Cleanup data structure to aggregate values.
     for (auto it = mCurrentSlicedBucket.begin(); it != mCurrentSlicedBucket.end();) {
         bool obsolete = true;
-        for (auto& interval : it->second) {
+        for (auto& interval : it->second.intervals) {
             interval.hasValue = false;
             interval.sampleSize = 0;
             if (interval.seenNewData) {
@@ -1107,7 +1107,7 @@
                     continue;
                 }
                 // TODO: fix this when anomaly can accept double values
-                auto& interval = slice.second[0];
+                auto& interval = slice.second.intervals[0];
                 if (interval.hasValue) {
                     mCurrentFullBucket[slice.first] += interval.value.long_value;
                 }
@@ -1126,7 +1126,7 @@
                 for (auto& tracker : mAnomalyTrackers) {
                     if (tracker != nullptr) {
                         // TODO: fix this when anomaly can accept double values
-                        auto& interval = slice.second[0];
+                        auto& interval = slice.second.intervals[0];
                         if (interval.hasValue) {
                             tracker->addPastBucket(slice.first, interval.value.long_value,
                                                    mCurrentBucketNum);
@@ -1139,7 +1139,7 @@
         // Accumulate partial bucket.
         for (const auto& slice : mCurrentSlicedBucket) {
             // TODO: fix this when anomaly can accept double values
-            auto& interval = slice.second[0];
+            auto& interval = slice.second.intervals[0];
             if (interval.hasValue) {
                 mCurrentFullBucket[slice.first] += interval.value.long_value;
             }
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index e72002e..472cc33 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -31,7 +31,7 @@
 namespace os {
 namespace statsd {
 
-struct ValueBucket {
+struct PastValueBucket {
     int64_t mBucketStartNs;
     int64_t mBucketEndNs;
     std::vector<int> valueIndex;
@@ -41,7 +41,6 @@
     int64_t mConditionTrueNs;
 };
 
-
 // Aggregates values within buckets.
 //
 // There are different events that might complete a bucket
@@ -173,7 +172,7 @@
     // if this is pulled metric
     const bool mIsPulled;
 
-    // internal state of an ongoing aggregation bucket.
+    // Tracks the value information of one value field.
     typedef struct {
         // Index in multi value aggregation.
         int valueIndex;
@@ -188,25 +187,40 @@
         bool seenNewData = false;
     } Interval;
 
+    // Internal state of an ongoing aggregation bucket.
+    typedef struct CurrentValueBucket {
+        // Value information for each value field of the metric.
+        std::vector<Interval> intervals;
+    } CurrentValueBucket;
+
+    // Holds base information for diffing values from one value field.
     typedef struct {
         // Holds current base value of the dimension. Take diff and update if necessary.
         Value base;
         // Whether there is a base to diff to.
         bool hasBase;
+    } BaseInfo;
+
+    // State key and base information for a specific DimensionsInWhat key.
+    typedef struct {
+        std::vector<BaseInfo> baseInfos;
         // Last seen state value(s).
         HashableDimensionKey currentState;
         // Whether this dimensions in what key has a current state key.
         bool hasCurrentState;
-    } BaseInfo;
+    } DimensionsInWhatInfo;
 
-    std::unordered_map<MetricDimensionKey, std::vector<Interval>> mCurrentSlicedBucket;
+    // Tracks the internal state in the ongoing aggregation bucket for each DimensionsInWhat
+    // key and StateValuesKey pair.
+    std::unordered_map<MetricDimensionKey, CurrentValueBucket> mCurrentSlicedBucket;
 
-    std::unordered_map<HashableDimensionKey, std::vector<BaseInfo>> mCurrentBaseInfo;
+    // Tracks current state key and base information for each DimensionsInWhat key.
+    std::unordered_map<HashableDimensionKey, DimensionsInWhatInfo> mCurrentBaseInfo;
 
     std::unordered_map<MetricDimensionKey, int64_t> mCurrentFullBucket;
 
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
-    std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>> mPastBuckets;
+    std::unordered_map<MetricDimensionKey, std::vector<PastValueBucket>> mPastBuckets;
 
     const int64_t mMinBucketSizeNs;
 
@@ -224,8 +238,8 @@
     void accumulateEvents(const std::vector<std::shared_ptr<LogEvent>>& allData,
                           int64_t originalPullTimeNs, int64_t eventElapsedTimeNs);
 
-    ValueBucket buildPartialBucket(int64_t bucketEndTime,
-                                   const std::vector<Interval>& intervals);
+    PastValueBucket buildPartialBucket(int64_t bucketEndTime,
+                                       const std::vector<Interval>& intervals);
 
     void initCurrentSlicedBucket(int64_t nextBucketStartTimeNs);
 
@@ -234,7 +248,7 @@
     // Reset diff base and mHasGlobalBase
     void resetBase();
 
-    static const size_t kBucketSize = sizeof(ValueBucket{});
+    static const size_t kBucketSize = sizeof(PastValueBucket{});
 
     const size_t mDimensionSoftLimit;
 
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 1000aea..8790fe4 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -57,7 +57,7 @@
 double epsilon = 0.001;
 
 static void assertPastBucketValuesSingleKey(
-        const std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>>& mPastBuckets,
+        const std::unordered_map<MetricDimensionKey, std::vector<PastValueBucket>>& mPastBuckets,
         const std::initializer_list<int>& expectedValuesList,
         const std::initializer_list<int64_t>& expectedDurationNsList,
         const std::initializer_list<int64_t>& expectedStartTimeNsList,
@@ -79,7 +79,7 @@
     ASSERT_EQ(1, mPastBuckets.size());
     ASSERT_EQ(expectedValues.size(), mPastBuckets.begin()->second.size());
 
-    const vector<ValueBucket>& buckets = mPastBuckets.begin()->second;
+    const vector<PastValueBucket>& buckets = mPastBuckets.begin()->second;
     for (int i = 0; i < expectedValues.size(); i++) {
         EXPECT_EQ(expectedValues[i], buckets[i].values[0].long_value)
                 << "Values differ at index " << i;
@@ -288,8 +288,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(11, curBaseInfo.base.long_value);
@@ -304,8 +305,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(23, curBaseInfo.base.long_value);
@@ -322,8 +323,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(36, curBaseInfo.base.long_value);
@@ -426,8 +427,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(11, curBaseInfo.base.long_value);
@@ -455,8 +457,8 @@
     allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 3, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     // the base was reset
     EXPECT_EQ(true, curBaseInfo.hasBase);
@@ -489,8 +491,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(11, curBaseInfo.base.long_value);
@@ -502,8 +505,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -516,8 +519,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(36, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -549,8 +552,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(11, curBaseInfo.base.long_value);
@@ -562,8 +566,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -573,8 +577,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(36, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -624,8 +628,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     // startUpdated:false sum:0 start:100
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
@@ -641,8 +646,8 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(110, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -654,8 +659,8 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(20, curInterval.value.long_value);
     EXPECT_EQ(false, curBaseInfo.hasBase);
@@ -879,8 +884,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
@@ -888,7 +894,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(30, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
@@ -925,8 +931,8 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(20, curInterval.value.long_value);
 
     LogEvent event3(/*uid=*/0, /*pid=*/0);
@@ -935,7 +941,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(50, curInterval.value.long_value);
 
     valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35);
@@ -946,7 +952,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(50, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
@@ -1089,8 +1095,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     // startUpdated:true sum:0 start:11
     EXPECT_EQ(true, curBaseInfo.hasBase);
@@ -1104,8 +1111,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     // tartUpdated:false sum:12
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(23, curBaseInfo.base.long_value);
@@ -1121,8 +1128,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket6StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket6StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     // startUpdated:false sum:12
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(36, curBaseInfo.base.long_value);
@@ -1180,8 +1187,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -1189,8 +1197,8 @@
 
     // pull on bucket boundary come late, condition change happens before it
     valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1);
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
     EXPECT_EQ(false, curBaseInfo.hasBase);
@@ -1203,8 +1211,8 @@
 
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 }
@@ -1252,8 +1260,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     // startUpdated:false sum:0 start:100
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
@@ -1265,8 +1274,8 @@
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 
@@ -1274,8 +1283,8 @@
     valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25);
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(130, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -1286,8 +1295,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 50, 140));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 50);
 
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(140, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
@@ -1327,7 +1336,7 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
@@ -1335,7 +1344,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(10, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
@@ -1364,7 +1373,7 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
@@ -1374,7 +1383,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(20, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
@@ -1405,7 +1414,7 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval;
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(1, curInterval.sampleSize);
@@ -1414,7 +1423,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(25, curInterval.value.long_value);
     EXPECT_EQ(2, curInterval.sampleSize);
 
@@ -1449,7 +1458,7 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
@@ -1457,7 +1466,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(25, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
@@ -1487,8 +1496,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -1499,7 +1509,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(5, curInterval.value.long_value);
 
@@ -1509,8 +1519,8 @@
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
 
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(15, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
@@ -1520,8 +1530,8 @@
     CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(15, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
@@ -1558,12 +1568,13 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(20, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -1572,12 +1583,12 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(5, curInterval.value.long_value);
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(2, curInterval.value.long_value);
 
@@ -1587,14 +1598,14 @@
 
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(15, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(25, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
@@ -1604,13 +1615,13 @@
 
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(15, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(29, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
@@ -1656,9 +1667,9 @@
 
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     auto iter = valueProducer->mCurrentSlicedBucket.begin();
-    auto& interval1 = iter->second[0];
+    auto& interval1 = iter->second.intervals[0];
     auto iterBase = valueProducer->mCurrentBaseInfo.begin();
-    auto& baseInfo1 = iterBase->second[0];
+    auto& baseInfo1 = iterBase->second.baseInfos[0];
     EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo1.hasBase);
     EXPECT_EQ(3, baseInfo1.base.long_value);
@@ -1692,8 +1703,8 @@
     }
     EXPECT_TRUE(it != iter);
     EXPECT_TRUE(itBase != iterBase);
-    auto& interval2 = it->second[0];
-    auto& baseInfo2 = itBase->second[0];
+    auto& interval2 = it->second.intervals[0];
+    auto& baseInfo2 = itBase->second.baseInfos[0];
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo2.hasBase);
     EXPECT_EQ(4, baseInfo2.base.long_value);
@@ -1732,9 +1743,10 @@
 
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     const auto& it = valueProducer->mCurrentSlicedBucket.begin();
-    ValueMetricProducer::Interval& interval1 = it->second[0];
+    ValueMetricProducer::Interval& interval1 = it->second.intervals[0];
     ValueMetricProducer::BaseInfo& baseInfo1 =
-            valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat())->second[0];
+            valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat())
+                    ->second.baseInfos[0];
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo1.hasBase);
     EXPECT_EQ(3, baseInfo1.base.long_value);
@@ -1761,9 +1773,10 @@
         }
     }
     EXPECT_TRUE(it2 != it);
-    ValueMetricProducer::Interval& interval2 = it2->second[0];
+    ValueMetricProducer::Interval& interval2 = it2->second.intervals[0];
     ValueMetricProducer::BaseInfo& baseInfo2 =
-            valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat())->second[0];
+            valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat())
+                    ->second.baseInfos[0];
     EXPECT_EQ(2, it2->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo2.hasBase);
     EXPECT_EQ(4, baseInfo2.base.long_value);
@@ -1792,14 +1805,16 @@
     // Get new references now that entries have been deleted from the map
     const auto& it3 = valueProducer->mCurrentSlicedBucket.begin();
     const auto& it4 = std::next(valueProducer->mCurrentSlicedBucket.begin());
-    ASSERT_EQ(it3->second.size(), 1);
-    ASSERT_EQ(it4->second.size(), 1);
-    ValueMetricProducer::Interval& interval3 = it3->second[0];
-    ValueMetricProducer::Interval& interval4 = it4->second[0];
+    ASSERT_EQ(it3->second.intervals.size(), 1);
+    ASSERT_EQ(it4->second.intervals.size(), 1);
+    ValueMetricProducer::Interval& interval3 = it3->second.intervals[0];
+    ValueMetricProducer::Interval& interval4 = it4->second.intervals[0];
     ValueMetricProducer::BaseInfo& baseInfo3 =
-            valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat())->second[0];
+            valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat())
+                    ->second.baseInfos[0];
     ValueMetricProducer::BaseInfo& baseInfo4 =
-            valueProducer->mCurrentBaseInfo.find(it4->first.getDimensionKeyInWhat())->second[0];
+            valueProducer->mCurrentBaseInfo.find(it4->first.getDimensionKeyInWhat())
+                    ->second.baseInfos[0];
 
     EXPECT_EQ(true, baseInfo3.hasBase);
     EXPECT_EQ(5, baseInfo3.base.long_value);
@@ -1837,9 +1852,9 @@
 
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     auto iter = valueProducer->mCurrentSlicedBucket.begin();
-    auto& interval1 = iter->second[0];
+    auto& interval1 = iter->second.intervals[0];
     auto iterBase = valueProducer->mCurrentBaseInfo.begin();
-    auto& baseInfo1 = iterBase->second[0];
+    auto& baseInfo1 = iterBase->second.baseInfos[0];
     EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo1.hasBase);
     EXPECT_EQ(3, baseInfo1.base.long_value);
@@ -1875,8 +1890,8 @@
     }
     EXPECT_TRUE(it != iter);
     EXPECT_TRUE(itBase != iterBase);
-    auto interval2 = it->second[0];
-    auto baseInfo2 = itBase->second[0];
+    auto interval2 = it->second.intervals[0];
+    auto baseInfo2 = itBase->second.baseInfos[0];
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo2.hasBase);
     EXPECT_EQ(4, baseInfo2.base.long_value);
@@ -1889,8 +1904,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
     // Only one interval left. One was trimmed.
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo2.hasBase);
     EXPECT_EQ(5, baseInfo2.base.long_value);
@@ -1903,8 +1918,8 @@
     allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 14));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs);
 
-    interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, baseInfo2.hasBase);
     EXPECT_EQ(14, baseInfo2.base.long_value);
     EXPECT_EQ(false, interval2.hasValue);
@@ -1943,8 +1958,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo& curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -1980,8 +1996,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo& curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -2030,8 +2047,9 @@
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 1);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(false, valueProducer->mHasGlobalBase);
@@ -2103,8 +2121,9 @@
     valueProducer->mHasGlobalBase = true;
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -2156,8 +2175,9 @@
     // Contains base from last pull which was successful.
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(140, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -2294,8 +2314,9 @@
     // Contains base from last pull which was successful.
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(140, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -2373,8 +2394,9 @@
     // Last pull failed so base has been reset.
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(false, valueProducer->mHasGlobalBase);
@@ -2460,8 +2482,9 @@
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
@@ -2469,8 +2492,8 @@
     // Empty pull.
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 10);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(false, valueProducer->mHasGlobalBase);
@@ -2513,8 +2536,9 @@
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 12);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
@@ -2524,8 +2548,8 @@
     allData.clear();
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     // Data is empty, base should be reset.
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(5, curBaseInfo.base.long_value);
@@ -2570,14 +2594,14 @@
     ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
     auto iterator = valueProducer->mCurrentSlicedBucket.begin();
     auto baseInfoIter = valueProducer->mCurrentBaseInfo.begin();
-    EXPECT_EQ(true, baseInfoIter->second[0].hasBase);
-    EXPECT_EQ(2, baseInfoIter->second[0].base.long_value);
-    EXPECT_EQ(false, iterator->second[0].hasValue);
+    EXPECT_EQ(true, baseInfoIter->second.baseInfos[0].hasBase);
+    EXPECT_EQ(2, baseInfoIter->second.baseInfos[0].base.long_value);
+    EXPECT_EQ(false, iterator->second.intervals[0].hasValue);
     iterator++;
     baseInfoIter++;
-    EXPECT_EQ(false, baseInfoIter->second[0].hasBase);
-    EXPECT_EQ(1, baseInfoIter->second[0].base.long_value);
-    EXPECT_EQ(false, iterator->second[0].hasValue);
+    EXPECT_EQ(false, baseInfoIter->second.baseInfos[0].hasBase);
+    EXPECT_EQ(1, baseInfoIter->second.baseInfos[0].base.long_value);
+    EXPECT_EQ(false, iterator->second.intervals[0].hasValue);
 
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
 }
@@ -2676,8 +2700,8 @@
 
     valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(5, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -2811,8 +2835,8 @@
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 12);
 
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(2, curInterval.value.long_value);
 
@@ -3045,8 +3069,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(20, curInterval.value.long_value);
@@ -3058,8 +3083,8 @@
 
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 }
@@ -3091,8 +3116,9 @@
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 }
@@ -3984,18 +4010,18 @@
     // Base for dimension key {}
     auto it = valueProducer->mCurrentSlicedBucket.begin();
     auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(3, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, kStateUnknown}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after screen state change kStateUnknown->ON.
     auto screenEvent = CreateScreenStateChangedEvent(
@@ -4005,19 +4031,19 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(5, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, kStateUnknown}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Bucket status after screen state change ON->OFF.
     screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10,
@@ -4027,26 +4053,26 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(9, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, ON}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(4, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(4, it->second.intervals[0].value.long_value);
     // Value for dimension, state key {{}, kStateUnknown}
     it++;
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Bucket status after screen state change OFF->ON.
     screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15,
@@ -4056,35 +4082,35 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(21, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(21, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, OFF}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(12, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(12, it->second.intervals[0].value.long_value);
     // Value for dimension, state key {{}, ON}
     it++;
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(4, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(4, it->second.intervals[0].value.long_value);
     // Value for dimension, state key {{}, kStateUnknown}
     it++;
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Start dump report and check output.
     ProtoOutputStream output;
@@ -4195,18 +4221,18 @@
     // Base for dimension key {}
     auto it = valueProducer->mCurrentSlicedBucket.begin();
     auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(3, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, {kStateUnknown}}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after screen state change kStateUnknown->ON.
     auto screenEvent = CreateScreenStateChangedEvent(
@@ -4216,19 +4242,19 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(5, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(screenOnGroup.group_id(),
-              itBase->second[0].currentState.getValues()[0].mValue.long_value);
+              itBase->second.currentState.getValues()[0].mValue.long_value);
     // Value for dimension, state key {{}, kStateUnknown}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Bucket status after screen state change ON->VR.
     // Both ON and VR are in the same state group, so the base should not change.
@@ -4239,19 +4265,19 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(5, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(screenOnGroup.group_id(),
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, kStateUnknown}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Bucket status after screen state change VR->ON.
     // Both ON and VR are in the same state group, so the base should not change.
@@ -4262,19 +4288,19 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(5, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(screenOnGroup.group_id(),
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, kStateUnknown}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Bucket status after screen state change VR->OFF.
     screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15,
@@ -4284,27 +4310,27 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(21, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(21, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(screenOffGroup.group_id(),
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, ON GROUP}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(screenOnGroup.group_id(),
               it->first.getStateValuesKey().getValues()[0].mValue.long_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(16, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(16, it->second.intervals[0].value.long_value);
     // Value for dimension, state key {{}, kStateUnknown}
     it++;
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Start dump report and check output.
     ProtoOutputStream output;
@@ -4447,35 +4473,35 @@
     // Base for dimension key {uid 1}.
     auto it = valueProducer->mCurrentSlicedBucket.begin();
     auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(3, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{uid 1}, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
     // Base for dimension key {uid 2}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(7, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(7, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{uid 2}, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after uid 1 process state change kStateUnknown -> Foreground.
     auto uidProcessEvent = CreateUidProcessStateChangedEvent(
@@ -4485,36 +4511,36 @@
     // Base for dimension key {uid 1}.
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(6, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 1, kStateUnknown}.
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(3, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(3, it->second.intervals[0].value.long_value);
 
     // Base for dimension key {uid 2}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(7, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(7, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 2, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after uid 2 process state change kStateUnknown -> Background.
     uidProcessEvent = CreateUidProcessStateChangedEvent(
@@ -4524,36 +4550,36 @@
     // Base for dimension key {uid 1}.
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(6, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 1, kStateUnknown}.
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(3, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(3, it->second.intervals[0].value.long_value);
 
     // Base for dimension key {uid 2}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(9, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 2, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Pull at end of first bucket.
     vector<shared_ptr<LogEvent>> allData;
@@ -4570,36 +4596,36 @@
     it = valueProducer->mCurrentSlicedBucket.begin();
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(15, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 2, BACKGROUND}.
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Base for dimension key {uid 1}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(10, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(10, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 1, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* kStateTracker::kUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Value for key {uid 1, FOREGROUND}
     it++;
@@ -4608,7 +4634,7 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Value for key {uid 2, kStateUnknown}
     it++;
@@ -4617,7 +4643,7 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* kStateTracker::kUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after uid 1 process state change from Foreground -> Background.
     uidProcessEvent = CreateUidProcessStateChangedEvent(
@@ -4630,35 +4656,35 @@
     // Base for dimension key {uid 2}.
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(15, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 2, BACKGROUND}.
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
     // Base for dimension key {uid 1}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(13, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(13, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 1, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
     // Value for key {uid 1, FOREGROUND}
     it++;
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
@@ -4666,8 +4692,8 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(3, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(3, it->second.intervals[0].value.long_value);
     // Value for key {uid 2, kStateUnknown}
     it++;
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
@@ -4675,7 +4701,7 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after uid 1 process state change Background->Foreground.
     uidProcessEvent = CreateUidProcessStateChangedEvent(
@@ -4687,36 +4713,36 @@
     // Base for dimension key {uid 2}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(15, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 2, BACKGROUND}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Base for dimension key {uid 1}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(17, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(17, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 1, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Value for key {uid 1, BACKGROUND}
     it++;
@@ -4725,8 +4751,8 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(4, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(4, it->second.intervals[0].value.long_value);
 
     // Value for key {uid 1, FOREGROUND}
     it++;
@@ -4735,8 +4761,8 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(3, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(3, it->second.intervals[0].value.long_value);
 
     // Value for key {uid 2, kStateUnknown}
     it++;
@@ -4857,23 +4883,23 @@
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 20 * NS_PER_SEC);
     // Base for dimension key {}
     ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
-    std::unordered_map<HashableDimensionKey, std::vector<ValueMetricProducer::BaseInfo>>::iterator
+    std::unordered_map<HashableDimensionKey, ValueMetricProducer::DimensionsInWhatInfo>::iterator
             itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(3, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::ON,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {{}, -1}
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    std::unordered_map<MetricDimensionKey, std::vector<ValueMetricProducer::Interval>>::iterator
-            it = valueProducer->mCurrentSlicedBucket.begin();
+    std::unordered_map<MetricDimensionKey, ValueMetricProducer::CurrentValueBucket>::iterator it =
+            valueProducer->mCurrentSlicedBucket.begin();
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after battery saver mode OFF event.
     unique_ptr<LogEvent> batterySaverOffEvent =
@@ -4882,12 +4908,12 @@
     // Base for dimension key {}
     ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
     itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(5, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::OFF,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {{}, ON}
     ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
     it = valueProducer->mCurrentSlicedBucket.begin();
@@ -4895,8 +4921,8 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::ON,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Pull at end of first bucket.
     vector<shared_ptr<LogEvent>> allData;
@@ -4909,23 +4935,23 @@
     // Base for dimension key {}
     ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
     itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(11, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(11, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::OFF,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
 
     // Bucket 2 status after condition change to false.
     valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC);
     // Base for dimension key {}
     ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
     itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
-    EXPECT_FALSE(itBase->second[0].hasBase);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_FALSE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::OFF,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {{}, OFF}
     ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
     it = valueProducer->mCurrentSlicedBucket.begin();
@@ -4933,8 +4959,8 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::OFF,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(4, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(4, it->second.intervals[0].value.long_value);
 
     // Start dump report and check output.
     ProtoOutputStream output;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0055711..7087b60 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1160,9 +1160,17 @@
     // TODO: Add as AppProtoEnums
     public static final int OP_PHONE_CALL_CAMERA = 101;
 
+    /**
+     * Audio is being recorded for hotword detection.
+     *
+     * @hide
+     */
+    // TODO: Add as AppProtoEnums
+    public static final int OP_RECORD_AUDIO_HOTWORD = 102;
+
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 102;
+    public static final int _NUM_OP = 103;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1497,6 +1505,13 @@
      */
     public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera";
 
+    /**
+     * Audio is being recorded for hotword detection.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RECORD_AUDIO_HOTWORD = "android:record_audio_hotword";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1688,6 +1703,7 @@
             OP_NO_ISOLATED_STORAGE,             // NO_ISOLATED_STORAGE
             OP_PHONE_CALL_MICROPHONE,           // OP_PHONE_CALL_MICROPHONE
             OP_PHONE_CALL_CAMERA,               // OP_PHONE_CALL_CAMERA
+            OP_RECORD_AUDIO_HOTWORD,            // RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -1796,6 +1812,7 @@
             OPSTR_NO_ISOLATED_STORAGE,
             OPSTR_PHONE_CALL_MICROPHONE,
             OPSTR_PHONE_CALL_CAMERA,
+            OPSTR_RECORD_AUDIO_HOTWORD,
     };
 
     /**
@@ -1905,6 +1922,7 @@
             "NO_ISOLATED_STORAGE",
             "PHONE_CALL_MICROPHONE",
             "PHONE_CALL_CAMERA",
+            "RECORD_AUDIO_HOTWORD",
     };
 
     /**
@@ -2015,6 +2033,7 @@
             null, // no permission for OP_NO_ISOLATED_STORAGE
             null, // no permission for OP_PHONE_CALL_MICROPHONE
             null, // no permission for OP_PHONE_CALL_CAMERA
+            null, // no permission for OP_RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2125,6 +2144,7 @@
             null, // NO_ISOLATED_STORAGE
             null, // PHONE_CALL_MICROPHONE
             null, // PHONE_CALL_MICROPHONE
+            null, // RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2234,6 +2254,7 @@
             null, // NO_ISOLATED_STORAGE
             null, // PHONE_CALL_MICROPHONE
             null, // PHONE_CALL_CAMERA
+            null, // RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2342,6 +2363,7 @@
             AppOpsManager.MODE_ERRORED, // OP_NO_ISOLATED_STORAGE
             AppOpsManager.MODE_ALLOWED, // PHONE_CALL_MICROPHONE
             AppOpsManager.MODE_ALLOWED, // PHONE_CALL_CAMERA
+            AppOpsManager.MODE_ALLOWED, // OP_RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2454,6 +2476,7 @@
             true, // NO_ISOLATED_STORAGE
             false, // PHONE_CALL_MICROPHONE
             false, // PHONE_CALL_CAMERA
+            false, // RECORD_AUDIO_HOTWORD
     };
 
     /**
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 7cd3fca..9e4ab33 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -54,6 +54,7 @@
 import java.io.PrintWriter;
 import java.lang.ref.Reference;
 import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -245,7 +246,7 @@
     /**
      * A cache of DisplayId, DisplayAdjustments to Display.
      */
-    private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>>
+    private final ArrayMap<Pair<Integer, DisplayAdjustments>, SoftReference<Display>>
             mAdjustedDisplays = new ArrayMap<>();
 
     /**
@@ -373,25 +374,28 @@
                 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
         final Pair<Integer, DisplayAdjustments> key =
                 Pair.create(displayId, displayAdjustmentsCopy);
+        SoftReference<Display> sd;
         synchronized (this) {
-            WeakReference<Display> wd = mAdjustedDisplays.get(key);
-            if (wd != null) {
-                final Display display = wd.get();
-                if (display != null) {
-                    return display;
-                }
-            }
-            final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
-            if (dm == null) {
-                // may be null early in system startup
-                return null;
-            }
-            final Display display = dm.getCompatibleDisplay(displayId, key.second);
-            if (display != null) {
-                mAdjustedDisplays.put(key, new WeakReference<>(display));
-            }
-            return display;
+            sd = mAdjustedDisplays.get(key);
         }
+        if (sd != null) {
+            final Display display = sd.get();
+            if (display != null) {
+                return display;
+            }
+        }
+        final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+        if (dm == null) {
+            // may be null early in system startup
+            return null;
+        }
+        final Display display = dm.getCompatibleDisplay(displayId, key.second);
+        if (display != null) {
+            synchronized (this) {
+                mAdjustedDisplays.put(key, new SoftReference<>(display));
+            }
+        }
+        return display;
     }
 
     /**
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index 7f43640..fa135b1 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -83,6 +83,8 @@
     private final AppPredictionSessionId mSessionId;
     private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
 
+    private final IBinder mToken = new Binder();
+
     /**
      * Creates a new Prediction client.
      * <p>
@@ -98,7 +100,7 @@
         mSessionId = new AppPredictionSessionId(
                 context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
         try {
-            mPredictionManager.createPredictionSession(predictionContext, mSessionId);
+            mPredictionManager.createPredictionSession(predictionContext, mSessionId, mToken);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to create predictor", e);
             e.rethrowAsRuntimeException();
diff --git a/core/java/android/app/prediction/IPredictionManager.aidl b/core/java/android/app/prediction/IPredictionManager.aidl
index 587e3fd..863fc6f9 100644
--- a/core/java/android/app/prediction/IPredictionManager.aidl
+++ b/core/java/android/app/prediction/IPredictionManager.aidl
@@ -29,7 +29,7 @@
 interface IPredictionManager {
 
     void createPredictionSession(in AppPredictionContext context,
-            in AppPredictionSessionId sessionId);
+            in AppPredictionSessionId sessionId, in IBinder token);
 
     void notifyAppTargetEvent(in AppPredictionSessionId sessionId, in AppTargetEvent event);
 
diff --git a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl b/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
index af77fe0..6d0fe72 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
@@ -20,5 +20,5 @@
 
 /** {@hide} */
 oneway interface ITimeZoneConfigurationListener {
-    void onChange(in TimeZoneConfiguration configuration);
+    void onChange();
 }
\ No newline at end of file
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
index 6e93af6..4f7e1f6 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -32,17 +32,15 @@
  * this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService}
  * for more complete documentation.
  *
- *
  * {@hide}
  */
 interface ITimeZoneDetectorService {
   TimeZoneCapabilities getCapabilities();
-
-  TimeZoneConfiguration getConfiguration();
-  boolean updateConfiguration(in TimeZoneConfiguration configuration);
   void addConfigurationListener(ITimeZoneConfigurationListener listener);
   void removeConfigurationListener(ITimeZoneConfigurationListener listener);
 
+  boolean updateConfiguration(in TimeZoneConfiguration configuration);
+
   boolean suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
   void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion);
 }
diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING
new file mode 100644
index 0000000..46f2319
--- /dev/null
+++ b/core/java/android/app/timezonedetector/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.app.timezonedetector."
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
index cc0af3f..09fffe9 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
+++ b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
@@ -16,9 +16,12 @@
 
 package android.app.timezonedetector;
 
+import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.UserIdInt;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -38,9 +41,9 @@
  *
  * <p>Actions have associated methods, see the documentation for each action for details.
  *
- * <p>For configuration capabilities, the associated current configuration value can be retrieved
- * using {@link TimeZoneDetector#getConfiguration()} and may be changed using
- * {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)}.
+ * <p>For configuration settings capabilities, the associated settings value can be found via
+ * {@link #getConfiguration()} and may be changed using {@link
+ * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} (if the user's capabilities allow).
  *
  * <p>Note: Capabilities are independent of app permissions required to call the associated APIs.
  *
@@ -60,7 +63,8 @@
     public static final int CAPABILITY_NOT_SUPPORTED = 10;
 
     /**
-     * Indicates that a capability is supported on this device, but not allowed for the user.
+     * Indicates that a capability is supported on this device, but not allowed for the user, e.g.
+     * if the capability relates to the ability to modify settings the user is not able to.
      * This could be because of the user's type (e.g. maybe it applies to the primary user only) or
      * device policy. Depending on the capability, this could mean the associated UI
      * should be hidden, or displayed but disabled.
@@ -68,9 +72,11 @@
     public static final int CAPABILITY_NOT_ALLOWED = 20;
 
     /**
-     * Indicates that a capability is possessed but not applicable, e.g. if it is configuration,
-     * the current configuration or device state renders it irrelevant. The associated UI may be
-     * hidden, disabled, or left visible (but ineffective) depending on requirements.
+     * Indicates that a capability is possessed but not currently applicable, e.g. if the
+     * capability relates to the ability to modify settings, the user has the ability to modify
+     * it, but it is currently rendered irrelevant by other settings or other device state (flags,
+     * resource config, etc.). The associated UI may be hidden, disabled, or left visible (but
+     * ineffective) depending on requirements.
      */
     public static final int CAPABILITY_NOT_APPLICABLE = 30;
 
@@ -89,13 +95,13 @@
             };
 
 
-    private final @UserIdInt int mUserId;
+    @NonNull private final TimeZoneConfiguration mConfiguration;
     private final @CapabilityState int mConfigureAutoDetectionEnabled;
     private final @CapabilityState int mConfigureGeoDetectionEnabled;
     private final @CapabilityState int mSuggestManualTimeZone;
 
     private TimeZoneCapabilities(@NonNull Builder builder) {
-        this.mUserId = builder.mUserId;
+        this.mConfiguration = Objects.requireNonNull(builder.mConfiguration);
         this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
         this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled;
         this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
@@ -103,7 +109,8 @@
 
     @NonNull
     private static TimeZoneCapabilities createFromParcel(Parcel in) {
-        return new TimeZoneCapabilities.Builder(in.readInt())
+        return new TimeZoneCapabilities.Builder()
+                .setConfiguration(in.readParcelable(null))
                 .setConfigureAutoDetectionEnabled(in.readInt())
                 .setConfigureGeoDetectionEnabled(in.readInt())
                 .setSuggestManualTimeZone(in.readInt())
@@ -112,21 +119,24 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mUserId);
+        dest.writeParcelable(mConfiguration, flags);
         dest.writeInt(mConfigureAutoDetectionEnabled);
         dest.writeInt(mConfigureGeoDetectionEnabled);
         dest.writeInt(mSuggestManualTimeZone);
     }
 
-    /** Returns the user ID the capabilities are for. */
-    public @UserIdInt int getUserId() {
-        return mUserId;
+    /**
+     * Returns the user's time zone behavior configuration.
+     */
+    public @NonNull TimeZoneConfiguration getConfiguration() {
+        return mConfiguration;
     }
 
     /**
-     * Returns the user's capability state for controlling whether automatic time zone detection is
-     * enabled via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
-     * TimeZoneConfiguration#isAutoDetectionEnabled()}.
+     * Returns the capability state associated with the user's ability to modify the automatic time
+     * zone detection setting. The setting can be updated via {@link
+     * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
+     * #getConfiguration()}.
      */
     @CapabilityState
     public int getConfigureAutoDetectionEnabled() {
@@ -134,9 +144,10 @@
     }
 
     /**
-     * Returns the user's capability state for controlling whether geolocation can be used to detect
-     * time zone via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
-     * TimeZoneConfiguration#isGeoDetectionEnabled()}.
+     * Returns the capability state associated with the user's ability to modify the geolocation
+     * detection setting. The setting can be updated via {@link
+     * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
+     * #getConfiguration()}.
      */
     @CapabilityState
     public int getConfigureGeoDetectionEnabled() {
@@ -144,8 +155,8 @@
     }
 
     /**
-     * Returns the user's capability state for manually setting the time zone on a device via
-     * {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
+     * Returns the capability state associated with the user's ability to manually set the time zone
+     * on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
      *
      * <p>The suggestion will be ignored in all cases unless the value is {@link
      * #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
@@ -155,6 +166,38 @@
         return mSuggestManualTimeZone;
     }
 
+    /**
+     * Constructs a new {@link TimeZoneConfiguration} from an {@code oldConfiguration} and a set of
+     * {@code requestedChanges}, if the current capabilities allow. The new configuration is
+     * returned and the capabilities are left unchanged. If the capabilities do not permit one or
+     * more of the changes then {@code null} is returned.
+     */
+    @Nullable
+    public TimeZoneConfiguration applyUpdate(TimeZoneConfiguration requestedChanges) {
+        if (requestedChanges.getUserId() != mConfiguration.getUserId()) {
+            throw new IllegalArgumentException("User does not match:"
+                    + " this=" + mConfiguration + ", other=" + requestedChanges);
+        }
+
+        TimeZoneConfiguration.Builder newConfigBuilder =
+                new TimeZoneConfiguration.Builder(mConfiguration);
+        if (requestedChanges.hasSetting(SETTING_AUTO_DETECTION_ENABLED)) {
+            if (getConfigureAutoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
+                return null;
+            }
+            newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled());
+        }
+
+        if (requestedChanges.hasSetting(SETTING_GEO_DETECTION_ENABLED)) {
+            if (getConfigureGeoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
+                return null;
+            }
+            newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
+        }
+
+        return newConfigBuilder.build();
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -169,7 +212,7 @@
             return false;
         }
         TimeZoneCapabilities that = (TimeZoneCapabilities) o;
-        return mUserId == that.mUserId
+        return Objects.equals(mConfiguration, that.mConfiguration)
                 && mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
                 && mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled
                 && mSuggestManualTimeZone == that.mSuggestManualTimeZone;
@@ -177,7 +220,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mUserId,
+        return Objects.hash(mConfiguration,
                 mConfigureAutoDetectionEnabled,
                 mConfigureGeoDetectionEnabled,
                 mSuggestManualTimeZone);
@@ -186,7 +229,7 @@
     @Override
     public String toString() {
         return "TimeZoneDetectorCapabilities{"
-                + "mUserId=" + mUserId
+                + "mConfiguration=" + mConfiguration
                 + ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
                 + ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled
                 + ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
@@ -196,16 +239,18 @@
     /** @hide */
     public static class Builder {
 
-        private final @UserIdInt int mUserId;
+        private TimeZoneConfiguration mConfiguration;
         private @CapabilityState int mConfigureAutoDetectionEnabled;
         private @CapabilityState int mConfigureGeoDetectionEnabled;
         private @CapabilityState int mSuggestManualTimeZone;
 
-        /**
-         * Creates a new Builder with no properties set.
-         */
-        public Builder(@UserIdInt int userId) {
-            mUserId = userId;
+        /** Sets the user-visible configuration settings. */
+        public Builder setConfiguration(@NonNull TimeZoneConfiguration configuration) {
+            if (!configuration.isComplete()) {
+                throw new IllegalArgumentException(configuration + " is not complete");
+            }
+            this.mConfiguration = configuration;
+            return this;
         }
 
         /** Sets the state for the automatic time zone detection enabled config. */
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
index 6f84ee2..95db0a2 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
+++ b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.StringDef;
+import android.annotation.UserIdInt;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -27,21 +28,20 @@
 import java.util.Objects;
 
 /**
- * Configuration that controls the behavior of the time zone detector associated with a specific
- * user.
+ * User visible settings that control the behavior of the time zone detector / manual time zone
+ * entry.
  *
- * <p>Configuration consists of a set of known properties. When reading configuration via
- * {@link TimeZoneDetector#getConfiguration()} values for all known properties will be provided. In
- * some cases, such as when the configuration relies on optional hardware, the values may be
- * meaningless / defaulted to safe values.
+ * <p>When reading the configuration, values for all settings will be provided. In some cases, such
+ * as when the device behavior relies on optional hardware / OEM configuration, or the value of
+ * several settings, the device behavior may not be directly affected by the setting value.
  *
- * <p>Configuration properties can be left absent when updating configuration via {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those values will not be
- * changed. Not all configuration properties can be modified by all users. See {@link
- * TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities}.
+ * <p>Settings can be left absent when updating configuration via {@link
+ * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those settings will not be
+ * changed. Not all configuration settings can be modified by all users: see {@link
+ * TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities} for details.
  *
- * <p>See {@link #isComplete()} to tell if all known properties are present, and {@link
- * #hasProperty(String)} with {@code PROPERTY_} constants for testing individual properties.
+ * <p>See {@link #hasSetting(String)} with {@code PROPERTY_} constants for testing for the presence
+ * of individual settings.
  *
  * @hide
  */
@@ -59,80 +59,82 @@
             };
 
     /** All configuration properties */
-    @StringDef(PROPERTY_AUTO_DETECTION_ENABLED)
+    @StringDef({ SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED })
     @Retention(RetentionPolicy.SOURCE)
-    @interface Property {}
+    @interface Setting {}
 
     /** See {@link TimeZoneConfiguration#isAutoDetectionEnabled()} for details. */
-    @Property
-    public static final String PROPERTY_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
+    @Setting
+    public static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
 
     /** See {@link TimeZoneConfiguration#isGeoDetectionEnabled()} for details. */
-    @Property
-    public static final String PROPERTY_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
+    @Setting
+    public static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
 
-    private final Bundle mBundle;
+    private final @UserIdInt int mUserId;
+    @NonNull private final Bundle mBundle;
 
     private TimeZoneConfiguration(Builder builder) {
-        this.mBundle = builder.mBundle;
+        this.mUserId = builder.mUserId;
+        this.mBundle = Objects.requireNonNull(builder.mBundle);
     }
 
     private static TimeZoneConfiguration createFromParcel(Parcel in) {
-        return new TimeZoneConfiguration.Builder()
+        return new TimeZoneConfiguration.Builder(in.readInt())
                 .setPropertyBundleInternal(in.readBundle())
                 .build();
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mUserId);
         dest.writeBundle(mBundle);
     }
 
-    /** Returns {@code true} if all known properties are set. */
-    public boolean isComplete() {
-        return hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
-                && hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
+    /** Returns the ID of the user this configuration is associated with. */
+    public @UserIdInt int getUserId() {
+        return mUserId;
     }
 
-    /** Returns true if the specified property is set. */
-    public boolean hasProperty(@Property String property) {
-        return mBundle.containsKey(property);
+    /** Returns {@code true} if all known settings are present. */
+    public boolean isComplete() {
+        return hasSetting(SETTING_AUTO_DETECTION_ENABLED)
+                && hasSetting(SETTING_GEO_DETECTION_ENABLED);
+    }
+
+    /** Returns true if the specified setting is set. */
+    public boolean hasSetting(@Setting String setting) {
+        return mBundle.containsKey(setting);
     }
 
     /**
-     * Returns the value of the {@link #PROPERTY_AUTO_DETECTION_ENABLED} property. This
+     * Returns the value of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting. This
      * controls whether a device will attempt to determine the time zone automatically using
-     * contextual information.
+     * contextual information if the device supports auto detection.
      *
-     * @throws IllegalStateException if the field has not been set
+     * <p>This setting is global and can be updated by some users.
+     *
+     * @throws IllegalStateException if the setting has not been set
      */
     public boolean isAutoDetectionEnabled() {
-        if (!mBundle.containsKey(PROPERTY_AUTO_DETECTION_ENABLED)) {
-            throw new IllegalStateException(PROPERTY_AUTO_DETECTION_ENABLED + " is not set");
-        }
-        return mBundle.getBoolean(PROPERTY_AUTO_DETECTION_ENABLED);
+        enforceSettingPresent(SETTING_AUTO_DETECTION_ENABLED);
+        return mBundle.getBoolean(SETTING_AUTO_DETECTION_ENABLED);
     }
 
     /**
-     * Returns the value of the {@link #PROPERTY_GEO_DETECTION_ENABLED} property. This
-     * controls whether a device can use location to determine time zone. Only used when
-     * {@link #isAutoDetectionEnabled()} is true.
+     * Returns the value of the {@link #SETTING_GEO_DETECTION_ENABLED} setting. This
+     * controls whether a device can use geolocation to determine time zone. Only used when
+     * {@link #isAutoDetectionEnabled()} is {@code true} and when the user has allowed their
+     * location to be used.
      *
-     * @throws IllegalStateException if the field has not been set
+     * <p>This setting is user-scoped and can be updated by some users.
+     * See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabled()}.
+     *
+     * @throws IllegalStateException if the setting has not been set
      */
     public boolean isGeoDetectionEnabled() {
-        if (!mBundle.containsKey(PROPERTY_GEO_DETECTION_ENABLED)) {
-            throw new IllegalStateException(PROPERTY_GEO_DETECTION_ENABLED + " is not set");
-        }
-        return mBundle.getBoolean(PROPERTY_GEO_DETECTION_ENABLED);
-    }
-
-    /**
-     * Convenience method to merge this with another. The argument configuration properties have
-     * precedence.
-     */
-    public TimeZoneConfiguration with(TimeZoneConfiguration other) {
-        return new Builder(this).mergeProperties(other).build();
+        enforceSettingPresent(SETTING_GEO_DETECTION_ENABLED);
+        return mBundle.getBoolean(SETTING_GEO_DETECTION_ENABLED);
     }
 
     @Override
@@ -149,43 +151,61 @@
             return false;
         }
         TimeZoneConfiguration that = (TimeZoneConfiguration) o;
-        return mBundle.kindofEquals(that.mBundle);
+        return mUserId == that.mUserId
+                && mBundle.kindofEquals(that.mBundle);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mBundle);
+        return Objects.hash(mUserId, mBundle);
     }
 
     @Override
     public String toString() {
         return "TimeZoneDetectorConfiguration{"
+                + "mUserId=" + mUserId
                 + "mBundle=" + mBundle
                 + '}';
     }
 
+    private void enforceSettingPresent(@Setting String setting) {
+        if (!mBundle.containsKey(setting)) {
+            throw new IllegalStateException(setting + " is not set");
+        }
+    }
+
     /** @hide */
     public static class Builder {
 
-        private Bundle mBundle = new Bundle();
+        private final @UserIdInt int mUserId;
+        private final Bundle mBundle = new Bundle();
 
         /**
-         * Creates a new Builder with no properties set.
+         * Creates a new Builder for a userId with no settings held.
          */
-        public Builder() {}
+        public Builder(@UserIdInt int userId) {
+            mUserId = userId;
+        }
 
         /**
-         * Creates a new Builder by copying properties from an existing instance.
+         * Creates a new Builder by copying the user ID and settings from an existing instance.
          */
         public Builder(TimeZoneConfiguration toCopy) {
+            this.mUserId = toCopy.mUserId;
             mergeProperties(toCopy);
         }
 
         /**
-         * Merges {@code other} properties into this instances, replacing existing values in this
-         * where the properties appear in both.
+         * Merges {@code other} settings into this instances, replacing existing values in this
+         * where the settings appear in both.
          */
         public Builder mergeProperties(TimeZoneConfiguration other) {
+            if (mUserId != other.mUserId) {
+                throw new IllegalArgumentException(
+                        "Cannot merge configurations for different user IDs."
+                                + " this.mUserId=" + this.mUserId
+                                + ", other.mUserId=" + other.mUserId);
+            }
             this.mBundle.putAll(other.mBundle);
             return this;
         }
@@ -195,15 +215,19 @@
             return this;
         }
 
-        /** Sets the desired state of the automatic time zone detection property. */
+        /**
+         * Sets the state of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting.
+         */
         public Builder setAutoDetectionEnabled(boolean enabled) {
-            this.mBundle.putBoolean(PROPERTY_AUTO_DETECTION_ENABLED, enabled);
+            this.mBundle.putBoolean(SETTING_AUTO_DETECTION_ENABLED, enabled);
             return this;
         }
 
-        /** Sets the desired state of the geolocation time zone detection enabled property. */
+        /**
+         * Sets the state of the {@link #SETTING_GEO_DETECTION_ENABLED} setting.
+         */
         public Builder setGeoDetectionEnabled(boolean enabled) {
-            this.mBundle.putBoolean(PROPERTY_GEO_DETECTION_ENABLED, enabled);
+            this.mBundle.putBoolean(SETTING_GEO_DETECTION_ENABLED, enabled);
             return this;
         }
 
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index 7885613..2b1cbf2 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -37,37 +37,29 @@
     TimeZoneCapabilities getCapabilities();
 
     /**
-     * Returns the current user's complete time zone configuration. See {@link
-     * TimeZoneConfiguration}.
-     */
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    @NonNull
-    TimeZoneConfiguration getConfiguration();
-
-    /**
      * Modifies the time zone detection configuration.
      *
-     * <p>Configuration properties vary in scope: some may be device-wide, others may be specific to
-     * the current user.
+     * <p>Configuration settings vary in scope: some may be global (affect all users), others may be
+     * specific to the current user.
      *
-     * <p>The ability to modify configuration properties can be subject to restrictions. For
+     * <p>The ability to modify configuration settings can be subject to restrictions. For
      * example, they may be determined by device hardware, general policy (i.e. only the primary
-     * user can set them), or by a managed device policy. See {@link #getCapabilities()} to obtain
+     * user can set them), or by a managed device policy. Use {@link #getCapabilities()} to obtain
      * information at runtime about the user's capabilities.
      *
-     * <p>Attempts to set configuration with capabilities that are {@link
+     * <p>Attempts to modify configuration settings with capabilities that are {@link
      * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link
      * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
-     * will be returned. Setting configuration with capabilities that are {@link
+     * will be returned. Modifying configuration settings with capabilities that are {@link
      * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link
      * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link
      * TimeZoneCapabilities} for further details.
      *
-     * <p>If the configuration is not "complete", then only the specified properties will be
-     * updated (where the user's capabilities allow) and other settings will be left unchanged. See
-     * {@link TimeZoneConfiguration#isComplete()}.
+     * <p>If the supplied configuration only has some values set, then only the specified settings
+     * will be updated (where the user's capabilities allow) and other settings will be left
+     * unchanged.
      *
-     * @return {@code true} if all the configuration properties specified have been set to the
+     * @return {@code true} if all the configuration settings specified have been set to the
      *   new values, {@code false} if none have
      */
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -76,14 +68,20 @@
     /**
      * An interface that can be used to listen for changes to the time zone detector configuration.
      */
+    @FunctionalInterface
     interface TimeZoneConfigurationListener {
-        /** Called when the configuration changes. There are no guarantees about the thread used. */
-        void onChange(@NonNull TimeZoneConfiguration configuration);
+        /**
+         * Called when something about the time zone configuration on the device has changed.
+         * This could be because the current user has changed, one of the device's relevant settings
+         * has changed, or something that could affect a user's capabilities has changed.
+         * There are no guarantees about the thread used.
+         */
+        void onChange();
     }
 
     /**
-     * Registers a listener that will be informed when the configuration changes. The complete
-     * configuration is passed to the listener, not just the properties that have changed.
+     * Registers a listener that will be informed when something about the time zone configuration
+     * changes.
      */
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener);
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
index 0770aff..4c69732 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
@@ -57,19 +57,6 @@
     }
 
     @Override
-    @NonNull
-    public TimeZoneConfiguration getConfiguration() {
-        if (DEBUG) {
-            Log.d(TAG, "getConfiguration called");
-        }
-        try {
-            return mITimeZoneDetectorService.getConfiguration();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @Override
     public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) {
         if (DEBUG) {
             Log.d(TAG, "updateConfiguration called: " + configuration);
@@ -94,8 +81,8 @@
                 ITimeZoneConfigurationListener iListener =
                         new ITimeZoneConfigurationListener.Stub() {
                     @Override
-                    public void onChange(@NonNull TimeZoneConfiguration configuration) {
-                        notifyConfigurationListeners(configuration);
+                    public void onChange() {
+                        notifyConfigurationListeners();
                     }
                 };
                 mConfigurationReceiver = iListener;
@@ -116,14 +103,14 @@
         }
     }
 
-    private void notifyConfigurationListeners(@NonNull TimeZoneConfiguration configuration) {
+    private void notifyConfigurationListeners() {
         final ArraySet<TimeZoneConfigurationListener> configurationListeners;
         synchronized (this) {
             configurationListeners = new ArraySet<>(mConfigurationListeners);
         }
         int size = configurationListeners.size();
         for (int i = 0; i < size; i++) {
-            configurationListeners.valueAt(i).onChange(configuration);
+            configurationListeners.valueAt(i).onChange();
         }
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c302def..03cf0cf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6404,6 +6404,17 @@
         public static final int LOCATION_MODE_ON = LOCATION_MODE_HIGH_ACCURACY;
 
         /**
+         * The current location time zone detection enabled state for the user.
+         *
+         * See {@link
+         * android.app.timezonedetector.TimeZoneDetector#getCapabilities} for access. See {@link
+         * android.app.timezonedetector.TimeZoneDetector#updateConfiguration} to update.
+         * @hide
+         */
+        public static final String LOCATION_TIME_ZONE_DETECTION_ENABLED =
+                "location_time_zone_detection_enabled";
+
+        /**
          * The accuracy in meters used for coarsening location for clients with only the coarse
          * location permission.
          *
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 73d148c..0834b2d 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -36,7 +36,18 @@
             Consts.TAG_WM),
     WM_DEBUG_ADD_REMOVE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM),
-    WM_DEBUG_FOCUS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
+    WM_DEBUG_CONFIGURATION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
+    WM_DEBUG_SWITCH(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
+    WM_DEBUG_CONTAINERS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
+    WM_DEBUG_FOCUS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
+    WM_DEBUG_IMMERSIVE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
+    WM_DEBUG_LOCKTASK(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
     WM_DEBUG_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM),
     WM_SHOW_TRANSACTIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 23af70a..cbcbe7f 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -613,7 +613,7 @@
     }
 
     // Do not change sched policy cgroup after boot complete.
-    rc = androidSetThreadPriority(pid, pri, !boot_completed);
+    rc = androidSetThreadPriorityAndPolicy(pid, pri, !boot_completed);
     if (rc != 0) {
         if (rc == INVALID_OPERATION) {
             signalExceptionForPriorityError(env, errno, pid);
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
index 72391f4..db127c6 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
@@ -22,6 +22,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 
 import org.junit.Test;
 
@@ -31,11 +32,22 @@
 
     @Test
     public void testEquals() {
-        TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
+        TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        TimeZoneConfiguration configuration2 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(false)
+                .setGeoDetectionEnabled(false)
+                .build();
+
+        TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder()
+                .setConfiguration(configuration1)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
-        TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
+        TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder()
+                .setConfiguration(configuration1)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
@@ -45,6 +57,20 @@
             assertEquals(one, two);
         }
 
+        builder2.setConfiguration(configuration2);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setConfiguration(configuration2);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertEquals(one, two);
+        }
+
         builder2.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
         {
             TimeZoneCapabilities one = builder1.build();
@@ -90,7 +116,12 @@
 
     @Test
     public void testParcelable() {
-        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
+        TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
+                .setConfiguration(configuration)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
@@ -105,4 +136,51 @@
         builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
         assertRoundTripParcelable(builder.build());
     }
+
+    @Test
+    public void testApplyUpdate_permitted() {
+        TimeZoneConfiguration oldConfiguration =
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                        .setAutoDetectionEnabled(true)
+                        .setGeoDetectionEnabled(true)
+                        .build();
+        TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
+                .setConfiguration(oldConfiguration)
+                .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
+                .build();
+        assertEquals(oldConfiguration, capabilities.getConfiguration());
+
+        TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(false)
+                .build();
+
+        TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration)
+                .setAutoDetectionEnabled(false)
+                .build();
+        assertEquals(expected, capabilities.applyUpdate(configChange));
+    }
+
+    @Test
+    public void testApplyUpdate_notPermitted() {
+        TimeZoneConfiguration oldConfiguration =
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                        .setAutoDetectionEnabled(true)
+                        .setGeoDetectionEnabled(true)
+                        .build();
+        TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
+                .setConfiguration(oldConfiguration)
+                .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
+                .setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED)
+                .build();
+        assertEquals(oldConfiguration, capabilities.getConfiguration());
+
+        TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(false)
+                .build();
+
+        assertNull(capabilities.applyUpdate(configChange));
+    }
 }
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
index 00dc73e..faf908d 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
@@ -27,11 +27,14 @@
 
 public class TimeZoneConfigurationTest {
 
+    private static final int ARBITRARY_USER_ID = 9876;
+
     @Test
     public void testBuilder_copyConstructor() {
-        TimeZoneConfiguration.Builder builder1 = new TimeZoneConfiguration.Builder()
-                .setAutoDetectionEnabled(true)
-                .setGeoDetectionEnabled(true);
+        TimeZoneConfiguration.Builder builder1 =
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                        .setAutoDetectionEnabled(true)
+                        .setGeoDetectionEnabled(true);
         TimeZoneConfiguration configuration1 = builder1.build();
 
         TimeZoneConfiguration configuration2 =
@@ -41,28 +44,28 @@
     }
 
     @Test
-    public void testIsComplete() {
-        TimeZoneConfiguration.Builder builder =
-                new TimeZoneConfiguration.Builder();
-        assertFalse(builder.build().isComplete());
+    public void testIntrospectionMethods() {
+        TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID).build();
+        assertFalse(empty.isComplete());
+        assertFalse(empty.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
 
-        builder.setAutoDetectionEnabled(true);
-        assertFalse(builder.build().isComplete());
-
-        builder.setGeoDetectionEnabled(true);
-        assertTrue(builder.build().isComplete());
+        TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        assertTrue(completeConfig.isComplete());
+        assertTrue(completeConfig.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
     }
 
     @Test
     public void testBuilder_mergeProperties() {
-        TimeZoneConfiguration configuration1 =
-                new TimeZoneConfiguration.Builder()
-                        .setAutoDetectionEnabled(true)
-                        .build();
+        TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(true)
+                .build();
 
         {
             TimeZoneConfiguration mergedEmptyAnd1 =
-                    new TimeZoneConfiguration.Builder()
+                    new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
                             .mergeProperties(configuration1)
                             .build();
             assertEquals(configuration1, mergedEmptyAnd1);
@@ -70,7 +73,7 @@
 
         {
             TimeZoneConfiguration configuration2 =
-                    new TimeZoneConfiguration.Builder()
+                    new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
                             .setAutoDetectionEnabled(false)
                             .build();
 
@@ -87,14 +90,22 @@
     @Test
     public void testEquals() {
         TimeZoneConfiguration.Builder builder1 =
-                new TimeZoneConfiguration.Builder();
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
         {
             TimeZoneConfiguration one = builder1.build();
             assertEquals(one, one);
         }
 
+        {
+            TimeZoneConfiguration.Builder differentUserBuilder =
+                    new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID + 1);
+            TimeZoneConfiguration one = builder1.build();
+            TimeZoneConfiguration two = differentUserBuilder.build();
+            assertNotEquals(one, two);
+        }
+
         TimeZoneConfiguration.Builder builder2 =
-                new TimeZoneConfiguration.Builder();
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
         {
             TimeZoneConfiguration one = builder1.build();
             TimeZoneConfiguration two = builder2.build();
@@ -148,7 +159,7 @@
     @Test
     public void testParcelable() {
         TimeZoneConfiguration.Builder builder =
-                new TimeZoneConfiguration.Builder();
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
         assertRoundTripParcelable(builder.build());
 
         builder.setAutoDetectionEnabled(true);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 73296987..03f7be7 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -13,6 +13,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-2121056984": {
+      "message": "%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "-2109936758": {
       "message": "removeAppToken make exiting: %s",
       "level": "VERBOSE",
@@ -43,6 +49,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-2029985709": {
+      "message": "setFocusedTask: taskId=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-2024464438": {
       "message": "app-onAnimationFinished(): mOuter=%s",
       "level": "DEBUG",
@@ -139,6 +151,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "-1868048288": {
+      "message": "Updating to new configuration after starting activity.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityStarter.java"
+    },
     "-1862269827": {
       "message": "applyAnimation: anim=%s transit=%s isEntrance=%b Callers=%s",
       "level": "VERBOSE",
@@ -163,6 +181,18 @@
       "group": "WM_DEBUG_RESIZE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-1810446914": {
+      "message": "Trying to update display configuration for system\/invalid process.",
+      "level": "WARN",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
+    "-1791031393": {
+      "message": "Ensuring correct configuration: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1782453012": {
       "message": "Checking theme of starting window: 0x%x",
       "level": "VERBOSE",
@@ -211,6 +241,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-1699018375": {
+      "message": "Adding activity %s to task %s callers: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
     "-1698815688": {
       "message": "Resetting app token %s of replacing window marks.",
       "level": "DEBUG",
@@ -229,12 +265,30 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1638958146": {
+      "message": "Removing activity %s from task=%s adding to task=%s Callers=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java"
+    },
     "-1632122349": {
       "message": "Changing surface while display frozen: %s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1630752478": {
+      "message": "removeLockedTask: removed %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
+    "-1598452494": {
+      "message": "activityDestroyedLocked: r=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_CONTAINERS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1596995693": {
       "message": "startAnimation",
       "level": "DEBUG",
@@ -307,6 +361,18 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "-1495062622": {
+      "message": "Can't report activity moved to display - client not running, activityRecord=%s, displayId=%d",
+      "level": "WARN",
+      "group": "WM_DEBUG_SWITCH",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-1492881555": {
+      "message": "Starting activity when config will change = %b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityStarter.java"
+    },
     "-1471946192": {
       "message": "Marking app token %s with replacing child windows.",
       "level": "DEBUG",
@@ -379,6 +445,12 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-1305755880": {
+      "message": "Initial config: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-1292329638": {
       "message": "Added starting %s: startingWindow=%s startingView=%s",
       "level": "VERBOSE",
@@ -445,6 +517,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "-1155279885": {
+      "message": "Frontmost changed immersion: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_IMMERSIVE",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-1144293044": {
       "message": "SURFACE SET FREEZE LAYER: %s",
       "level": "INFO",
@@ -475,6 +553,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "-1115019498": {
+      "message": "Configuration & display unchanged in %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1113134997": {
       "message": "Attempted to add application window with unknown token %s.  Aborting.",
       "level": "WARN",
@@ -559,12 +643,24 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-929676529": {
+      "message": "Configuration changes for %s, allChanges=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-928291778": {
       "message": "applyAnimation: anim=%s nextAppTransition=%d transit=%s Callers=%s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/AppTransition.java"
     },
+    "-927199900": {
+      "message": "Updating global configuration to: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-916108501": {
       "message": "Adding %s to %s",
       "level": "VERBOSE",
@@ -619,12 +715,24 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-846078709": {
+      "message": "Configuration doesn't matter in finishing %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-809771899": {
       "message": "findFocusedWindow: Reached focused app=%s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-804217032": {
+      "message": "Skipping config check (will change): %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-793346159": {
       "message": "New transit into wallpaper: %s",
       "level": "VERBOSE",
@@ -673,6 +781,18 @@
       "group": "WM_DEBUG_SCREEN_ON",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-743431900": {
+      "message": "Configuration no differences in %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-716565534": {
+      "message": "moveActivityStackToFront: unfocusable activity=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-714291355": {
       "message": "Losing delayed focus: %s",
       "level": "INFO",
@@ -739,6 +859,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-593535526": {
+      "message": "Binding proc %s with config %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/am\/ActivityManagerService.java"
+    },
     "-583031528": {
       "message": "%s",
       "level": "INFO",
@@ -757,6 +883,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-548282316": {
+      "message": "setLockTaskMode: Locking to %s Callers=%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "-547111355": {
       "message": "hideIme Control target: %s ",
       "level": "DEBUG",
@@ -781,6 +913,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-503656156": {
+      "message": "Update process config of %s to new config %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-496681057": {
       "message": "Attempted to get remove mode of a display that does not exist: %d",
       "level": "WARN",
@@ -799,6 +937,12 @@
       "group": "WM_SHOW_SURFACE_ALLOC",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
+    "-449118559": {
+      "message": "Trying to update display configuration for invalid process, pid=%d",
+      "level": "WARN",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-445944810": {
       "message": "finish(%b): mCanceled=%b",
       "level": "DEBUG",
@@ -835,6 +979,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java"
     },
+    "-401282500": {
+      "message": "destroyIfPossible: r=%s destroy returned removed=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_CONTAINERS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-395922585": {
       "message": "InsetsSource setWin %s",
       "level": "DEBUG",
@@ -907,18 +1057,42 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-317194205": {
+      "message": "clearLockedTasks: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "-303497363": {
       "message": "reparent: moving activity=%s to task=%d at %d",
       "level": "INFO",
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-272719931": {
+      "message": "startLockTaskModeLocked: %s",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
+    "-260960989": {
+      "message": "Removing and adding activity %s to stack at top callers=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
     "-251259736": {
       "message": "No longer freezing: %s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-235225312": {
+      "message": "Skipping config check for initializing activity: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-198463978": {
       "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b",
       "level": "VERBOSE",
@@ -943,6 +1117,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
+    "-168799453": {
+      "message": "Allowing features %d:0x%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-167822951": {
       "message": "Attempted to add starting window to token with already existing starting window",
       "level": "WARN",
@@ -979,6 +1159,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-90559682": {
+      "message": "Config is skipping already pausing %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-87705714": {
       "message": "findFocusedWindow: focusedApp=null using new focus @ %s",
       "level": "VERBOSE",
@@ -1303,6 +1489,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "355940361": {
+      "message": "Config is destroying non-running %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "371641947": {
       "message": "Window Manager Crash %s",
       "level": "WTF",
@@ -1315,6 +1507,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "374506950": {
+      "message": "Reporting activity moved to display, activityRecord=%s, displayId=%d, config=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_SWITCH",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "374972436": {
       "message": "performEnableScreen: Waiting for anim complete",
       "level": "INFO",
@@ -1411,6 +1609,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "556758086": {
+      "message": "Applying new update lock state '%s' for %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_IMMERSIVE",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "557227556": {
       "message": "onAnimationFinished(): Notify animation finished:",
       "level": "DEBUG",
@@ -1531,12 +1735,6 @@
       "group": "WM_DEBUG_SCREEN_ON",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "676824470": {
-      "message": "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
-      "level": "ERROR",
-      "group": "TEST_GROUP",
-      "at": "com\/android\/server\/wm\/ProtoLogGroup.java"
-    },
     "685047360": {
       "message": "Resizing window %s",
       "level": "VERBOSE",
@@ -1561,6 +1759,18 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "715749922": {
+      "message": "Allowlisting %d:%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
+    "736692676": {
+      "message": "Config is relaunching %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "745391677": {
       "message": "  CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x \/ %s",
       "level": "INFO",
@@ -1615,6 +1825,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "869266572": {
+      "message": "Removing activity %s from stack, reason= %s callers=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "873914452": {
       "message": "goodToGo()",
       "level": "DEBUG",
@@ -1645,12 +1861,30 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
+    "950074526": {
+      "message": "setLockTaskMode: Can't lock due to auth",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "954470154": {
       "message": "FORCED DISPLAY SCALING DISABLED",
       "level": "INFO",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "956374481": {
+      "message": "removeLockedTask: task=%s last task, reverting locktask mode. Callers=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
+    "969323241": {
+      "message": "Sending new config to %s, config: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "972354148": {
       "message": "\tcontainer=%s",
       "level": "DEBUG",
@@ -1663,12 +1897,24 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1040675582": {
+      "message": "Can't report activity configuration update - client not running, activityRecord=%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1046922686": {
       "message": "requestScrollCapture: caught exception dispatching callback: %s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1049367566": {
+      "message": "Sending to proc %s new config %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/WindowProcessController.java"
+    },
     "1051545910": {
       "message": "Exit animation finished in %s: remove=%b",
       "level": "VERBOSE",
@@ -1681,6 +1927,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
     },
+    "1088929964": {
+      "message": "onLockTaskPackagesUpdated: starting new locktask task=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "1089714158": {
       "message": "  FREEZE %s: DESTROY",
       "level": "INFO",
@@ -1789,6 +2041,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1337596507": {
+      "message": "Sending to proc %s new compat %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/CompatModePackages.java"
+    },
     "1346895820": {
       "message": "ScreenRotation still animating: type: %d\nmDisplayAnimator: %s\nmEnterBlackFrameAnimator: %s\nmRotateScreenAnimator: %s\nmScreenshotRotationAnimator: %s",
       "level": "VERBOSE",
@@ -1801,6 +2059,12 @@
       "group": "WM_DEBUG_FOCUS",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "1360551978": {
+      "message": "Trying to update display configuration for non-existing displayId=%d",
+      "level": "WARN",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "1364498663": {
       "message": "notifyAppResumed: wasStopped=%b %s",
       "level": "VERBOSE",
@@ -1819,6 +2083,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
     },
+    "1401295262": {
+      "message": "Mode default, asking user",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "1401700824": {
       "message": "Window drawn win=%s",
       "level": "DEBUG",
@@ -1927,6 +2197,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1522489371": {
+      "message": "moveActivityStackToFront: activity=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1525976603": {
       "message": "cancelAnimation(): reason=%s",
       "level": "DEBUG",
@@ -1951,6 +2227,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1576607724": {
+      "message": "Report configuration: %s %s %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "1577579529": {
       "message": "win=%s destroySurfaces: appStopped=%b win.mWindowRemovalAllowed=%b win.mRemoveOnExit=%b",
       "level": "ERROR",
@@ -1981,6 +2263,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1635062046": {
+      "message": "Skipping config check invisible: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1635462459": {
       "message": "onMovedByResize: Moving %s",
       "level": "DEBUG",
@@ -2023,6 +2311,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1679569477": {
+      "message": "Configuration doesn't matter not running %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1720229827": {
       "message": "Creating animation bounds layer",
       "level": "INFO",
@@ -2077,12 +2371,30 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1789603530": {
+      "message": "Removing activity %s hasSavedState=%b stateNotNeeded=%s finishing=%b state=%s callers=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1822843721": {
       "message": "Aborted starting %s: startingData=%s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1824105730": {
+      "message": "setLockTaskAuth: task=%s mLockTaskAuth=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
+    "1829094918": {
+      "message": "onLockTaskPackagesUpdated: removing %s mLockTaskAuth()=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "1831008694": {
       "message": "Loading animation for app transition. transit=%s enter=%b frame=%s insets=%s surfaceInsets=%s",
       "level": "DEBUG",
@@ -2161,6 +2473,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "1975793405": {
+      "message": "setFocusedStack: stackId=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "1984470582": {
       "message": "Creating TaskScreenshotAnimatable: task: %s width: %d height: %d",
       "level": "DEBUG",
@@ -2173,6 +2491,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowAnimator.java"
     },
+    "1995093920": {
+      "message": "Checking to restart %s: changed=0x%s, handles=0x%s, mLastReportedConfiguration=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "2016061474": {
       "message": "Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d Callers=%s",
       "level": "VERBOSE",
@@ -2191,6 +2515,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "2022322588": {
+      "message": "Adding activity %s to stack to task %s callers: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
     "2022422429": {
       "message": "createAnimationAdapter(): container=%s",
       "level": "DEBUG",
@@ -2269,6 +2599,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "2134999275": {
+      "message": "moveActivityStackToFront: already on top, activity=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "2137411379": {
       "message": "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b Callers=%s",
       "level": "VERBOSE",
@@ -2277,9 +2613,6 @@
     }
   },
   "groups": {
-    "TEST_GROUP": {
-      "tag": "WindowManagetProtoLogTest"
-    },
     "WM_DEBUG_ADD_REMOVE": {
       "tag": "WindowManager"
     },
@@ -2292,6 +2625,12 @@
     "WM_DEBUG_BOOT": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_CONFIGURATION": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_CONTAINERS": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_DRAW": {
       "tag": "WindowManager"
     },
@@ -2304,9 +2643,15 @@
     "WM_DEBUG_IME": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_IMMERSIVE": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_KEEP_SCREEN_ON": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_LOCKTASK": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_ORIENTATION": {
       "tag": "WindowManager"
     },
@@ -2325,6 +2670,9 @@
     "WM_DEBUG_STARTING_WINDOW": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_SWITCH": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_WINDOW_MOVEMENT": {
       "tag": "WindowManager"
     },
diff --git a/libs/WindowManager/Shell/res/layout/pip_menu_activity.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/layout/pip_menu_activity.xml
rename to libs/WindowManager/Shell/res/layout/pip_menu.xml
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
index 552cadf..b17ad0f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui;
 
+import com.android.systemui.dagger.GlobalModule;
 import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.dagger.WMModule;
 
 import javax.inject.Singleton;
 
@@ -26,7 +28,9 @@
 @Singleton
 @Component(
         modules = {
-                CarSysUIComponentModule.class
+                GlobalModule.class,
+                CarSysUIComponentModule.class,
+                WMModule.class
         })
 public interface CarGlobalRootComponent extends GlobalRootComponent {
     /**
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java
index 24d9d09..9039671 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java
@@ -20,7 +20,6 @@
 import com.android.systemui.dagger.DependencyProvider;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.SystemServicesModule;
 import com.android.systemui.dagger.SystemUIModule;
 import com.android.systemui.pip.phone.dagger.PipModule;
 
@@ -35,7 +34,6 @@
         DependencyProvider.class,
         DependencyBinder.class,
         PipModule.class,
-        SystemServicesModule.class,
         SystemUIModule.class,
         CarSystemUIModule.class,
         CarSystemUIBinder.class})
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index 3b22fdb..38e1a48 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -299,10 +299,10 @@
         // The glass pane is used to view touch events before passed to the notification list.
         // This allows us to initialize gesture listeners and detect when to close the notifications
         glassPane.setOnTouchListener((v, event) -> {
-            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+            if (isClosingAction(event)) {
                 mNotificationListAtEndAtTimeOfTouch = false;
             }
-            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            if (isOpeningAction(event)) {
                 mFirstTouchDownOnGlassPane = event.getRawX();
                 mNotificationListAtEndAtTimeOfTouch = mNotificationListAtEnd;
                 // Reset the tracker when there is a touch down on the glass pane.
@@ -355,8 +355,7 @@
             if (rect != null) {
                 clippedHeight = rect.bottom;
             }
-            if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
-                    && mIsSwipingVerticallyToClose) {
+            if (!handled && isClosingAction(event) && mIsSwipingVerticallyToClose) {
                 if (getSettleClosePercentage() < getPercentageFromEndingEdge() && isTracking) {
                     animatePanel(DEFAULT_FLING_VELOCITY, false);
                 } else if (clippedHeight != getLayout().getHeight() && isTracking) {
@@ -369,7 +368,7 @@
             // Updating the mNotificationListAtEndAtTimeOfTouch state has to be done after
             // the event has been passed to the closeGestureDetector above, such that the
             // closeGestureDetector sees the up event before the state has changed.
-            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+            if (isClosingAction(event)) {
                 mNotificationListAtEndAtTimeOfTouch = false;
             }
             return handled || isTracking;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
index 45808a8..bde31f1 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
@@ -191,6 +191,38 @@
         }
     }
 
+    /** Checks if a {@link MotionEvent} is an action to open the panel.
+     * @param e {@link MotionEvent} to check.
+     * @return true only if opening action.
+     */
+    protected boolean isOpeningAction(MotionEvent e) {
+        if (mAnimateDirection == POSITIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_DOWN;
+        }
+
+        if (mAnimateDirection == NEGATIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_UP;
+        }
+
+        return false;
+    }
+
+    /** Checks if a {@link MotionEvent} is an action to close the panel.
+     * @param e {@link MotionEvent} to check.
+     * @return true only if closing action.
+     */
+    protected boolean isClosingAction(MotionEvent e) {
+        if (mAnimateDirection == POSITIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_UP;
+        }
+
+        if (mAnimateDirection == NEGATIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_DOWN;
+        }
+
+        return false;
+    }
+
     /* ***************************************************************************************** *
      * Panel Animation
      * ***************************************************************************************** */
@@ -243,8 +275,7 @@
      * Depending on certain conditions, determines whether to fully expand or collapse the panel.
      */
     protected void maybeCompleteAnimation(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_UP
-                && isPanelVisible()) {
+        if (isClosingAction(event) && isPanelVisible()) {
             if (mSettleClosePercentage < mPercentageFromEndingEdge) {
                 animatePanel(DEFAULT_FLING_VELOCITY, false);
             } else {
diff --git a/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java b/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java
index fd6685f..6d31a8d 100644
--- a/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java
@@ -22,8 +22,6 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.pip.phone.PipMenuActivity;
-import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
 import com.android.systemui.wm.DisplaySystemBarsController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
@@ -43,12 +41,4 @@
         return new DisplaySystemBarsController(context, wmService, displayController,
                 mainHandler, transactionPool);
     }
-
-    /** TODO(b/150319024): PipMenuActivity will move to a Window */
-    @SysUISingleton
-    @PipMenuActivityClass
-    @Provides
-    Class<?> providePipMenuActivityClass() {
-        return PipMenuActivity.class;
-    }
 }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index af008b9..7f4f580 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -530,20 +530,6 @@
             androidprv:alwaysFocusable="true"
             android:excludeFromRecents="true" />
 
-        <activity
-            android:name=".pip.phone.PipMenuActivity"
-            android:permission="com.android.systemui.permission.SELF"
-            android:theme="@style/PipPhoneOverlayControlTheme"
-            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-            android:excludeFromRecents="true"
-            android:exported="false"
-            android:resizeableActivity="true"
-            android:supportsPictureInPicture="true"
-            android:stateNotNeeded="true"
-            android:taskAffinity=""
-            android:launchMode="singleTop"
-            androidprv:alwaysFocusable="true" />
-
         <!-- started from SliceProvider -->
         <activity android:name=".SlicePermissionActivity"
             android:theme="@style/Theme.SystemUI.Dialog.Alert"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index 9d52098..63f8b1f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -30,7 +30,7 @@
  */
 @ProvidesInterface(version = FalsingManager.VERSION)
 public interface FalsingManager {
-    int VERSION = 4;
+    int VERSION = 5;
 
     void onSuccessfulUnlock();
 
@@ -42,7 +42,8 @@
 
     boolean isUnlockingDisabled();
 
-    boolean isFalseTouch();
+    /** Returns true if the gesture should be rejected. */
+    boolean isFalseTouch(int interactionType);
 
     void onNotificatonStopDraggingDown();
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 02c4c5e..4b6efa9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -14,16 +14,16 @@
 
 package com.android.systemui.plugins.statusbar;
 
-import com.android.systemui.plugins.annotations.DependsOn;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
-
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+
 @ProvidesInterface(version = NotificationSwipeActionHelper.VERSION)
 @DependsOn(target = SnoozeOption.class)
 public interface NotificationSwipeActionHelper {
@@ -52,7 +52,8 @@
 
     public boolean isDismissGesture(MotionEvent ev);
 
-    public boolean isFalseGesture(MotionEvent ev);
+    /** Returns true if the gesture should be rejected. */
+    boolean isFalseGesture();
 
     public boolean swipedFarEnough(float translation, float viewSize);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index ecf1c2c9..5ad8cad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -22,6 +22,7 @@
 import android.widget.TextClock;
 
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.keyguard.dagger.KeyguardStatusViewScope;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.plugins.ClockPlugin;
@@ -36,6 +37,7 @@
 /**
  * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
  */
+@KeyguardStatusViewScope
 public class KeyguardClockSwitch extends RelativeLayout {
 
     private static final String TAG = "KeyguardClockSwitch";
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index f17f1ca..fe5fcc6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -34,10 +34,11 @@
 public class KeyguardClockSwitchController {
     private static final boolean CUSTOM_CLOCKS_ENABLED = true;
 
+    private final KeyguardClockSwitch mView;
     private final StatusBarStateController mStatusBarStateController;
     private final SysuiColorExtractor mColorExtractor;
     private final ClockManager mClockManager;
-    private KeyguardClockSwitch mView;
+    private final KeyguardSliceViewController mKeyguardSliceViewController;
 
     private final StatusBarStateController.StateListener mStateListener =
             new StatusBarStateController.StateListener() {
@@ -52,9 +53,13 @@
      *
      * The color palette changes when the wallpaper is changed.
      */
-    private final ColorExtractor.OnColorsChangedListener mColorsListener = (extractor, which) -> {
-        if ((which & WallpaperManager.FLAG_LOCK) != 0) {
-            mView.updateColors(getGradientColors());
+    private final ColorExtractor.OnColorsChangedListener mColorsListener =
+            new ColorExtractor.OnColorsChangedListener() {
+        @Override
+        public void onColorsChanged(ColorExtractor extractor, int which) {
+            if ((which & WallpaperManager.FLAG_LOCK) != 0) {
+                mView.updateColors(getGradientColors());
+            }
         }
     };
 
@@ -84,22 +89,27 @@
     };
 
     @Inject
-    public KeyguardClockSwitchController(StatusBarStateController statusBarStateController,
-            SysuiColorExtractor colorExtractor, ClockManager clockManager) {
+    public KeyguardClockSwitchController(KeyguardClockSwitch keyguardClockSwitch,
+            StatusBarStateController statusBarStateController,
+            SysuiColorExtractor colorExtractor, ClockManager clockManager,
+            KeyguardSliceViewController keyguardSliceViewController) {
+        mView = keyguardClockSwitch;
         mStatusBarStateController = statusBarStateController;
         mColorExtractor = colorExtractor;
         mClockManager = clockManager;
+        mKeyguardSliceViewController = keyguardSliceViewController;
     }
 
     /**
      * Attach the controller to the view it relates to.
      */
-    public void attach(KeyguardClockSwitch view) {
-        mView = view;
+    public void init() {
         if (mView.isAttachedToWindow()) {
             mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
         }
         mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+
+        mKeyguardSliceViewController.init();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 6f19613..be21d20 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -34,12 +34,15 @@
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.util.InjectionInflationController;
 
+import javax.inject.Inject;
+
 public class KeyguardDisplayManager {
     protected static final String TAG = "KeyguardDisplayManager";
     private static boolean DEBUG = KeyguardConstants.DEBUG;
@@ -47,6 +50,7 @@
     private final MediaRouter mMediaRouter;
     private final DisplayManager mDisplayService;
     private final InjectionInflationController mInjectableInflater;
+    private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     private final Context mContext;
 
     private boolean mShowing;
@@ -86,10 +90,13 @@
         }
     };
 
+    @Inject
     public KeyguardDisplayManager(Context context,
-            InjectionInflationController injectableInflater) {
+            InjectionInflationController injectableInflater,
+            KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
         mContext = context;
         mInjectableInflater = injectableInflater;
+        mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         mMediaRouter = mContext.getSystemService(MediaRouter.class);
         mDisplayService = mContext.getSystemService(DisplayManager.class);
         mDisplayService.registerDisplayListener(mDisplayListener, null /* handler */);
@@ -124,6 +131,7 @@
         Presentation presentation = mPresentations.get(displayId);
         if (presentation == null) {
             final Presentation newPresentation = new KeyguardPresentation(mContext, display,
+                    mKeyguardStatusViewComponentFactory,
                     mInjectableInflater.injectable(LayoutInflater.from(mContext)));
             newPresentation.setOnDismissListener(dialog -> {
                 if (newPresentation.equals(mPresentations.get(displayId))) {
@@ -241,7 +249,9 @@
     static final class KeyguardPresentation extends Presentation {
         private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
         private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
+        private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
         private final LayoutInflater mInjectableLayoutInflater;
+        private KeyguardClockSwitchController mKeyguardClockSwitchController;
         private View mClock;
         private int mUsableWidth;
         private int mUsableHeight;
@@ -259,8 +269,10 @@
         };
 
         KeyguardPresentation(Context context, Display display,
+                KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
                 LayoutInflater injectionLayoutInflater) {
             super(context, display, R.style.Theme_SystemUI_KeyguardPresentation);
+            mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
             mInjectableLayoutInflater = injectionLayoutInflater;
             getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
             setCancelable(false);
@@ -302,6 +314,12 @@
 
             // Avoid screen burn in
             mClock.post(mMoveTextRunnable);
+
+            mKeyguardClockSwitchController = mKeyguardStatusViewComponentFactory
+                    .build(findViewById(R.id.clock))
+                    .getKeyguardClockSwitchController();
+
+            mKeyguardClockSwitchController.init();
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index f639c88..a479bca 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -16,12 +16,6 @@
 
 package com.android.keyguard;
 
-import static android.app.slice.Slice.HINT_LIST_ITEM;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
@@ -35,28 +29,19 @@
 import android.graphics.text.LineBreaker;
 import android.net.Uri;
 import android.os.Trace;
-import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.TypedValue;
-import android.view.Display;
 import android.view.View;
 import android.view.animation.Animation;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.Observer;
-import androidx.slice.Slice;
 import androidx.slice.SliceItem;
-import androidx.slice.SliceViewManager;
 import androidx.slice.core.SliceQuery;
-import androidx.slice.widget.ListContent;
 import androidx.slice.widget.RowContent;
 import androidx.slice.widget.SliceContent;
-import androidx.slice.widget.SliceLiveData;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
@@ -64,70 +49,49 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.KeyguardSliceProvider;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
+import java.util.Map;
 
 /**
  * View visible under the clock on the lock screen and AoD.
  */
-public class KeyguardSliceView extends LinearLayout implements View.OnClickListener,
-        Observer<Slice>, TunerService.Tunable, ConfigurationController.ConfigurationListener {
+public class KeyguardSliceView extends LinearLayout {
 
     private static final String TAG = "KeyguardSliceView";
     public static final int DEFAULT_ANIM_DURATION = 550;
 
-    private final HashMap<View, PendingIntent> mClickActions;
-    private final ActivityStarter mActivityStarter;
-    private final ConfigurationController mConfigurationController;
     private final LayoutTransition mLayoutTransition;
-    private final TunerService mTunerService;
-    private Uri mKeyguardSliceUri;
     @VisibleForTesting
     TextView mTitle;
     private Row mRow;
     private int mTextColor;
     private float mDarkAmount = 0;
 
-    private LiveData<Slice> mLiveData;
-    private int mDisplayId = INVALID_DISPLAY;
     private int mIconSize;
     private int mIconSizeWithHeader;
     /**
      * Runnable called whenever the view contents change.
      */
     private Runnable mContentChangeListener;
-    private Slice mSlice;
     private boolean mHasHeader;
     private final int mRowWithHeaderPadding;
     private final int mRowPadding;
     private float mRowTextSize;
     private float mRowWithHeaderTextSize;
+    private View.OnClickListener mOnClickListener;
 
-    @Inject
-    public KeyguardSliceView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
-            ActivityStarter activityStarter, ConfigurationController configurationController,
-            TunerService tunerService, @Main Resources resources) {
+    public KeyguardSliceView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        mTunerService = tunerService;
-        mClickActions = new HashMap<>();
+        Resources resources = context.getResources();
         mRowPadding = resources.getDimensionPixelSize(R.dimen.subtitle_clock_padding);
         mRowWithHeaderPadding = resources.getDimensionPixelSize(R.dimen.header_subtitle_padding);
-        mActivityStarter = activityStarter;
-        mConfigurationController = configurationController;
 
         mLayoutTransition = new LayoutTransition();
         mLayoutTransition.setStagger(LayoutTransition.CHANGE_APPEARING, DEFAULT_ANIM_DURATION / 2);
@@ -153,39 +117,10 @@
                 R.dimen.widget_label_font_size);
         mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.header_row_font_size);
-        mTitle.setOnClickListener(this);
         mTitle.setBreakStrategy(LineBreaker.BREAK_STRATEGY_BALANCED);
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        Display display = getDisplay();
-        if (display != null) {
-            mDisplayId = display.getDisplayId();
-        }
-        mTunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI);
-        // Make sure we always have the most current slice
-        if (mDisplayId == DEFAULT_DISPLAY) {
-            mLiveData.observeForever(this);
-        }
-        mConfigurationController.addCallback(this);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        // TODO(b/117344873) Remove below work around after this issue be fixed.
-        if (mDisplayId == DEFAULT_DISPLAY) {
-            mLiveData.removeObserver(this);
-        }
-        mTunerService.removeTunable(this);
-        mConfigurationController.removeCallback(this);
-    }
-
-    @Override
     public void onVisibilityAggregated(boolean isVisible) {
         super.onVisibilityAggregated(isVisible);
         setLayoutTransition(isVisible ? mLayoutTransition : null);
@@ -198,44 +133,31 @@
         return mHasHeader;
     }
 
-    private void showSlice() {
-        Trace.beginSection("KeyguardSliceView#showSlice");
-        if (mSlice == null) {
-            mTitle.setVisibility(GONE);
-            mRow.setVisibility(GONE);
-            mHasHeader = false;
-            if (mContentChangeListener != null) {
-                mContentChangeListener.run();
-            }
-            Trace.endSection();
-            return;
+    void hideSlice() {
+        mTitle.setVisibility(GONE);
+        mRow.setVisibility(GONE);
+        mHasHeader = false;
+        if (mContentChangeListener != null) {
+            mContentChangeListener.run();
         }
-        mClickActions.clear();
+    }
 
-        ListContent lc = new ListContent(getContext(), mSlice);
-        SliceContent headerContent = lc.getHeader();
-        mHasHeader = headerContent != null && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM);
-        List<SliceContent> subItems = new ArrayList<>();
-        for (int i = 0; i < lc.getRowItems().size(); i++) {
-            SliceContent subItem = lc.getRowItems().get(i);
-            String itemUri = subItem.getSliceItem().getSlice().getUri().toString();
-            // Filter out the action row
-            if (!KeyguardSliceProvider.KEYGUARD_ACTION_URI.equals(itemUri)) {
-                subItems.add(subItem);
-            }
-        }
+    Map<View, PendingIntent> showSlice(RowContent header, List<SliceContent> subItems) {
+        Trace.beginSection("KeyguardSliceView#showSlice");
+        mHasHeader = header != null;
+        Map<View, PendingIntent> clickActions = new HashMap<>();
+
         if (!mHasHeader) {
             mTitle.setVisibility(GONE);
         } else {
             mTitle.setVisibility(VISIBLE);
 
-            RowContent header = lc.getHeader();
             SliceItem mainTitle = header.getTitleItem();
             CharSequence title = mainTitle != null ? mainTitle.getText() : null;
             mTitle.setText(title);
             if (header.getPrimaryAction() != null
                     && header.getPrimaryAction().getAction() != null) {
-                mClickActions.put(mTitle, header.getPrimaryAction().getAction());
+                clickActions.put(mTitle, header.getPrimaryAction().getAction());
             }
         }
 
@@ -265,7 +187,7 @@
             if (rc.getPrimaryAction() != null) {
                 pendingIntent = rc.getPrimaryAction().getAction();
             }
-            mClickActions.put(button, pendingIntent);
+            clickActions.put(button, pendingIntent);
 
             final SliceItem titleItem = rc.getTitleItem();
             button.setText(titleItem == null ? null : titleItem.getText());
@@ -286,14 +208,14 @@
                 }
             }
             button.setCompoundDrawables(iconDrawable, null, null, null);
-            button.setOnClickListener(this);
+            button.setOnClickListener(mOnClickListener);
             button.setClickable(pendingIntent != null);
         }
 
         // Removing old views
         for (int i = 0; i < mRow.getChildCount(); i++) {
             View child = mRow.getChildAt(i);
-            if (!mClickActions.containsKey(child)) {
+            if (!clickActions.containsKey(child)) {
                 mRow.removeView(child);
                 i--;
             }
@@ -303,6 +225,8 @@
             mContentChangeListener.run();
         }
         Trace.endSection();
+
+        return clickActions;
     }
 
     public void setDarkAmount(float darkAmount) {
@@ -323,14 +247,6 @@
         }
     }
 
-    @Override
-    public void onClick(View v) {
-        final PendingIntent action = mClickActions.get(v);
-        if (action != null) {
-            mActivityStarter.startPendingIntentDismissingKeyguard(action);
-        }
-    }
-
     /**
      * Runnable that gets invoked every time the title or the row visibility changes.
      * @param contentChangeListener The listener.
@@ -339,43 +255,6 @@
         mContentChangeListener = contentChangeListener;
     }
 
-    /**
-     * LiveData observer lifecycle.
-     * @param slice the new slice content.
-     */
-    @Override
-    public void onChanged(Slice slice) {
-        mSlice = slice;
-        showSlice();
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        setupUri(newValue);
-    }
-
-    /**
-     * Sets the slice provider Uri.
-     */
-    public void setupUri(String uriString) {
-        if (uriString == null) {
-            uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
-        }
-
-        boolean wasObserving = false;
-        if (mLiveData != null && mLiveData.hasActiveObservers()) {
-            wasObserving = true;
-            mLiveData.removeObserver(this);
-        }
-
-        mKeyguardSliceUri = Uri.parse(uriString);
-        mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
-
-        if (wasObserving) {
-            mLiveData.observeForever(this);
-        }
-    }
-
     @VisibleForTesting
     int getTextColor() {
         return ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
@@ -387,8 +266,7 @@
         updateTextColors();
     }
 
-    @Override
-    public void onDensityOrFontScaleChanged() {
+    void onDensityOrFontScaleChanged() {
         mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.widget_icon_size);
         mIconSizeWithHeader = (int) mContext.getResources().getDimension(R.dimen.header_icon_size);
         mRowTextSize = mContext.getResources().getDimensionPixelSize(
@@ -397,37 +275,21 @@
                 R.dimen.header_row_font_size);
     }
 
-    public void refresh() {
-        Slice slice;
-        Trace.beginSection("KeyguardSliceView#refresh");
-        // We can optimize performance and avoid binder calls when we know that we're bound
-        // to a Slice on the same process.
-        if (KeyguardSliceProvider.KEYGUARD_SLICE_URI.equals(mKeyguardSliceUri.toString())) {
-            KeyguardSliceProvider instance = KeyguardSliceProvider.getAttachedInstance();
-            if (instance != null) {
-                slice = instance.onBindSlice(mKeyguardSliceUri);
-            } else {
-                Log.w(TAG, "Keyguard slice not bound yet?");
-                slice = null;
-            }
-        } else {
-            slice = SliceViewManager.getInstance(getContext()).bindSlice(mKeyguardSliceUri);
-        }
-        onChanged(slice);
-        Trace.endSection();
-    }
-
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardSliceView:");
-        pw.println("  mClickActions: " + mClickActions);
         pw.println("  mTitle: " + (mTitle == null ? "null" : mTitle.getVisibility() == VISIBLE));
         pw.println("  mRow: " + (mRow == null ? "null" : mRow.getVisibility() == VISIBLE));
         pw.println("  mTextColor: " + Integer.toHexString(mTextColor));
         pw.println("  mDarkAmount: " + mDarkAmount);
-        pw.println("  mSlice: " + mSlice);
         pw.println("  mHasHeader: " + mHasHeader);
     }
 
+    @Override
+    public void setOnClickListener(View.OnClickListener onClickListener) {
+        mOnClickListener = onClickListener;
+        mTitle.setOnClickListener(onClickListener);
+    }
+
     public static class Row extends LinearLayout {
 
         /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
new file mode 100644
index 0000000..2470b95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static android.app.slice.Slice.HINT_LIST_ITEM;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.os.Trace;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.slice.Slice;
+import androidx.slice.SliceViewManager;
+import androidx.slice.widget.ListContent;
+import androidx.slice.widget.RowContent;
+import androidx.slice.widget.SliceContent;
+import androidx.slice.widget.SliceLiveData;
+
+import com.android.keyguard.dagger.KeyguardStatusViewScope;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+/** Controller for a {@link KeyguardSliceView}. */
+@KeyguardStatusViewScope
+public class KeyguardSliceViewController implements Dumpable {
+    private static final String TAG = "KeyguardSliceViewCtrl";
+
+    private final KeyguardSliceView mView;
+    private final KeyguardStatusView mKeyguardStatusView;
+    private final ActivityStarter mActivityStarter;
+    private final ConfigurationController mConfigurationController;
+    private final TunerService mTunerService;
+    private final DumpManager mDumpManager;
+    private int mDisplayId;
+    private LiveData<Slice> mLiveData;
+    private Uri mKeyguardSliceUri;
+    private Slice mSlice;
+    private Map<View, PendingIntent> mClickActions;
+
+    private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
+            new View.OnAttachStateChangeListener() {
+
+                @Override
+                public void onViewAttachedToWindow(View v) {
+
+                    Display display = mView.getDisplay();
+                    if (display != null) {
+                        mDisplayId = display.getDisplayId();
+                    }
+                    mTunerService.addTunable(mTunable, Settings.Secure.KEYGUARD_SLICE_URI);
+                    // Make sure we always have the most current slice
+                    if (mDisplayId == DEFAULT_DISPLAY && mLiveData != null) {
+                        mLiveData.observeForever(mObserver);
+                    }
+                    mConfigurationController.addCallback(mConfigurationListener);
+                    mDumpManager.registerDumpable(
+                            TAG + "@" + Integer.toHexString(
+                                    KeyguardSliceViewController.this.hashCode()),
+                            KeyguardSliceViewController.this);
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+
+                    // TODO(b/117344873) Remove below work around after this issue be fixed.
+                    if (mDisplayId == DEFAULT_DISPLAY) {
+                        mLiveData.removeObserver(mObserver);
+                    }
+                    mTunerService.removeTunable(mTunable);
+                    mConfigurationController.removeCallback(mConfigurationListener);
+                    mDumpManager.unregisterDumpable(TAG);
+                }
+            };
+
+    TunerService.Tunable mTunable = (key, newValue) -> setupUri(newValue);
+
+    ConfigurationController.ConfigurationListener mConfigurationListener =
+            new ConfigurationController.ConfigurationListener() {
+        @Override
+        public void onDensityOrFontScaleChanged() {
+            mView.onDensityOrFontScaleChanged();
+        }
+    };
+
+    Observer<Slice> mObserver = new Observer<Slice>() {
+        @Override
+        public void onChanged(Slice slice) {
+            mSlice = slice;
+            showSlice(slice);
+        }
+    };
+
+    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            final PendingIntent action = mClickActions.get(v);
+            if (action != null && mActivityStarter != null) {
+                mActivityStarter.startPendingIntentDismissingKeyguard(action);
+            }
+        }
+    };
+
+    @Inject
+    public KeyguardSliceViewController(KeyguardSliceView keyguardSliceView,
+            KeyguardStatusView keyguardStatusView, ActivityStarter activityStarter,
+            ConfigurationController configurationController, TunerService tunerService,
+            DumpManager dumpManager) {
+        mView = keyguardSliceView;
+        mKeyguardStatusView = keyguardStatusView;
+        mActivityStarter = activityStarter;
+        mConfigurationController = configurationController;
+        mTunerService = tunerService;
+        mDumpManager = dumpManager;
+    }
+
+    /** Initialize the controller. */
+    public void init() {
+        if (mView.isAttachedToWindow()) {
+            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
+        }
+        mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+        mView.setOnClickListener(mOnClickListener);
+        // TODO: remove the line below.
+        mKeyguardStatusView.setKeyguardSliceViewController(this);
+    }
+
+    /**
+     * Sets the slice provider Uri.
+     */
+    public void setupUri(String uriString) {
+        if (uriString == null) {
+            uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
+        }
+
+        boolean wasObserving = false;
+        if (mLiveData != null && mLiveData.hasActiveObservers()) {
+            wasObserving = true;
+            mLiveData.removeObserver(mObserver);
+        }
+
+        mKeyguardSliceUri = Uri.parse(uriString);
+        mLiveData = SliceLiveData.fromUri(mView.getContext(), mKeyguardSliceUri);
+
+        if (wasObserving) {
+            mLiveData.observeForever(mObserver);
+        }
+    }
+
+    /**
+     * Update contents of the view.
+     */
+    public void refresh() {
+        Slice slice;
+        Trace.beginSection("KeyguardSliceViewController#refresh");
+        // We can optimize performance and avoid binder calls when we know that we're bound
+        // to a Slice on the same process.
+        if (KeyguardSliceProvider.KEYGUARD_SLICE_URI.equals(mKeyguardSliceUri.toString())) {
+            KeyguardSliceProvider instance = KeyguardSliceProvider.getAttachedInstance();
+            if (instance != null) {
+                slice = instance.onBindSlice(mKeyguardSliceUri);
+            } else {
+                Log.w(TAG, "Keyguard slice not bound yet?");
+                slice = null;
+            }
+        } else {
+            // TODO: Make SliceViewManager injectable
+            slice = SliceViewManager.getInstance(mView.getContext()).bindSlice(mKeyguardSliceUri);
+        }
+        mObserver.onChanged(slice);
+        Trace.endSection();
+    }
+
+    void showSlice(Slice slice) {
+        Trace.beginSection("KeyguardSliceViewController#showSlice");
+        if (slice == null) {
+            mView.hideSlice();
+            Trace.endSection();
+            return;
+        }
+
+        ListContent lc = new ListContent(slice);
+        RowContent headerContent = lc.getHeader();
+        boolean hasHeader =
+                headerContent != null && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM);
+
+        List<SliceContent> subItems = lc.getRowItems().stream().filter(sliceContent -> {
+            String itemUri = sliceContent.getSliceItem().getSlice().getUri().toString();
+            // Filter out the action row
+            return !KeyguardSliceProvider.KEYGUARD_ACTION_URI.equals(itemUri);
+        }).collect(Collectors.toList());
+
+
+        mClickActions = mView.showSlice(hasHeader ? headerContent : null, subItems);
+
+        Trace.endSection();
+    }
+
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("  mSlice: " + mSlice);
+        pw.println("  mClickActions: " + mClickActions);
+
+        mKeyguardStatusView.dump(fd, pw, args);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 4c6aafb..6e11174 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -32,7 +32,6 @@
 import android.util.TypedValue;
 import android.view.View;
 import android.widget.GridLayout;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.core.graphics.ColorUtils;
@@ -56,7 +55,6 @@
     private final LockPatternUtils mLockPatternUtils;
     private final IActivityManager mIActivityManager;
 
-    private LinearLayout mStatusViewContainer;
     private TextView mLogoutView;
     private KeyguardClockSwitch mClockView;
     private TextView mOwnerInfo;
@@ -64,6 +62,7 @@
     private View mNotificationIcons;
     private Runnable mPendingMarqueeStart;
     private Handler mHandler;
+    private KeyguardSliceViewController mKeyguardSliceViewController;
 
     private boolean mPulsing;
     private float mDarkAmount = 0;
@@ -179,7 +178,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mStatusViewContainer = findViewById(R.id.status_view_container);
         mLogoutView = findViewById(R.id.logout);
         mNotificationIcons = findViewById(R.id.clock_notification_icon_container);
         if (mLogoutView != null) {
@@ -250,7 +248,7 @@
 
     public void dozeTimeTick() {
         refreshTime();
-        mKeyguardSlice.refresh();
+        mKeyguardSliceViewController.refresh();
     }
 
     private void refreshTime() {
@@ -456,4 +454,9 @@
             Log.e(TAG, "Failed to logout user", re);
         }
     }
+
+    // TODO: remove this method when a controller is available.
+    void setKeyguardSliceViewController(KeyguardSliceViewController keyguardSliceViewController) {
+        mKeyguardSliceViewController = keyguardSliceViewController;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
new file mode 100644
index 0000000..21ccff7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.dagger;
+
+import com.android.keyguard.KeyguardClockSwitchController;
+import com.android.keyguard.KeyguardStatusView;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardStatusView and its children.
+ */
+@Subcomponent(modules = {KeyguardStatusViewModule.class})
+@KeyguardStatusViewScope
+public interface KeyguardStatusViewComponent {
+    /** Simple factory for {@link KeyguardStatusViewComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        KeyguardStatusViewComponent build(@BindsInstance KeyguardStatusView presentation);
+    }
+
+    /** Builds a {@link com.android.keyguard.KeyguardClockSwitchController}. */
+    KeyguardClockSwitchController getKeyguardClockSwitchController();
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
new file mode 100644
index 0000000..1d51e59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.dagger;
+
+import com.android.keyguard.KeyguardClockSwitch;
+import com.android.keyguard.KeyguardSliceView;
+import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.R;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger module for {@link KeyguardStatusViewComponent}. */
+@Module
+public abstract class KeyguardStatusViewModule {
+    @Provides
+    static KeyguardClockSwitch getKeyguardClockSwitch(KeyguardStatusView keyguardPresentation) {
+        return keyguardPresentation.findViewById(R.id.keyguard_clock_container);
+    }
+
+    @Provides
+    static KeyguardSliceView getKeyguardSliceView(KeyguardClockSwitch keyguardClockSwitch) {
+        return keyguardClockSwitch.findViewById(R.id.keyguard_status_area);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
rename to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
index 114c30e..880822a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
@@ -14,17 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.systemui.pip.phone.dagger;
+package com.android.keyguard.dagger;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 
-import javax.inject.Qualifier;
+import javax.inject.Scope;
 
-@Qualifier
+/**
+ * Scope annotation for singleton items within the StatusBarComponent.
+ */
 @Documented
 @Retention(RUNTIME)
-public @interface PipMenuActivityClass {
-}
+@Scope
+public @interface KeyguardStatusViewScope {}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 47066a0..e91284b 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui;
 
+import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -696,14 +698,15 @@
         float translation = getTranslation(mCurrView);
         return ev.getActionMasked() == MotionEvent.ACTION_UP
                 && !mFalsingManager.isUnlockingDisabled()
-                && !isFalseGesture(ev) && (swipedFastEnough() || swipedFarEnough())
+                && !isFalseGesture() && (swipedFastEnough() || swipedFarEnough())
                 && mCallback.canChildBeDismissedInDirection(mCurrView, translation > 0);
     }
 
-    public boolean isFalseGesture(MotionEvent ev) {
+    /** Returns true if the gesture should be rejected. */
+    public boolean isFalseGesture() {
         boolean falsingDetected = mCallback.isAntiFalsingNeeded();
         if (mFalsingManager.isClassifierEnabled()) {
-            falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
+            falsingDetected = falsingDetected && mFalsingManager.isFalseTouch(NOTIFICATION_DISMISS);
         } else {
             falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
index 646e620..6961b45 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -70,7 +70,7 @@
     }
 
     @Override
-    public boolean isFalseTouch() {
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
         return mIsFalseTouch;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index cc64fb5..decaec1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -262,7 +262,7 @@
     /**
      * @return true if the classifier determined that this is not a human interacting with the phone
      */
-    public boolean isFalseTouch() {
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
         if (FalsingLog.ENABLED) {
             // We're getting some false wtfs from touches that happen after the device went
             // to sleep. Only report missing sessions that happen when the device is interactive.
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 83b6df3..2c31862 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -187,8 +187,8 @@
     }
 
     @Override
-    public boolean isFalseTouch() {
-        return mInternalFalsingManager.isFalseTouch();
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+        return mInternalFalsingManager.isFalseTouch(interactionType);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index a50f9ce..9d847ca 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -189,7 +189,8 @@
     }
 
     @Override
-    public boolean isFalseTouch() {
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+        mDataProvider.setInteractionType(interactionType);
         if (!mDataProvider.isDirty()) {
             return mPreviousResult;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
index ea46441..8d06748 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
@@ -116,7 +116,10 @@
      * interactionType is defined by {@link com.android.systemui.classifier.Classifier}.
      */
     final void setInteractionType(@Classifier.InteractionType int interactionType) {
-        this.mInteractionType = interactionType;
+        if (mInteractionType != interactionType) {
+            mInteractionType = interactionType;
+            mDirty = true;
+        }
     }
 
     public boolean isDirty() {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 8a67e96..0dd9488 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -18,6 +18,8 @@
 
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.INotificationManager;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -26,6 +28,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.util.DisplayMetrics;
 import android.view.Choreographer;
 import android.view.IWindowManager;
@@ -39,6 +42,7 @@
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.Prefs;
 import com.android.systemui.accessibility.ModeSwitchesController;
 import com.android.systemui.accessibility.SystemActions;
@@ -88,7 +92,7 @@
  * Provides dependencies for the root component of sysui injection.
  *
  * Only SystemUI owned classes and instances should go in here. Other, framework-owned classes
- * should go in {@link SystemServicesModule}.
+ * should go in {@link FrameworkServicesModule}.
  *
  * See SystemUI/docs/dagger.md
  */
@@ -163,6 +167,15 @@
 
     }
 
+    @SuppressLint("MissingPermission")
+    @SysUISingleton
+    @Provides
+    @Nullable
+    static LocalBluetoothManager provideLocalBluetoothController(Context context,
+            @Background Handler bgHandler) {
+        return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL);
+    }
+
     /** */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
rename to packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index ceb9aee..66063a8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -17,7 +17,6 @@
 package com.android.systemui.dagger;
 
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
@@ -49,10 +48,8 @@
 import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
 import android.os.BatteryStats;
-import android.os.Handler;
 import android.os.PowerManager;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
 import android.service.dreams.DreamService;
@@ -68,12 +65,12 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.LatencyTracker;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
+import javax.inject.Singleton;
+
 import dagger.Module;
 import dagger.Provides;
 
@@ -81,51 +78,51 @@
  * Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
  */
 @Module
-public class SystemServicesModule {
+public class FrameworkServicesModule {
     @Provides
-    @SysUISingleton
+    @Singleton
     static AccessibilityManager provideAccessibilityManager(Context context) {
         return context.getSystemService(AccessibilityManager.class);
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static ActivityManager provideActivityManager(Context context) {
         return context.getSystemService(ActivityManager.class);
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static AlarmManager provideAlarmManager(Context context) {
         return context.getSystemService(AlarmManager.class);
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static AudioManager provideAudioManager(Context context) {
         return context.getSystemService(AudioManager.class);
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static ColorDisplayManager provideColorDisplayManager(Context context) {
         return context.getSystemService(ColorDisplayManager.class);
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static ConnectivityManager provideConnectivityManagager(Context context) {
         return context.getSystemService(ConnectivityManager.class);
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static ContentResolver provideContentResolver(Context context) {
         return context.getContentResolver();
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static DevicePolicyManager provideDevicePolicyManager(Context context) {
         return context.getSystemService(DevicePolicyManager.class);
     }
@@ -137,39 +134,39 @@
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static DisplayManager provideDisplayManager(Context context) {
         return context.getSystemService(DisplayManager.class);
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static IActivityManager provideIActivityManager() {
         return ActivityManager.getService();
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static IActivityTaskManager provideIActivityTaskManager() {
         return ActivityTaskManager.getService();
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static IBatteryStats provideIBatteryStats() {
         return IBatteryStats.Stub.asInterface(
                 ServiceManager.getService(BatteryStats.SERVICE_NAME));
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static IDreamManager provideIDreamManager() {
         return IDreamManager.Stub.asInterface(
                 ServiceManager.checkService(DreamService.DREAM_SERVICE));
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     @Nullable
     static FaceManager provideFaceManager(Context context) {
         return context.getSystemService(FaceManager.class);
@@ -177,13 +174,13 @@
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static IPackageManager provideIPackageManager() {
         return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static IStatusBarService provideIStatusBarService() {
         return IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -196,39 +193,30 @@
                 ServiceManager.getService(Context.WALLPAPER_SERVICE));
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static IWindowManager provideIWindowManager() {
         return WindowManagerGlobal.getWindowManagerService();
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static KeyguardManager provideKeyguardManager(Context context) {
         return context.getSystemService(KeyguardManager.class);
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static LatencyTracker provideLatencyTracker(Context context) {
         return LatencyTracker.getInstance(context);
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static LauncherApps provideLauncherApps(Context context) {
         return context.getSystemService(LauncherApps.class);
     }
 
-    @SuppressLint("MissingPermission")
-    @SysUISingleton
-    @Provides
-    @Nullable
-    static LocalBluetoothManager provideLocalBluetoothController(Context context,
-            @Background Handler bgHandler) {
-        return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL);
-    }
-
     @Provides
     static MediaRouter2Manager provideMediaRouter2Manager(Context context) {
         return MediaRouter2Manager.getInstance(context);
@@ -240,32 +228,32 @@
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static NetworkScoreManager provideNetworkScoreManager(Context context) {
         return context.getSystemService(NetworkScoreManager.class);
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static NotificationManager provideNotificationManager(Context context) {
         return context.getSystemService(NotificationManager.class);
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static PackageManager providePackageManager(Context context) {
         return context.getPackageManager();
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static PackageManagerWrapper providePackageManagerWrapper() {
         return PackageManagerWrapper.getInstance();
     }
 
     /** */
-    @SysUISingleton
     @Provides
+    @Singleton
     static PowerManager providePowerManager(Context context) {
         return context.getSystemService(PowerManager.class);
     }
@@ -277,57 +265,63 @@
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
+    static RoleManager provideRoleManager(Context context) {
+        return context.getSystemService(RoleManager.class);
+    }
+
+    @Provides
+    @Singleton
     static SensorManager providesSensorManager(Context context) {
         return context.getSystemService(SensorManager.class);
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static SensorPrivacyManager provideSensorPrivacyManager(Context context) {
         return context.getSystemService(SensorPrivacyManager.class);
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static ShortcutManager provideShortcutManager(Context context) {
         return context.getSystemService(ShortcutManager.class);
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     @Nullable
     static TelecomManager provideTelecomManager(Context context) {
         return context.getSystemService(TelecomManager.class);
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static TelephonyManager provideTelephonyManager(Context context) {
         return context.getSystemService(TelephonyManager.class);
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static TrustManager provideTrustManager(Context context) {
         return context.getSystemService(TrustManager.class);
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     @Nullable
     static Vibrator provideVibrator(Context context) {
         return context.getSystemService(Vibrator.class);
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static ViewConfiguration provideViewConfiguration(Context context) {
         return ViewConfiguration.get(context);
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     static UserManager provideUserManager(Context context) {
         return context.getSystemService(UserManager.class);
     }
@@ -338,21 +332,15 @@
     }
 
     @Provides
-    @SysUISingleton
+    @Singleton
     @Nullable
     static WifiManager provideWifiManager(Context context) {
         return context.getSystemService(WifiManager.class);
     }
 
-    @SysUISingleton
     @Provides
+    @Singleton
     static WindowManager provideWindowManager(Context context) {
         return context.getSystemService(WindowManager.class);
     }
-
-    @Provides
-    @SysUISingleton
-    static RoleManager provideRoleManager(Context context) {
-        return context.getSystemService(RoleManager.class);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
index 553655b..fd4a409 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -16,63 +16,23 @@
 
 package com.android.systemui.dagger;
 
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.hardware.display.AmbientDisplayConfiguration;
-import android.util.DisplayMetrics;
-import android.view.Choreographer;
-
-import com.android.systemui.Prefs;
-import com.android.systemui.dagger.qualifiers.Main;
-
-import javax.inject.Singleton;
-
 import dagger.Module;
-import dagger.Provides;
 
 /**
- * Supplies globally scoped instances.
+ * Supplies globally scoped instances that should be available in all versions of SystemUI
  *
  * Providers in this module will be accessible to both WMComponent and SysUIComponent scoped
  * classes. They are in here because they are either needed globally or are inherently universal
  * to the application.
  *
  * Note that just because a class might be used by both WM and SysUI does not necessarily mean that
- * it should got into this module. If WM and SysUI might need the class for different purposes
+ * it should go into this module. If WM and SysUI might need the class for different purposes
  * or different semantics, it may make sense to ask them to supply their own. Something like
  * threading and concurrency provide a good example. Both components need
  * Threads/Handlers/Executors, but they need separate instances of them in many cases.
  *
  * Please use discretion when adding things to the global scope.
  */
-@Module
+@Module(includes = {FrameworkServicesModule.class})
 public class GlobalModule {
-    /** */
-    @Provides
-    @Main
-    public SharedPreferences provideSharePreferences(Context context) {
-        return Prefs.get(context);
-    }
-
-    /** */
-    @Provides
-    public AmbientDisplayConfiguration provideAmbientDisplayConfiguration(Context context) {
-        return new AmbientDisplayConfiguration(context);
-    }
-
-    /** */
-    @Provides
-    @Singleton
-    public Choreographer providesChoreographer() {
-        return Choreographer.getInstance();
-    }
-
-    /** */
-    @Provides
-    @Singleton
-    public DisplayMetrics provideDisplayMetrics(Context context) {
-        DisplayMetrics displayMetrics = new DisplayMetrics();
-        context.getDisplay().getMetrics(displayMetrics);
-        return displayMetrics;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index 3d7c8ad4..36fd337 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -28,6 +28,7 @@
  */
 @Singleton
 @Component(modules = {
+        GlobalModule.class,
         SysUISubcomponentModule.class,
         WMModule.class})
 public interface GlobalRootComponent {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index b606201..e4e3d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -37,7 +37,6 @@
         DependencyProvider.class,
         DependencyBinder.class,
         PipModule.class,
-        SystemServicesModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
         SystemUIDefaultModule.class})
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index e38dce0..8364b48 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -262,11 +262,11 @@
             onWakeScreen(wakeEvent, mMachine.isExecutingTransition() ? null : mMachine.getState());
         } else if (isLongPress) {
             requestPulse(pulseReason, true /* alreadyPerformedProxCheck */,
-                    null /* onPulseSupressedListener */);
+                    null /* onPulseSuppressedListener */);
         } else if (isWakeLockScreen) {
             if (wakeEvent) {
                 requestPulse(pulseReason, true /* alreadyPerformedProxCheck */,
-                        null /* onPulseSupressedListener */);
+                        null /* onPulseSuppressedListener */);
             }
         } else {
             proximityCheckThenCall((result) -> {
@@ -536,7 +536,7 @@
             if (PULSE_ACTION.equals(intent.getAction())) {
                 if (DozeMachine.DEBUG) Log.d(TAG, "Received pulse intent");
                 requestPulse(DozeLog.PULSE_REASON_INTENT, false, /* performedProxCheck */
-                        null /* onPulseSupressedListener */);
+                        null /* onPulseSuppressedListener */);
             }
             if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
                 mMachine.requestState(DozeMachine.State.FINISH);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6214a64..3340791 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -89,15 +89,14 @@
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.InjectionInflationController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -228,7 +227,6 @@
 
     /** TrustManager for letting it know when we change visibility */
     private final TrustManager mTrustManager;
-    private final InjectionInflationController mInjectionInflationController;
 
     /**
      * Used to keep the device awake while to ensure the keyguard finishes opening before
@@ -345,7 +343,7 @@
     /**
      * For managing external displays
      */
-    private KeyguardDisplayManager mKeyguardDisplayManager;
+    private final KeyguardDisplayManager mKeyguardDisplayManager;
 
     private final ArrayList<IKeyguardStateCallback> mKeyguardStateCallbacks = new ArrayList<>();
 
@@ -724,7 +722,7 @@
             TrustManager trustManager,
             DeviceConfigProxy deviceConfig,
             NavigationModeController navigationModeController,
-            InjectionInflationController injectionInflationController) {
+            KeyguardDisplayManager keyguardDisplayManager) {
         super(context);
         mFalsingManager = falsingManager;
         mLockPatternUtils = lockPatternUtils;
@@ -735,7 +733,7 @@
         mUpdateMonitor = keyguardUpdateMonitor;
         mPM = powerManager;
         mTrustManager = trustManager;
-        mInjectionInflationController = injectionInflationController;
+        mKeyguardDisplayManager = keyguardDisplayManager;
         dumpManager.registerDumpable(getClass().getName(), this);
         mDeviceConfig = deviceConfig;
         mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
@@ -775,9 +773,6 @@
         mContext.registerReceiver(mDelayedLockBroadcastReceiver, delayedActionFilter,
                 SYSTEMUI_PERMISSION, null /* scheduler */);
 
-        mKeyguardDisplayManager = new KeyguardDisplayManager(mContext,
-                mInjectionInflationController);
-
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
         KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index c9164f0..9d8e73a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -26,8 +26,10 @@
 import android.os.PowerManager;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -43,7 +45,6 @@
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.InjectionInflationController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SystemSettings;
@@ -58,7 +59,7 @@
 /**
  * Dagger Module providing {@link StatusBar}.
  */
-@Module
+@Module(subcomponents = {KeyguardStatusViewComponent.class})
 public class KeyguardModule {
     /**
      * Provides our instance of KeyguardViewMediator which is considered optional.
@@ -79,7 +80,7 @@
             @UiBackground Executor uiBgExecutor,
             DeviceConfigProxy deviceConfig,
             NavigationModeController navigationModeController,
-            InjectionInflationController injectionInflationController) {
+            KeyguardDisplayManager keyguardDisplayManager) {
         return new KeyguardViewMediator(
                 context,
                 falsingManager,
@@ -94,7 +95,8 @@
                 trustManager,
                 deviceConfig,
                 navigationModeController,
-                injectionInflationController);
+                keyguardDisplayManager
+        );
     }
 
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index 77cac50..4863999 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.qs.PageIndicator
 import com.android.systemui.R
+import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.util.animation.PhysicsAnimator
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -315,7 +316,8 @@
         return false
     }
 
-    private fun isFalseTouch() = falsingProtectionNeeded && falsingManager.isFalseTouch
+    private fun isFalseTouch() = falsingProtectionNeeded &&
+            falsingManager.isFalseTouch(NOTIFICATION_DISMISS)
 
     private fun getMaxTranslation() = if (showsSettingsButton) {
             settingsButton.width
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 7421ec1..c956702 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -49,6 +49,9 @@
 import android.util.Log;
 import android.util.Size;
 import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowManager;
 import android.window.TaskOrganizer;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
@@ -57,6 +60,7 @@
 
 import com.android.internal.os.SomeArgs;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.pip.phone.PipMenuActivityController;
 import com.android.systemui.pip.phone.PipUpdateThread;
 import com.android.systemui.stackdivider.SplitScreen;
 import com.android.wm.shell.R;
@@ -95,6 +99,7 @@
     private static final int MSG_FINISH_RESIZE = 4;
     private static final int MSG_RESIZE_USER = 5;
 
+    private final Context mContext;
     private final Handler mMainHandler;
     private final Handler mUpdateHandler;
     private final PipBoundsHandler mPipBoundsHandler;
@@ -107,6 +112,8 @@
     private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
     private final Optional<SplitScreen> mSplitScreenOptional;
     protected final ShellTaskOrganizer mTaskOrganizer;
+    private SurfaceControlViewHost mPipViewHost;
+    private SurfaceControl mPipMenuSurface;
 
     // These callbacks are called on the update thread
     private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
@@ -212,6 +219,7 @@
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
             @NonNull ShellTaskOrganizer shellTaskOrganizer) {
+        mContext = context;
         mMainHandler = new Handler(Looper.getMainLooper());
         mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
         mPipBoundsHandler = boundsHandler;
@@ -504,6 +512,45 @@
     }
 
     /**
+     * Setup the ViewHost and attach the provided menu view to the ViewHost.
+     */
+    public void attachPipMenuViewHost(View menuView, WindowManager.LayoutParams lp) {
+        if (mPipMenuSurface != null) {
+            Log.e(TAG, "PIP Menu View already created and attached.");
+            return;
+        }
+
+        if (Looper.getMainLooper() != Looper.myLooper()) {
+            throw new RuntimeException("PipMenuView needs to be attached on the main thread.");
+        }
+
+        mPipViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+                (android.os.Binder) null);
+        mPipMenuSurface = mPipViewHost.getSurfacePackage().getSurfaceControl();
+        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+        transaction.reparent(mPipMenuSurface, mLeash);
+        transaction.show(mPipMenuSurface);
+        transaction.setRelativeLayer(mPipMenuSurface, mLeash, 1);
+        transaction.apply();
+        mPipViewHost.setView(menuView, lp);
+    }
+
+
+    /**
+     * Releases the PIP Menu's View host, remove it from PIP task surface.
+     */
+    public void detachPipMenuViewHost() {
+        if (mPipMenuSurface != null) {
+            SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+            transaction.remove(mPipMenuSurface);
+            transaction.apply();
+            mPipMenuSurface = null;
+            mPipViewHost = null;
+        }
+    }
+
+
+    /**
      * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int)}.
      * Meanwhile this callback is invoked whenever the task is removed. For instance:
      *   - as a result of removeStacksInWindowingModes from WM
@@ -838,6 +885,12 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         prepareFinishResizeTransaction(destinationBounds, direction, tx, wct);
         applyFinishBoundsResize(wct, direction);
+        runOnMainHandler(() -> {
+            if (mPipViewHost != null) {
+                mPipViewHost.relayout(PipMenuActivityController.getPipMenuLayoutParams(
+                        destinationBounds.width(), destinationBounds.height()));
+            }
+        });
     }
 
     private void prepareFinishResizeTransaction(Rect destinationBounds,
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index d8864ec..5ef5b90 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -49,7 +49,6 @@
 import com.android.systemui.pip.PipSurfaceTransactionHelper;
 import com.android.systemui.pip.PipTaskOrganizer;
 import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
@@ -267,7 +266,6 @@
 
     @Inject
     public PipManager(Context context, BroadcastDispatcher broadcastDispatcher,
-            @PipMenuActivityClass Class<?> pipMenuActivityClass,
             ConfigurationController configController,
             DeviceConfigProxy deviceConfig,
             DisplayController displayController,
@@ -296,8 +294,8 @@
         mPipTaskOrganizer.registerPipTransitionCallback(this);
         mInputConsumerController = InputConsumerController.getPipInputConsumer();
         mMediaController = new PipMediaController(context, mActivityManager, broadcastDispatcher);
-        mMenuController = new PipMenuActivityController(context, pipMenuActivityClass,
-                mMediaController, mInputConsumerController);
+        mMenuController = new PipMenuActivityController(context,
+                mMediaController, mInputConsumerController, mPipTaskOrganizer);
         mTouchHandler = new PipTouchHandler(context, mActivityManager,
                 mMenuController, mInputConsumerController, mPipBoundsHandler, mPipTaskOrganizer,
                 floatingContentCoordinator, deviceConfig, sysUiState, pipUiEventLogger);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
deleted file mode 100644
index 1b1b2de..0000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ /dev/null
@@ -1,730 +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.systemui.pip.phone;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
-import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
-import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
-
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_DISMISS_FRACTION;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MENU_STATE;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_SHOW_MENU_WITH_DELAY;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_SHOW_RESIZE_HANDLE;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_STACK_BOUNDS;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_WILL_RESIZE_MENU;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.PendingIntent.CanceledException;
-import android.app.RemoteAction;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ParceledListSlice;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.Pair;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager.LayoutParams;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-import com.android.systemui.Interpolators;
-import com.android.wm.shell.R;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Translucent activity that gets started on top of a task in PIP to allow the user to control it.
- * TODO(b/150319024): PipMenuActivity will move to a Window
- */
-public class PipMenuActivity extends Activity {
-
-    private static final String TAG = "PipMenuActivity";
-
-    private static final int MESSAGE_INVALID_TYPE = -1;
-
-    public static final int MESSAGE_SHOW_MENU = 1;
-    public static final int MESSAGE_POKE_MENU = 2;
-    public static final int MESSAGE_HIDE_MENU = 3;
-    public static final int MESSAGE_UPDATE_ACTIONS = 4;
-    public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5;
-    public static final int MESSAGE_ANIMATION_ENDED = 6;
-    public static final int MESSAGE_POINTER_EVENT = 7;
-    public static final int MESSAGE_MENU_EXPANDED = 8;
-    public static final int MESSAGE_FADE_OUT_MENU = 9;
-    public static final int MESSAGE_UPDATE_MENU_LAYOUT = 10;
-
-    private static final int INITIAL_DISMISS_DELAY = 3500;
-    private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
-    private static final long MENU_FADE_DURATION = 125;
-    private static final long MENU_SLOW_FADE_DURATION = 175;
-    private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
-
-    private static final float MENU_BACKGROUND_ALPHA = 0.3f;
-    private static final float DISMISS_BACKGROUND_ALPHA = 0.6f;
-
-    private static final float DISABLED_ACTION_ALPHA = 0.54f;
-
-    private static final boolean ENABLE_RESIZE_HANDLE = false;
-
-    private int mMenuState;
-    private boolean mResize = true;
-    private boolean mAllowMenuTimeout = true;
-    private boolean mAllowTouches = true;
-
-    private final List<RemoteAction> mActions = new ArrayList<>();
-
-    private AccessibilityManager mAccessibilityManager;
-    private Drawable mBackgroundDrawable;
-    private View mMenuContainer;
-    private LinearLayout mActionsGroup;
-    private int mBetweenActionPaddingLand;
-
-    private AnimatorSet mMenuContainerAnimator;
-
-    private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
-            new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    final float alpha = (float) animation.getAnimatedValue();
-                    mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA*alpha*255));
-                }
-            };
-
-    private Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MESSAGE_SHOW_MENU: {
-                    final Bundle data = (Bundle) msg.obj;
-                    showMenu(data.getInt(EXTRA_MENU_STATE),
-                            data.getParcelable(EXTRA_STACK_BOUNDS),
-                            data.getBoolean(EXTRA_ALLOW_TIMEOUT),
-                            data.getBoolean(EXTRA_WILL_RESIZE_MENU),
-                            data.getBoolean(EXTRA_SHOW_MENU_WITH_DELAY),
-                            data.getBoolean(EXTRA_SHOW_RESIZE_HANDLE));
-                    break;
-                }
-                case MESSAGE_POKE_MENU:
-                    cancelDelayedFinish();
-                    break;
-                case MESSAGE_HIDE_MENU:
-                    hideMenu((Runnable) msg.obj);
-                    break;
-                case MESSAGE_UPDATE_ACTIONS: {
-                    final Bundle data = (Bundle) msg.obj;
-                    final ParceledListSlice<RemoteAction> actions = data.getParcelable(
-                            EXTRA_ACTIONS);
-                    setActions(data.getParcelable(EXTRA_STACK_BOUNDS), actions != null
-                            ? actions.getList() : Collections.emptyList());
-                    break;
-                }
-                case MESSAGE_UPDATE_DISMISS_FRACTION: {
-                    final Bundle data = (Bundle) msg.obj;
-                    updateDismissFraction(data.getFloat(EXTRA_DISMISS_FRACTION));
-                    break;
-                }
-                case MESSAGE_ANIMATION_ENDED: {
-                    mAllowTouches = true;
-                    break;
-                }
-                case MESSAGE_POINTER_EVENT: {
-                    final MotionEvent ev = (MotionEvent) msg.obj;
-                    dispatchPointerEvent(ev);
-                    break;
-                }
-                case MESSAGE_MENU_EXPANDED : {
-                    if (mMenuContainerAnimator == null) {
-                        return;
-                    }
-                    mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY);
-                    mMenuContainerAnimator.start();
-                    break;
-                }
-                case MESSAGE_FADE_OUT_MENU: {
-                    fadeOutMenu();
-                    break;
-                }
-                case MESSAGE_UPDATE_MENU_LAYOUT: {
-                    if (mPipMenuIconsAlgorithm == null) {
-                        return;
-                    }
-                    final Rect bounds = (Rect) msg.obj;
-                    mPipMenuIconsAlgorithm.onBoundsChanged(bounds);
-                    break;
-                }
-            }
-        }
-    };
-    private Messenger mToControllerMessenger;
-    private Messenger mMessenger = new Messenger(mHandler);
-
-    private final Runnable mFinishRunnable = this::hideMenu;
-
-    protected View mViewRoot;
-    protected View mSettingsButton;
-    protected View mDismissButton;
-    protected View mResizeHandle;
-    protected View mTopEndContainer;
-    protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        // Set the flags to allow us to watch for outside touches and also hide the menu and start
-        // manipulating the PIP in the same touch gesture
-        getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
-
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.pip_menu_activity);
-
-        mAccessibilityManager = getSystemService(AccessibilityManager.class);
-        mBackgroundDrawable = new ColorDrawable(Color.BLACK);
-        mBackgroundDrawable.setAlpha(0);
-        mViewRoot = findViewById(R.id.background);
-        mViewRoot.setBackground(mBackgroundDrawable);
-        mMenuContainer = findViewById(R.id.menu_container);
-        mMenuContainer.setAlpha(0);
-        mTopEndContainer = findViewById(R.id.top_end_container);
-        mSettingsButton = findViewById(R.id.settings);
-        mSettingsButton.setAlpha(0);
-        mSettingsButton.setOnClickListener((v) -> {
-            if (v.getAlpha() != 0) {
-                showSettings();
-            }
-        });
-        mDismissButton = findViewById(R.id.dismiss);
-        mDismissButton.setAlpha(0);
-        mDismissButton.setOnClickListener(v -> dismissPip());
-        findViewById(R.id.expand_button).setOnClickListener(v -> {
-            if (mMenuContainer.getAlpha() != 0) {
-                expandPip();
-            }
-        });
-        mResizeHandle = findViewById(R.id.resize_handle);
-        mResizeHandle.setAlpha(0);
-        mActionsGroup = findViewById(R.id.actions_group);
-        mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
-                R.dimen.pip_between_action_padding_land);
-        mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(this.getApplicationContext());
-        mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
-                mResizeHandle, mSettingsButton, mDismissButton);
-        updateFromIntent(getIntent());
-        setTitle(R.string.pip_menu_title);
-        setDisablePreviewScreenshots(true);
-
-        // Hide without an animation.
-        getWindow().setExitTransition(null);
-
-        initAccessibility();
-    }
-
-    private void initAccessibility() {
-        getWindow().getDecorView().setAccessibilityDelegate(new View.AccessibilityDelegate() {
-            @Override
-            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-                super.onInitializeAccessibilityNodeInfo(host, info);
-                String label = getResources().getString(R.string.pip_menu_title);
-                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, label));
-            }
-
-            @Override
-            public boolean performAccessibilityAction(View host, int action, Bundle args) {
-                if (action == ACTION_CLICK && mMenuState == MENU_STATE_CLOSE) {
-                    Message m = Message.obtain();
-                    m.what = PipMenuActivityController.MESSAGE_SHOW_MENU;
-                    sendMessage(m, "Could not notify controller to show PIP menu");
-                }
-                return super.performAccessibilityAction(host, action, args);
-            }
-        });
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
-            hideMenu();
-            return true;
-        }
-        return super.onKeyUp(keyCode, event);
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        updateFromIntent(intent);
-    }
-
-    @Override
-    public void onUserInteraction() {
-        if (mAllowMenuTimeout) {
-            repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
-        }
-    }
-
-    @Override
-    protected void onUserLeaveHint() {
-        super.onUserLeaveHint();
-
-        // If another task is starting on top of the menu, then hide and finish it so that it can be
-        // recreated on the top next time it starts
-        hideMenu();
-    }
-
-    @Override
-    public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
-        super.onTopResumedActivityChanged(isTopResumedActivity);
-        if (!isTopResumedActivity && mMenuState != MENU_STATE_NONE) {
-            hideMenu();
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        // In cases such as device lock, hide and finish it so that it can be recreated on the top
-        // next time it starts, see also {@link #onUserLeaveHint}
-        hideMenu();
-        cancelDelayedFinish();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        // Fallback, if we are destroyed for any other reason (like when the task is being reset),
-        // also reset the callback.
-        notifyActivityCallback(null);
-    }
-
-    @Override
-    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
-        if (!isInPictureInPictureMode) {
-            finish();
-        }
-    }
-
-    /**
-     * Dispatch a pointer event from {@link PipTouchHandler}.
-     */
-    private void dispatchPointerEvent(MotionEvent event) {
-        if (event.isTouchEvent()) {
-            dispatchTouchEvent(event);
-        } else {
-            dispatchGenericMotionEvent(event);
-        }
-    }
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (!mAllowTouches) {
-            return false;
-        }
-
-        // On the first action outside the window, hide the menu
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_OUTSIDE:
-                hideMenu();
-                return true;
-        }
-        return super.dispatchTouchEvent(ev);
-    }
-
-    @Override
-    public void finish() {
-        notifyActivityCallback(null);
-        super.finish();
-    }
-
-    @Override
-    public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
-        // Do nothing
-    }
-
-    private void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
-            boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) {
-        mAllowMenuTimeout = allowMenuTimeout;
-        if (mMenuState != menuState) {
-            // Disallow touches if the menu needs to resize while showing, and we are transitioning
-            // to/from a full menu state.
-            boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow &&
-                    (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
-            mAllowTouches = !disallowTouchesUntilAnimationEnd;
-            cancelDelayedFinish();
-            updateActionViews(stackBounds);
-            if (mMenuContainerAnimator != null) {
-                mMenuContainerAnimator.cancel();
-            }
-            mMenuContainerAnimator = new AnimatorSet();
-            ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
-                    mMenuContainer.getAlpha(), 1f);
-            menuAnim.addUpdateListener(mMenuBgUpdateListener);
-            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
-                    mSettingsButton.getAlpha(), 1f);
-            ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
-                    mDismissButton.getAlpha(), 1f);
-            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
-                    mResizeHandle.getAlpha(),
-                    ENABLE_RESIZE_HANDLE && menuState == MENU_STATE_CLOSE && showResizeHandle
-                            ? 1f : 0f);
-            if (menuState == MENU_STATE_FULL) {
-                mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
-                        resizeAnim);
-            } else {
-                mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim);
-            }
-            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
-            mMenuContainerAnimator.setDuration(menuState == MENU_STATE_CLOSE
-                    ? MENU_FADE_DURATION
-                    : MENU_SLOW_FADE_DURATION);
-            if (allowMenuTimeout) {
-                mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        repostDelayedFinish(INITIAL_DISMISS_DELAY);
-                    }
-                });
-            }
-            if (withDelay) {
-                // starts the menu container animation after window expansion is completed
-                notifyMenuStateChange(menuState, resizeMenuOnShow, MESSAGE_MENU_EXPANDED);
-            } else {
-                notifyMenuStateChange(menuState, resizeMenuOnShow, MESSAGE_INVALID_TYPE);
-                mMenuContainerAnimator.start();
-            }
-        } else {
-            // If we are already visible, then just start the delayed dismiss and unregister any
-            // existing input consumers from the previous drag
-            if (allowMenuTimeout) {
-                repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
-            }
-        }
-    }
-
-    /**
-     * Different from {@link #hideMenu()}, this function does not try to finish this menu activity
-     * and instead, it fades out the controls by setting the alpha to 0 directly without menu
-     * visibility callbacks invoked.
-     */
-    private void fadeOutMenu() {
-        mMenuContainer.setAlpha(0f);
-        mSettingsButton.setAlpha(0f);
-        mDismissButton.setAlpha(0f);
-        mResizeHandle.setAlpha(0f);
-    }
-
-    private void hideMenu() {
-        hideMenu(null);
-    }
-
-    private void hideMenu(Runnable animationEndCallback) {
-        hideMenu(animationEndCallback, true /* notifyMenuVisibility */, false /* isDismissing */,
-                true /* animate */);
-    }
-
-    private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
-            boolean isDismissing, boolean animate) {
-        if (mMenuState != MENU_STATE_NONE) {
-            cancelDelayedFinish();
-            if (notifyMenuVisibility) {
-                notifyMenuStateChange(MENU_STATE_NONE, mResize, MESSAGE_INVALID_TYPE);
-            }
-            mMenuContainerAnimator = new AnimatorSet();
-            ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
-                    mMenuContainer.getAlpha(), 0f);
-            menuAnim.addUpdateListener(mMenuBgUpdateListener);
-            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
-                    mSettingsButton.getAlpha(), 0f);
-            ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
-                    mDismissButton.getAlpha(), 0f);
-            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
-                    mResizeHandle.getAlpha(), 0f);
-            mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim);
-            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
-            mMenuContainerAnimator.setDuration(animate ? MENU_FADE_DURATION : 0);
-            mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (animationFinishedRunnable != null) {
-                        animationFinishedRunnable.run();
-                    }
-
-                    if (!isDismissing) {
-                        // If we are dismissing the PiP, then don't try to pre-emptively finish the
-                        // menu activity
-                        finish();
-                    }
-                }
-            });
-            mMenuContainerAnimator.start();
-        } else {
-            // If the menu is not visible, just finish now
-            finish();
-        }
-    }
-
-    private void updateFromIntent(Intent intent) {
-        mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER);
-        if (mToControllerMessenger == null) {
-            Log.w(TAG, "Controller messenger is null. Stopping.");
-            finish();
-            return;
-        }
-        notifyActivityCallback(mMessenger);
-
-        ParceledListSlice<RemoteAction> actions = intent.getParcelableExtra(EXTRA_ACTIONS);
-        if (actions != null) {
-            mActions.clear();
-            mActions.addAll(actions.getList());
-        }
-
-        final int menuState = intent.getIntExtra(EXTRA_MENU_STATE, MENU_STATE_NONE);
-        if (menuState != MENU_STATE_NONE) {
-            Rect stackBounds = intent.getParcelableExtra(EXTRA_STACK_BOUNDS);
-            boolean allowMenuTimeout = intent.getBooleanExtra(EXTRA_ALLOW_TIMEOUT, true);
-            boolean willResizeMenu = intent.getBooleanExtra(EXTRA_WILL_RESIZE_MENU, false);
-            boolean withDelay = intent.getBooleanExtra(EXTRA_SHOW_MENU_WITH_DELAY, false);
-            boolean showResizeHandle = intent.getBooleanExtra(EXTRA_SHOW_RESIZE_HANDLE, false);
-            showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
-                    showResizeHandle);
-        }
-    }
-
-    private void setActions(Rect stackBounds, List<RemoteAction> actions) {
-        mActions.clear();
-        mActions.addAll(actions);
-        updateActionViews(stackBounds);
-    }
-
-    private void updateActionViews(Rect stackBounds) {
-        ViewGroup expandContainer = findViewById(R.id.expand_container);
-        ViewGroup actionsContainer = findViewById(R.id.actions_container);
-        actionsContainer.setOnTouchListener((v, ev) -> {
-            // Do nothing, prevent click through to parent
-            return true;
-        });
-
-        if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE) {
-            actionsContainer.setVisibility(View.INVISIBLE);
-        } else {
-            actionsContainer.setVisibility(View.VISIBLE);
-            if (mActionsGroup != null) {
-                // Ensure we have as many buttons as actions
-                final LayoutInflater inflater = LayoutInflater.from(this);
-                while (mActionsGroup.getChildCount() < mActions.size()) {
-                    final ImageButton actionView = (ImageButton) inflater.inflate(
-                            R.layout.pip_menu_action, mActionsGroup, false);
-                    mActionsGroup.addView(actionView);
-                }
-
-                // Update the visibility of all views
-                for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
-                    mActionsGroup.getChildAt(i).setVisibility(i < mActions.size()
-                            ? View.VISIBLE
-                            : View.GONE);
-                }
-
-                // Recreate the layout
-                final boolean isLandscapePip = stackBounds != null &&
-                        (stackBounds.width() > stackBounds.height());
-                for (int i = 0; i < mActions.size(); i++) {
-                    final RemoteAction action = mActions.get(i);
-                    final ImageButton actionView = (ImageButton) mActionsGroup.getChildAt(i);
-
-                    // TODO: Check if the action drawable has changed before we reload it
-                    action.getIcon().loadDrawableAsync(this, d -> {
-                        d.setTint(Color.WHITE);
-                        actionView.setImageDrawable(d);
-                    }, mHandler);
-                    actionView.setContentDescription(action.getContentDescription());
-                    if (action.isEnabled()) {
-                        actionView.setOnClickListener(v -> {
-                            mHandler.post(() -> {
-                                try {
-                                    action.getActionIntent().send();
-                                } catch (CanceledException e) {
-                                    Log.w(TAG, "Failed to send action", e);
-                                }
-                            });
-                        });
-                    }
-                    actionView.setEnabled(action.isEnabled());
-                    actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
-
-                    // Update the margin between actions
-                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
-                            actionView.getLayoutParams();
-                    lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0;
-                }
-            }
-
-            // Update the expand container margin to adjust the center of the expand button to
-            // account for the existence of the action container
-            FrameLayout.LayoutParams expandedLp =
-                    (FrameLayout.LayoutParams) expandContainer.getLayoutParams();
-            expandedLp.topMargin = getResources().getDimensionPixelSize(
-                    R.dimen.pip_action_padding);
-            expandedLp.bottomMargin = getResources().getDimensionPixelSize(
-                    R.dimen.pip_expand_container_edge_margin);
-            expandContainer.requestLayout();
-        }
-    }
-
-    private void updateDismissFraction(float fraction) {
-        int alpha;
-        final float menuAlpha = 1 - fraction;
-        if (mMenuState == MENU_STATE_FULL) {
-            mMenuContainer.setAlpha(menuAlpha);
-            mSettingsButton.setAlpha(menuAlpha);
-            mDismissButton.setAlpha(menuAlpha);
-            final float interpolatedAlpha =
-                    MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
-            alpha = (int) (interpolatedAlpha * 255);
-        } else {
-            if (mMenuState == MENU_STATE_CLOSE) {
-                mDismissButton.setAlpha(menuAlpha);
-            }
-            alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
-        }
-        mBackgroundDrawable.setAlpha(alpha);
-    }
-
-    private void notifyMenuStateChange(int menuState, boolean resize, int callbackWhat) {
-        mMenuState = menuState;
-        mResize = resize;
-        Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_MENU_STATE_CHANGED;
-        m.arg1 = menuState;
-        m.arg2 = resize ? 1 : 0;
-        if (callbackWhat != MESSAGE_INVALID_TYPE) {
-            // This message could be sent across processes when in secondary user.
-            // Make the receiver end sending back via our own Messenger
-            m.replyTo = mMessenger;
-            final Bundle data = new Bundle(1);
-            data.putInt(PipMenuActivityController.EXTRA_MESSAGE_CALLBACK_WHAT, callbackWhat);
-            m.obj = data;
-        }
-        sendMessage(m, "Could not notify controller of PIP menu visibility");
-    }
-
-    private void expandPip() {
-        // Do not notify menu visibility when hiding the menu, the controller will do this when it
-        // handles the message
-        hideMenu(() -> {
-            sendEmptyMessage(PipMenuActivityController.MESSAGE_EXPAND_PIP,
-                    "Could not notify controller to expand PIP");
-        }, false /* notifyMenuVisibility */, false /* isDismissing */, true /* animate */);
-    }
-
-    private void dismissPip() {
-        // Since tapping on the close-button invokes a double-tap wait callback in PipTouchHandler,
-        // we want to disable animating the fadeout animation of the buttons in order to call on
-        // PipTouchHandler#onPipDismiss fast enough.
-        final boolean animate = mMenuState != MENU_STATE_CLOSE;
-        // Do not notify menu visibility when hiding the menu, the controller will do this when it
-        // handles the message
-        hideMenu(() -> {
-            sendEmptyMessage(PipMenuActivityController.MESSAGE_DISMISS_PIP,
-                    "Could not notify controller to dismiss PIP");
-        }, false /* notifyMenuVisibility */, true /* isDismissing */, animate);
-    }
-
-    private void showSettings() {
-        final Pair<ComponentName, Integer> topPipActivityInfo =
-                PipUtils.getTopPipActivity(this, ActivityManager.getService());
-        if (topPipActivityInfo.first != null) {
-            final UserHandle user = UserHandle.of(topPipActivityInfo.second);
-            final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
-                    Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
-            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
-            settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-            startActivity(settingsIntent);
-        }
-    }
-
-    private void notifyActivityCallback(Messenger callback) {
-        Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
-        m.replyTo = callback;
-        m.arg1 = mResize ?  1 : 0;
-        sendMessage(m, "Could not notify controller of activity finished");
-    }
-
-    private void sendEmptyMessage(int what, String errorMsg) {
-        Message m = Message.obtain();
-        m.what = what;
-        sendMessage(m, errorMsg);
-    }
-
-    private void sendMessage(Message m, String errorMsg) {
-        if (mToControllerMessenger == null) {
-            return;
-        }
-        try {
-            mToControllerMessenger.send(m);
-        } catch (RemoteException e) {
-            Log.e(TAG, errorMsg, e);
-        }
-    }
-
-    private void cancelDelayedFinish() {
-        mHandler.removeCallbacks(mFinishRunnable);
-    }
-
-    private void repostDelayedFinish(int delay) {
-        int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
-                FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS);
-        mHandler.removeCallbacks(mFinishRunnable);
-        mHandler.postDelayed(mFinishRunnable, recommendedTimeout);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 383f6b3..873ba26 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -19,27 +19,20 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
-import android.annotation.Nullable;
 import android.app.ActivityManager.StackInfo;
-import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.RemoteAction;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.os.Bundle;
 import android.os.Debug;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
 import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.WindowManager;
 
+import com.android.systemui.pip.PipTaskOrganizer;
 import com.android.systemui.pip.phone.PipMediaController.ActionListener;
 import com.android.systemui.shared.system.InputConsumerController;
 
@@ -58,30 +51,10 @@
     private static final String TAG = "PipMenuActController";
     private static final boolean DEBUG = false;
 
-    public static final String EXTRA_CONTROLLER_MESSENGER = "messenger";
-    public static final String EXTRA_ACTIONS = "actions";
-    public static final String EXTRA_STACK_BOUNDS = "stack_bounds";
-    public static final String EXTRA_ALLOW_TIMEOUT = "allow_timeout";
-    public static final String EXTRA_WILL_RESIZE_MENU = "resize_menu_on_show";
-    public static final String EXTRA_DISMISS_FRACTION = "dismiss_fraction";
-    public static final String EXTRA_MENU_STATE = "menu_state";
-    public static final String EXTRA_SHOW_MENU_WITH_DELAY = "show_menu_with_delay";
-    public static final String EXTRA_SHOW_RESIZE_HANDLE = "show_resize_handle";
-    public static final String EXTRA_MESSAGE_CALLBACK_WHAT = "message_callback_what";
-
-    public static final int MESSAGE_MENU_STATE_CHANGED = 100;
-    public static final int MESSAGE_EXPAND_PIP = 101;
-    public static final int MESSAGE_DISMISS_PIP = 103;
-    public static final int MESSAGE_UPDATE_ACTIVITY_CALLBACK = 104;
-    public static final int MESSAGE_SHOW_MENU = 107;
-
     public static final int MENU_STATE_NONE = 0;
     public static final int MENU_STATE_CLOSE = 1;
     public static final int MENU_STATE_FULL = 2;
 
-    // The duration to wait before we consider the start activity as having timed out
-    private static final long START_ACTIVITY_REQUEST_TIMEOUT_MS = 300;
-
     /**
      * A listener interface to receive notification on changes in PIP.
      */
@@ -110,9 +83,8 @@
         void onPipShowMenu();
     }
 
-    /** TODO(b/150319024): PipMenuActivity will move to a Window */
-    private Class<?> mPipMenuActivityClass;
     private Context mContext;
+    private PipTaskOrganizer mPipTaskOrganizer;
     private PipMediaController mMediaController;
     private InputConsumerController mInputConsumerController;
 
@@ -121,63 +93,7 @@
     private ParceledListSlice<RemoteAction> mMediaActions;
     private int mMenuState;
 
-    // The dismiss fraction update is sent frequently, so use a temporary bundle for the message
-    private Bundle mTmpDismissFractionData = new Bundle();
-
-    private Runnable mOnAnimationEndRunnable;
-    private boolean mStartActivityRequested;
-    private long mStartActivityRequestedTime;
-    private Messenger mToActivityMessenger;
-    private Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MESSAGE_MENU_STATE_CHANGED: {
-                    final int menuState = msg.arg1;
-                    final boolean resize = msg.arg2 != 0;
-                    onMenuStateChanged(menuState, resize,
-                            getMenuStateChangeFinishedCallback(msg.replyTo, (Bundle) msg.obj));
-                    break;
-                }
-                case MESSAGE_EXPAND_PIP: {
-                    mListeners.forEach(Listener::onPipExpand);
-                    break;
-                }
-                case MESSAGE_DISMISS_PIP: {
-                    mListeners.forEach(Listener::onPipDismiss);
-                    break;
-                }
-                case MESSAGE_SHOW_MENU: {
-                    mListeners.forEach(Listener::onPipShowMenu);
-                    break;
-                }
-                case MESSAGE_UPDATE_ACTIVITY_CALLBACK: {
-                    mToActivityMessenger = msg.replyTo;
-                    setStartActivityRequested(false);
-                    if (mOnAnimationEndRunnable != null) {
-                        mOnAnimationEndRunnable.run();
-                        mOnAnimationEndRunnable = null;
-                    }
-                    // Mark the menu as invisible once the activity finishes as well
-                    if (mToActivityMessenger == null) {
-                        final boolean resize = msg.arg1 != 0;
-                        onMenuStateChanged(MENU_STATE_NONE, resize, null /* callback */);
-                    }
-                    break;
-                }
-            }
-        }
-    };
-    private Messenger mMessenger = new Messenger(mHandler);
-
-    private Runnable mStartActivityRequestedTimeoutRunnable = () -> {
-        setStartActivityRequested(false);
-        if (mOnAnimationEndRunnable != null) {
-            mOnAnimationEndRunnable.run();
-            mOnAnimationEndRunnable = null;
-        }
-        Log.e(TAG, "Expected start menu activity request timed out");
-    };
+    private PipMenuView mPipMenuView;
 
     private ActionListener mMediaActionListener = new ActionListener() {
         @Override
@@ -187,39 +103,40 @@
         }
     };
 
-    public PipMenuActivityController(Context context, Class<?> pipMenuActivityClass,
-            PipMediaController mediaController, InputConsumerController inputConsumerController
+    public PipMenuActivityController(Context context,
+            PipMediaController mediaController, InputConsumerController inputConsumerController,
+            PipTaskOrganizer pipTaskOrganizer
     ) {
         mContext = context;
         mMediaController = mediaController;
         mInputConsumerController = inputConsumerController;
-        mPipMenuActivityClass = pipMenuActivityClass;
+        mPipTaskOrganizer = pipTaskOrganizer;
     }
 
-    public boolean isMenuActivityVisible() {
-        return mToActivityMessenger != null;
+    public boolean isMenuVisible() {
+        return mPipMenuView != null && mMenuState != MENU_STATE_NONE;
     }
 
     public void onActivityPinned() {
+        if (mPipMenuView == null) {
+            WindowManager.LayoutParams lp =
+                    getPipMenuLayoutParams(0, 0);
+            mPipMenuView = new PipMenuView(mContext, this);
+            mPipTaskOrganizer.attachPipMenuViewHost(mPipMenuView, lp);
+        }
         mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
     }
 
     public void onActivityUnpinned() {
         hideMenu();
         mInputConsumerController.unregisterInputConsumer();
-        setStartActivityRequested(false);
+        mPipTaskOrganizer.detachPipMenuViewHost();
+        mPipMenuView = null;
     }
 
     public void onPinnedStackAnimationEnded() {
-        // Note: Only active menu activities care about this event
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_ANIMATION_ENDED;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu pinned animation ended", e);
-            }
+        if (isMenuVisible()) {
+            mPipMenuView.onPipAnimationEnded();
         }
     }
 
@@ -236,27 +153,13 @@
      * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
      */
     public void setDismissFraction(float fraction) {
+        final boolean isMenuVisible = isMenuVisible();
         if (DEBUG) {
-            Log.d(TAG, "setDismissFraction() hasActivity=" + (mToActivityMessenger != null)
+            Log.d(TAG, "setDismissFraction() isMenuVisible=" + isMenuVisible
                     + " fraction=" + fraction);
         }
-        if (mToActivityMessenger != null) {
-            mTmpDismissFractionData.clear();
-            mTmpDismissFractionData.putFloat(EXTRA_DISMISS_FRACTION, fraction);
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_UPDATE_DISMISS_FRACTION;
-            m.obj = mTmpDismissFractionData;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu to update dismiss fraction", e);
-            }
-        } else if (!mStartActivityRequested || isStartActivityRequestedElapsed()) {
-            // If we haven't requested the start activity, or if it previously took too long to
-            // start, then start it
-            startMenuActivity(MENU_STATE_NONE, null /* stackBounds */,
-                    false /* allowMenuTimeout */, false /* resizeMenuOnShow */,
-                    false /* withDelay */, false /* showResizeHandle */);
+        if (isMenuVisible) {
+            mPipMenuView.updateDismissFraction(fraction);
         }
     }
 
@@ -282,27 +185,11 @@
                 false /* withDelay */, showResizeHandle);
     }
 
-    private Runnable getMenuStateChangeFinishedCallback(@Nullable Messenger replyTo,
-            @Nullable Bundle data) {
-        if (replyTo == null || data == null) {
-            return null;
-        }
-        return () -> {
-            try {
-                final Message m = Message.obtain();
-                m.what = data.getInt(EXTRA_MESSAGE_CALLBACK_WHAT);
-                replyTo.send(m);
-            } catch (RemoteException e) {
-                // ignored
-            }
-        };
-    }
-
     private void showMenuInternal(int menuState, Rect stackBounds, boolean allowMenuTimeout,
             boolean willResizeMenu, boolean withDelay, boolean showResizeHandle) {
         if (DEBUG) {
             Log.d(TAG, "showMenu() state=" + menuState
-                    + " hasActivity=" + (mToActivityMessenger != null)
+                    + " isMenuVisible=" + isMenuVisible()
                     + " allowMenuTimeout=" + allowMenuTimeout
                     + " willResizeMenu=" + willResizeMenu
                     + " withDelay=" + withDelay
@@ -310,64 +197,34 @@
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
 
-        if (mToActivityMessenger != null) {
-            Bundle data = new Bundle();
-            data.putInt(EXTRA_MENU_STATE, menuState);
-            if (stackBounds != null) {
-                data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
-            }
-            data.putBoolean(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout);
-            data.putBoolean(EXTRA_WILL_RESIZE_MENU, willResizeMenu);
-            data.putBoolean(EXTRA_SHOW_MENU_WITH_DELAY, withDelay);
-            data.putBoolean(EXTRA_SHOW_RESIZE_HANDLE, showResizeHandle);
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_SHOW_MENU;
-            m.obj = data;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu to show", e);
-            }
-        } else if (!mStartActivityRequested || isStartActivityRequestedElapsed()) {
-            // If we haven't requested the start activity, or if it previously took too long to
-            // start, then start it
-            startMenuActivity(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
-                    showResizeHandle);
+        if (mPipMenuView == null) {
+            Log.e(TAG, "PipMenu has not been attached yet.");
+            return;
         }
+        mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
+                showResizeHandle);
     }
 
     /**
      * Pokes the menu, indicating that the user is interacting with it.
      */
     public void pokeMenu() {
+        final boolean isMenuVisible = isMenuVisible();
         if (DEBUG) {
-            Log.d(TAG, "pokeMenu() hasActivity=" + (mToActivityMessenger != null));
+            Log.d(TAG, "pokeMenu() isMenuVisible=" + isMenuVisible);
         }
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_POKE_MENU;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify poke menu", e);
-            }
+        if (isMenuVisible) {
+            mPipMenuView.pokeMenu();
         }
     }
 
     private void fadeOutMenu() {
+        final boolean isMenuVisible = isMenuVisible();
         if (DEBUG) {
-            Log.d(TAG, "fadeOutMenu() state=" + mMenuState
-                    + " hasActivity=" + (mToActivityMessenger != null)
-                    + " callers=\n" + Debug.getCallers(5, "    "));
+            Log.d(TAG, "fadeOutMenu() isMenuVisible=" + isMenuVisible);
         }
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_FADE_OUT_MENU;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu to fade out", e);
-            }
+        if (isMenuVisible) {
+            mPipMenuView.fadeOutMenu();
         }
     }
 
@@ -375,19 +232,14 @@
      * Hides the menu activity.
      */
     public void hideMenu() {
+        final boolean isMenuVisible = isMenuVisible();
         if (DEBUG) {
             Log.d(TAG, "hideMenu() state=" + mMenuState
-                    + " hasActivity=" + (mToActivityMessenger != null)
+                    + " isMenuVisible=" + isMenuVisible
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_HIDE_MENU;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu to hide", e);
-            }
+        if (isMenuVisible) {
+            mPipMenuView.hideMenu();
         }
     }
 
@@ -395,29 +247,11 @@
      * Hides the menu activity.
      */
     public void hideMenu(Runnable onStartCallback, Runnable onEndCallback) {
-        if (mStartActivityRequested) {
-            // If the menu has been start-requested, but not actually started, then we defer the
-            // trigger callback until the menu has started and called back to the controller.
-            mOnAnimationEndRunnable = onEndCallback;
-            onStartCallback.run();
-
-            // Fallback for b/63752800, we have started the PipMenuActivity but it has not made any
-            // callbacks. Don't continue to wait for the menu to show past some timeout.
-            mHandler.removeCallbacks(mStartActivityRequestedTimeoutRunnable);
-            mHandler.postDelayed(mStartActivityRequestedTimeoutRunnable,
-                    START_ACTIVITY_REQUEST_TIMEOUT_MS);
-        } else if (mMenuState != MENU_STATE_NONE && mToActivityMessenger != null) {
+        if (isMenuVisible()) {
             // If the menu is visible in either the closed or full state, then hide the menu and
             // trigger the animation trigger afterwards
             onStartCallback.run();
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_HIDE_MENU;
-            m.obj = onEndCallback;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify hide menu", e);
-            }
+            mPipMenuView.hideMenu(onEndCallback);
         }
     }
 
@@ -438,6 +272,18 @@
         updateMenuActions();
     }
 
+    void onPipExpand() {
+        mListeners.forEach(Listener::onPipExpand);
+    }
+
+    void onPipDismiss() {
+        mListeners.forEach(Listener::onPipDismiss);
+    }
+
+    void onPipShowMenu() {
+        mListeners.forEach(Listener::onPipShowMenu);
+    }
+
     /**
      * @return the best set of actions to show in the PiP menu.
      */
@@ -449,47 +295,20 @@
     }
 
     /**
-     * Starts the menu activity on the top task of the pinned stack.
+     * Returns a default LayoutParams for the PIP Menu.
+     * @param width the PIP stack width.
+     * @param height the PIP stack height.
      */
-    private void startMenuActivity(int menuState, Rect stackBounds, boolean allowMenuTimeout,
-            boolean willResizeMenu, boolean withDelay, boolean showResizeHandle) {
-        try {
-            StackInfo pinnedStackInfo = ActivityTaskManager.getService().getStackInfo(
-                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
-            if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
-                    pinnedStackInfo.taskIds.length > 0) {
-                Intent intent = new Intent(mContext, mPipMenuActivityClass);
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger);
-                intent.putExtra(EXTRA_ACTIONS, resolveMenuActions());
-                if (stackBounds != null) {
-                    intent.putExtra(EXTRA_STACK_BOUNDS, stackBounds);
-                }
-                intent.putExtra(EXTRA_MENU_STATE, menuState);
-                intent.putExtra(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout);
-                intent.putExtra(EXTRA_WILL_RESIZE_MENU, willResizeMenu);
-                intent.putExtra(EXTRA_SHOW_MENU_WITH_DELAY, withDelay);
-                intent.putExtra(EXTRA_SHOW_RESIZE_HANDLE, showResizeHandle);
-                ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
-                options.setLaunchTaskId(
-                        pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
-                options.setTaskOverlay(true, true /* canResume */);
-                mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
-                setStartActivityRequested(true);
-            } else {
-                Log.e(TAG, "No PIP tasks found");
-            }
-        } catch (RemoteException e) {
-            setStartActivityRequested(false);
-            Log.e(TAG, "Error showing PIP menu activity", e);
-        }
+    public static WindowManager.LayoutParams getPipMenuLayoutParams(int width, int height) {
+        return new WindowManager.LayoutParams(width, height,
+                WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSLUCENT);
     }
 
     /**
-     * Updates the PiP menu activity with the best set of actions provided.
+     * Updates the PiP menu with the best set of actions provided.
      */
     private void updateMenuActions() {
-        if (mToActivityMessenger != null) {
+        if (isMenuVisible()) {
             // Fetch the pinned stack bounds
             Rect stackBounds = null;
             try {
@@ -499,20 +318,10 @@
                     stackBounds = pinnedStackInfo.bounds;
                 }
             } catch (RemoteException e) {
-                Log.e(TAG, "Error showing PIP menu activity", e);
+                Log.e(TAG, "Error showing PIP menu", e);
             }
 
-            Bundle data = new Bundle();
-            data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
-            data.putParcelable(EXTRA_ACTIONS, resolveMenuActions());
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_UPDATE_ACTIONS;
-            m.obj = data;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu activity to update actions", e);
-            }
+            mPipMenuView.setActions(stackBounds, resolveMenuActions().getList());
         }
     }
 
@@ -524,17 +333,9 @@
     }
 
     /**
-     * @return whether the time of the activity request has exceeded the timeout.
-     */
-    private boolean isStartActivityRequestedElapsed() {
-        return (SystemClock.uptimeMillis() - mStartActivityRequestedTime)
-                >= START_ACTIVITY_REQUEST_TIMEOUT_MS;
-    }
-
-    /**
      * Handles changes in menu visibility.
      */
-    private void onMenuStateChanged(int menuState, boolean resize, Runnable callback) {
+    void onMenuStateChanged(int menuState, boolean resize, Runnable callback) {
         if (DEBUG) {
             Log.d(TAG, "onMenuStateChanged() mMenuState=" + mMenuState
                     + " menuState=" + menuState + " resize=" + resize
@@ -556,25 +357,14 @@
         mMenuState = menuState;
     }
 
-    private void setStartActivityRequested(boolean requested) {
-        mHandler.removeCallbacks(mStartActivityRequestedTimeoutRunnable);
-        mStartActivityRequested = requested;
-        mStartActivityRequestedTime = requested ? SystemClock.uptimeMillis() : 0;
-    }
-
     /**
      * Handles a pointer event sent from pip input consumer.
      */
     void handlePointerEvent(MotionEvent ev) {
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_POINTER_EVENT;
-            m.obj = ev;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not dispatch touch event", e);
-            }
+        if (ev.isTouchEvent()) {
+            mPipMenuView.dispatchTouchEvent(ev);
+        } else {
+            mPipMenuView.dispatchGenericMotionEvent(ev);
         }
     }
 
@@ -582,15 +372,14 @@
      * Tell the PIP Menu to recalculate its layout given its current position on the display.
      */
     public void updateMenuLayout(Rect bounds) {
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_UPDATE_MENU_LAYOUT;
-            m.obj = bounds;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not dispatch touch event", e);
-            }
+        final boolean isMenuVisible = isMenuVisible();
+        if (DEBUG) {
+            Log.d(TAG, "updateMenuLayout() state=" + mMenuState
+                    + " isMenuVisible=" + isMenuVisible
+                    + " callers=\n" + Debug.getCallers(5, "    "));
+        }
+        if (isMenuVisible) {
+            mPipMenuView.updateMenuLayout(bounds);
         }
     }
 
@@ -598,9 +387,7 @@
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
         pw.println(innerPrefix + "mMenuState=" + mMenuState);
-        pw.println(innerPrefix + "mToActivityMessenger=" + mToActivityMessenger);
+        pw.println(innerPrefix + "mPipMenuView=" + mPipMenuView);
         pw.println(innerPrefix + "mListeners=" + mListeners.size());
-        pw.println(innerPrefix + "mStartActivityRequested=" + mStartActivityRequested);
-        pw.println(innerPrefix + "mStartActivityRequestedTime=" + mStartActivityRequestedTime);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java
new file mode 100644
index 0000000..993bfe0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.pip.phone;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
+
+import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
+import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
+import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteAction;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Translucent window that gets started on top of a task in PIP to allow the user to control it.
+ */
+public class PipMenuView extends FrameLayout {
+
+    private static final String TAG = "PipMenuView";
+
+    private static final int MESSAGE_INVALID_TYPE = -1;
+    public static final int MESSAGE_MENU_EXPANDED = 8;
+
+    private static final int INITIAL_DISMISS_DELAY = 3500;
+    private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
+    private static final long MENU_FADE_DURATION = 125;
+    private static final long MENU_SLOW_FADE_DURATION = 175;
+    private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
+
+    private static final float MENU_BACKGROUND_ALPHA = 0.3f;
+    private static final float DISMISS_BACKGROUND_ALPHA = 0.6f;
+
+    private static final float DISABLED_ACTION_ALPHA = 0.54f;
+
+    private static final boolean ENABLE_RESIZE_HANDLE = false;
+
+    private int mMenuState;
+    private boolean mResize = true;
+    private boolean mAllowMenuTimeout = true;
+    private boolean mAllowTouches = true;
+
+    private final List<RemoteAction> mActions = new ArrayList<>();
+
+    private AccessibilityManager mAccessibilityManager;
+    private Drawable mBackgroundDrawable;
+    private View mMenuContainer;
+    private LinearLayout mActionsGroup;
+    private int mBetweenActionPaddingLand;
+
+    private AnimatorSet mMenuContainerAnimator;
+    private PipMenuActivityController mController;
+
+    private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    final float alpha = (float) animation.getAnimatedValue();
+                    mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA * alpha * 255));
+                }
+            };
+
+    private Handler mHandler = new Handler();
+
+    private final Runnable mHideMenuRunnable = this::hideMenu;
+
+    protected View mViewRoot;
+    protected View mSettingsButton;
+    protected View mDismissButton;
+    protected View mResizeHandle;
+    protected View mTopEndContainer;
+    protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
+
+    public PipMenuView(Context context, PipMenuActivityController controller) {
+        super(context, null, 0);
+        mContext = context;
+        mController = controller;
+
+        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+        inflate(context, R.layout.pip_menu, this);
+
+        mBackgroundDrawable = new ColorDrawable(Color.BLACK);
+        mBackgroundDrawable.setAlpha(0);
+        mViewRoot = findViewById(R.id.background);
+        mViewRoot.setBackground(mBackgroundDrawable);
+        mMenuContainer = findViewById(R.id.menu_container);
+        mMenuContainer.setAlpha(0);
+        mTopEndContainer = findViewById(R.id.top_end_container);
+        mSettingsButton = findViewById(R.id.settings);
+        mSettingsButton.setAlpha(0);
+        mSettingsButton.setOnClickListener((v) -> {
+            if (v.getAlpha() != 0) {
+                showSettings();
+            }
+        });
+        mDismissButton = findViewById(R.id.dismiss);
+        mDismissButton.setAlpha(0);
+        mDismissButton.setOnClickListener(v -> dismissPip());
+        findViewById(R.id.expand_button).setOnClickListener(v -> {
+            if (mMenuContainer.getAlpha() != 0) {
+                expandPip();
+            }
+        });
+        // TODO (b/161710689): Remove this once focusability for Windowless window is working
+        findViewById(R.id.expand_button).setFocusable(false);
+        mDismissButton.setFocusable(false);
+        mSettingsButton.setFocusable(false);
+
+        mResizeHandle = findViewById(R.id.resize_handle);
+        mResizeHandle.setAlpha(0);
+        mActionsGroup = findViewById(R.id.actions_group);
+        mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
+                R.dimen.pip_between_action_padding_land);
+        mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
+        mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
+                mResizeHandle, mSettingsButton, mDismissButton);
+
+        initAccessibility();
+    }
+
+    private void initAccessibility() {
+        this.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+            @Override
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                String label = getResources().getString(R.string.pip_menu_title);
+                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, label));
+            }
+
+            @Override
+            public boolean performAccessibilityAction(View host, int action, Bundle args) {
+                if (action == ACTION_CLICK && mMenuState == MENU_STATE_CLOSE) {
+                    mController.onPipShowMenu();
+                }
+                return super.performAccessibilityAction(host, action, args);
+            }
+        });
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
+            hideMenu();
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (!mAllowTouches) {
+            return false;
+        }
+
+        if (mAllowMenuTimeout) {
+            repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+        }
+
+        return super.dispatchTouchEvent(ev);
+    }
+
+    @Override
+    public boolean dispatchGenericMotionEvent(MotionEvent event) {
+        if (mAllowMenuTimeout) {
+            repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+        }
+
+        return super.dispatchGenericMotionEvent(event);
+    }
+
+    void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
+            boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) {
+        mAllowMenuTimeout = allowMenuTimeout;
+        if (mMenuState != menuState) {
+            // Disallow touches if the menu needs to resize while showing, and we are transitioning
+            // to/from a full menu state.
+            boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow
+                    && (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
+            mAllowTouches = !disallowTouchesUntilAnimationEnd;
+            cancelDelayedHide();
+            updateActionViews(stackBounds);
+            if (mMenuContainerAnimator != null) {
+                mMenuContainerAnimator.cancel();
+            }
+            mMenuContainerAnimator = new AnimatorSet();
+            ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
+                    mMenuContainer.getAlpha(), 1f);
+            menuAnim.addUpdateListener(mMenuBgUpdateListener);
+            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+                    mSettingsButton.getAlpha(), 1f);
+            ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
+                    mDismissButton.getAlpha(), 1f);
+            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
+                    mResizeHandle.getAlpha(),
+                    ENABLE_RESIZE_HANDLE && menuState == MENU_STATE_CLOSE && showResizeHandle
+                            ? 1f : 0f);
+            if (menuState == MENU_STATE_FULL) {
+                mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
+                        resizeAnim);
+            } else {
+                mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim);
+            }
+            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
+            mMenuContainerAnimator.setDuration(menuState == MENU_STATE_CLOSE
+                    ? MENU_FADE_DURATION
+                    : MENU_SLOW_FADE_DURATION);
+            if (allowMenuTimeout) {
+                mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        repostDelayedHide(INITIAL_DISMISS_DELAY);
+                    }
+                });
+            }
+            if (withDelay) {
+                // starts the menu container animation after window expansion is completed
+                notifyMenuStateChange(menuState, resizeMenuOnShow, () -> {
+                    if (mMenuContainerAnimator == null) {
+                        return;
+                    }
+                    mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY);
+                    mMenuContainerAnimator.start();
+                });
+            } else {
+                notifyMenuStateChange(menuState, resizeMenuOnShow, null);
+                mMenuContainerAnimator.start();
+            }
+        } else {
+            // If we are already visible, then just start the delayed dismiss and unregister any
+            // existing input consumers from the previous drag
+            if (allowMenuTimeout) {
+                repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+            }
+        }
+    }
+
+    /**
+     * Different from {@link #hideMenu()}, this function does not try to finish this menu activity
+     * and instead, it fades out the controls by setting the alpha to 0 directly without menu
+     * visibility callbacks invoked.
+     */
+    void fadeOutMenu() {
+        mMenuContainer.setAlpha(0f);
+        mSettingsButton.setAlpha(0f);
+        mDismissButton.setAlpha(0f);
+        mResizeHandle.setAlpha(0f);
+    }
+
+    void pokeMenu() {
+        cancelDelayedHide();
+    }
+
+    void onPipAnimationEnded() {
+        mAllowTouches = true;
+    }
+
+    void updateMenuLayout(Rect bounds) {
+        mPipMenuIconsAlgorithm.onBoundsChanged(bounds);
+    }
+
+    void hideMenu() {
+        hideMenu(null);
+    }
+
+    void hideMenu(Runnable animationEndCallback) {
+        hideMenu(animationEndCallback, true /* notifyMenuVisibility */, false);
+    }
+
+    private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
+            boolean animate) {
+        if (mMenuState != MENU_STATE_NONE) {
+            cancelDelayedHide();
+            if (notifyMenuVisibility) {
+                notifyMenuStateChange(MENU_STATE_NONE, mResize, null);
+            }
+            mMenuContainerAnimator = new AnimatorSet();
+            ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
+                    mMenuContainer.getAlpha(), 0f);
+            menuAnim.addUpdateListener(mMenuBgUpdateListener);
+            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+                    mSettingsButton.getAlpha(), 0f);
+            ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
+                    mDismissButton.getAlpha(), 0f);
+            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
+                    mResizeHandle.getAlpha(), 0f);
+            mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim);
+            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+            mMenuContainerAnimator.setDuration(animate ? MENU_FADE_DURATION : 0);
+            mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (animationFinishedRunnable != null) {
+                        animationFinishedRunnable.run();
+                    }
+                }
+            });
+            mMenuContainerAnimator.start();
+        }
+    }
+
+    void setActions(Rect stackBounds, List<RemoteAction> actions) {
+        mActions.clear();
+        mActions.addAll(actions);
+        updateActionViews(stackBounds);
+    }
+
+    private void updateActionViews(Rect stackBounds) {
+        ViewGroup expandContainer = findViewById(R.id.expand_container);
+        ViewGroup actionsContainer = findViewById(R.id.actions_container);
+        actionsContainer.setOnTouchListener((v, ev) -> {
+            // Do nothing, prevent click through to parent
+            return true;
+        });
+
+        if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE) {
+            actionsContainer.setVisibility(View.INVISIBLE);
+        } else {
+            actionsContainer.setVisibility(View.VISIBLE);
+            if (mActionsGroup != null) {
+                // Ensure we have as many buttons as actions
+                final LayoutInflater inflater = LayoutInflater.from(mContext);
+                while (mActionsGroup.getChildCount() < mActions.size()) {
+                    final ImageButton actionView = (ImageButton) inflater.inflate(
+                            R.layout.pip_menu_action, mActionsGroup, false);
+                    mActionsGroup.addView(actionView);
+                }
+
+                // Update the visibility of all views
+                for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+                    mActionsGroup.getChildAt(i).setVisibility(i < mActions.size()
+                            ? View.VISIBLE
+                            : View.GONE);
+                }
+
+                // Recreate the layout
+                final boolean isLandscapePip = stackBounds != null
+                        && (stackBounds.width() > stackBounds.height());
+                for (int i = 0; i < mActions.size(); i++) {
+                    final RemoteAction action = mActions.get(i);
+                    final ImageButton actionView = (ImageButton) mActionsGroup.getChildAt(i);
+
+                    // TODO: Check if the action drawable has changed before we reload it
+                    action.getIcon().loadDrawableAsync(mContext, d -> {
+                        d.setTint(Color.WHITE);
+                        actionView.setImageDrawable(d);
+                    }, mHandler);
+                    actionView.setContentDescription(action.getContentDescription());
+                    if (action.isEnabled()) {
+                        actionView.setOnClickListener(v -> {
+                            mHandler.post(() -> {
+                                try {
+                                    action.getActionIntent().send();
+                                } catch (CanceledException e) {
+                                    Log.w(TAG, "Failed to send action", e);
+                                }
+                            });
+                        });
+                    }
+                    actionView.setEnabled(action.isEnabled());
+                    actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
+
+                    // Update the margin between actions
+                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+                            actionView.getLayoutParams();
+                    lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0;
+                }
+            }
+
+            // Update the expand container margin to adjust the center of the expand button to
+            // account for the existence of the action container
+            FrameLayout.LayoutParams expandedLp =
+                    (FrameLayout.LayoutParams) expandContainer.getLayoutParams();
+            expandedLp.topMargin = getResources().getDimensionPixelSize(
+                    R.dimen.pip_action_padding);
+            expandedLp.bottomMargin = getResources().getDimensionPixelSize(
+                    R.dimen.pip_expand_container_edge_margin);
+            expandContainer.requestLayout();
+        }
+    }
+
+    void updateDismissFraction(float fraction) {
+        int alpha;
+        final float menuAlpha = 1 - fraction;
+        if (mMenuState == MENU_STATE_FULL) {
+            mMenuContainer.setAlpha(menuAlpha);
+            mSettingsButton.setAlpha(menuAlpha);
+            mDismissButton.setAlpha(menuAlpha);
+            final float interpolatedAlpha =
+                    MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
+            alpha = (int) (interpolatedAlpha * 255);
+        } else {
+            if (mMenuState == MENU_STATE_CLOSE) {
+                mDismissButton.setAlpha(menuAlpha);
+            }
+            alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
+        }
+        mBackgroundDrawable.setAlpha(alpha);
+    }
+
+    private void notifyMenuStateChange(int menuState, boolean resize, Runnable callback) {
+        mMenuState = menuState;
+        mController.onMenuStateChanged(menuState, resize, callback);
+    }
+
+    private void expandPip() {
+        // Do not notify menu visibility when hiding the menu, the controller will do this when it
+        // handles the message
+        hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */);
+    }
+
+    private void dismissPip() {
+        // Since tapping on the close-button invokes a double-tap wait callback in PipTouchHandler,
+        // we want to disable animating the fadeout animation of the buttons in order to call on
+        // PipTouchHandler#onPipDismiss fast enough.
+        final boolean animate = mMenuState != MENU_STATE_CLOSE;
+        // Do not notify menu visibility when hiding the menu, the controller will do this when it
+        // handles the message
+        hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate);
+    }
+
+    private void showSettings() {
+        final Pair<ComponentName, Integer> topPipActivityInfo =
+                PipUtils.getTopPipActivity(mContext, ActivityManager.getService());
+        if (topPipActivityInfo.first != null) {
+            final UserHandle user = UserHandle.of(topPipActivityInfo.second);
+            final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+                    Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
+            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
+            settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+            mContext.startActivity(settingsIntent);
+        }
+    }
+
+    private void cancelDelayedHide() {
+        mHandler.removeCallbacks(mHideMenuRunnable);
+    }
+
+    private void repostDelayedHide(int delay) {
+        int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
+                FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS);
+        mHandler.removeCallbacks(mHideMenuRunnable);
+        mHandler.postDelayed(mHideMenuRunnable, recommendedTimeout);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 19138fdb..dcee2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -23,6 +23,8 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Debug;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
 import android.view.Choreographer;
 
@@ -66,6 +68,8 @@
     private PipMenuActivityController mMenuController;
     private PipSnapAlgorithm mSnapAlgorithm;
 
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
     /** PIP's current bounds on the screen. */
     private final Rect mBounds = new Rect();
 
@@ -128,8 +132,10 @@
                         SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
 
     private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
-        mMenuController.updateMenuLayout(newBounds);
-        mBounds.set(newBounds);
+        mMainHandler.post(() -> {
+            mMenuController.updateMenuLayout(newBounds);
+            mBounds.set(newBounds);
+        });
     };
 
     /**
@@ -253,7 +259,9 @@
                 mTemporaryBounds.set(toBounds);
                 mPipTaskOrganizer.scheduleUserResizePip(mBounds, mTemporaryBounds,
                         (Rect newBounds) -> {
-                            mMenuController.updateMenuLayout(newBounds);
+                            mMainHandler.post(() -> {
+                                mMenuController.updateMenuLayout(newBounds);
+                            });
                     });
             }
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
index 2800bb9..f6853ec 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -111,6 +111,7 @@
     private InputMonitor mInputMonitor;
     private InputEventReceiver mInputEventReceiver;
     private PipTaskOrganizer mPipTaskOrganizer;
+    private PipMenuActivityController mPipMenuActivityController;
     private PipUiEventLogger mPipUiEventLogger;
 
     private int mCtrlType;
@@ -119,7 +120,7 @@
             PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
             PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
             Runnable updateMovementBoundsRunnable, SysUiState sysUiState,
-            PipUiEventLogger pipUiEventLogger) {
+            PipUiEventLogger pipUiEventLogger, PipMenuActivityController menuActivityController) {
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = context.getMainExecutor();
@@ -129,6 +130,7 @@
         mMovementBoundsSupplier = movementBoundsSupplier;
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mSysUiState = sysUiState;
+        mPipMenuActivityController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
 
         context.getDisplay().getRealSize(mMaxSize);
@@ -298,6 +300,7 @@
         float x = ev.getX();
         float y = ev.getY();
         if (action == MotionEvent.ACTION_DOWN) {
+            final Rect currentPipBounds = mMotionHelper.getBounds();
             mLastResizeBounds.setEmpty();
             mAllowGesture = isInValidSysUiState() && isWithinTouchRegion((int) x, (int) y);
             if (mAllowGesture) {
@@ -305,6 +308,10 @@
                 mDownPoint.set(x, y);
                 mLastDownBounds.set(mMotionHelper.getBounds());
             }
+            if (!currentPipBounds.contains((int) ev.getX(), (int) ev.getY())
+                    && mPipMenuActivityController.isMenuVisible()) {
+                mPipMenuActivityController.hideMenu();
+            }
 
         } else if (mAllowGesture) {
             switch (action) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 1b84c14..9693f23 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -230,7 +230,7 @@
         mPipResizeGestureHandler =
                 new PipResizeGestureHandler(context, pipBoundsHandler, mMotionHelper,
                         deviceConfig, pipTaskOrganizer, this::getMovementBounds,
-                        this::updateMovementBounds, sysUiState, pipUiEventLogger);
+                        this::updateMovementBounds, sysUiState, pipUiEventLogger, menuController);
         mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
                 () -> mMenuController.showMenuWithDelay(MENU_STATE_FULL, mMotionHelper.getBounds(),
                         true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle()),
@@ -773,10 +773,7 @@
      * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
      */
     private void updateDismissFraction() {
-        // Skip updating the dismiss fraction when the IME is showing. This is to work around an
-        // issue where starting the menu activity for the dismiss overlay will steal the window
-        // focus, which closes the IME.
-        if (mMenuController != null && !mIsImeShowing) {
+        if (mMenuController != null) {
             Rect bounds = mMotionHelper.getBounds();
             final float target = mInsetBounds.bottom;
             float fraction = 0f;
@@ -784,7 +781,7 @@
                 final float distance = bounds.bottom - target;
                 fraction = Math.min(distance / bounds.height(), 1f);
             }
-            if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuActivityVisible()) {
+            if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuVisible()) {
                 // Update if the fraction > 0, or if fraction == 0 and the menu was already visible
                 mMenuController.setDismissFraction(fraction);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 8d0948b..a388fa3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -142,7 +142,6 @@
     // Used to calculate the movement bounds
     private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
     private final Rect mTmpInsetBounds = new Rect();
-    private final Rect mTmpNormalBounds = new Rect();
 
     // Keeps track of the IME visibility to adjust the PiP when the IME is visible
     private boolean mImeVisible;
@@ -216,10 +215,8 @@
         public void onMovementBoundsChanged(boolean fromImeAdjustment) {
             mHandler.post(() -> {
                 // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
-                final Rect destinationBounds = new Rect();
-                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
-                        destinationBounds, mTmpDisplayInfo);
-                mDefaultPipBounds.set(destinationBounds);
+                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mPipBounds,
+                        mDefaultPipBounds, mTmpDisplayInfo);
             });
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
index 214088c..7037403 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
@@ -36,8 +36,8 @@
  * Activity to show the PIP menu to control PIP.
  */
 public class PipMenuActivity extends Activity implements PipManager.Listener {
-    private static final boolean DEBUG = false;
     private static final String TAG = "PipMenuActivity";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
index 651a4f3..5e5de58 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
@@ -114,6 +114,14 @@
                 notifyPipNotification();
             }
         }
+
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            if (updateMediaControllerMetadata() && mNotified) {
+                // update notification
+                notifyPipNotification();
+            }
+        }
     };
 
     private final PipManager.MediaListener mPipMediaListener = new PipManager.MediaListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 4fa7822..e61e05a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -163,7 +165,7 @@
         if (!mDragDownCallback.isFalsingCheckNeeded()) {
             return false;
         }
-        return mFalsingManager.isFalseTouch() || !mDraggedFarEnough;
+        return mFalsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN) || !mDraggedFarEnough;
     }
 
     private void captureStartingChild(float x, float y) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index ba54d1b..6fa3633 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.Interpolators
 import com.android.systemui.R
+import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -106,7 +107,7 @@
     private var velocityTracker: VelocityTracker? = null
 
     private val isFalseTouch: Boolean
-        get() = falsingManager.isFalseTouch
+        get() = falsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN)
     var qsExpanded: Boolean = false
     var pulseExpandAbortListener: Runnable? = null
     var bouncerShowing: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 1e80e88..ba01c84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -227,7 +227,7 @@
                 || (isFastNonDismissGesture && isAbleToShowMenu);
         int menuSnapTarget = menuRow.getMenuSnapTarget();
         boolean isNonFalseMenuRevealingGesture =
-                !isFalseGesture(ev) && isMenuRevealingGestureAwayFromMenu;
+                !isFalseGesture() && isMenuRevealingGestureAwayFromMenu;
         if ((isNonDismissGestureTowardsMenu || isNonFalseMenuRevealingGesture)
                 && menuSnapTarget != 0) {
             // Menu has not been snapped to previously and this is menu revealing gesture
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 858023d..ba94202 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -27,6 +27,7 @@
 
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.classifier.Classifier;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -317,7 +318,9 @@
         // We snap back if the current translation is not far enough
         boolean snapBack = false;
         if (mCallback.needsAntiFalsing()) {
-            snapBack = snapBack || mFalsingManager.isFalseTouch();
+            snapBack = snapBack || mFalsingManager.isFalseTouch(
+                    mTargetedView == mRightIcon
+                            ? Classifier.RIGHT_AFFORDANCE : Classifier.LEFT_AFFORDANCE);
         }
         snapBack = snapBack || isBelowFalsingThreshold();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index d83758a..8750a02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -18,6 +18,7 @@
 
 import static android.view.View.GONE;
 
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
@@ -68,9 +69,11 @@
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.classifier.Classifier;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeLog;
@@ -129,7 +132,6 @@
 import java.util.function.Function;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 
 @StatusBarComponent.StatusBarScope
 public class NotificationPanelViewController extends PanelViewController {
@@ -260,7 +262,7 @@
     private final ConversationNotificationManager mConversationNotificationManager;
     private final MediaHierarchyManager mMediaHierarchyManager;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private final Provider<KeyguardClockSwitchController> mKeyguardClockSwitchControllerProvider;
+    private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     // Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card.
     // If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications
     private final int mMaxKeyguardNotifications;
@@ -511,9 +513,9 @@
             MediaHierarchyManager mediaHierarchyManager,
             BiometricUnlockController biometricUnlockController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            Provider<KeyguardClockSwitchController> keyguardClockSwitchControllerProvider,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
-            NotificationIconAreaController notificationIconAreaController) {
+            NotificationIconAreaController notificationIconAreaController,
+            KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
                 latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
@@ -525,9 +527,9 @@
         mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
         mMediaHierarchyManager = mediaHierarchyManager;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
-        mKeyguardClockSwitchControllerProvider = keyguardClockSwitchControllerProvider;
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
         mNotificationIconAreaController = notificationIconAreaController;
+        mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         mView.setWillNotDraw(!DEBUG);
         mInjectionInflationController = injectionInflationController;
         mFalsingManager = falsingManager;
@@ -602,8 +604,10 @@
         mKeyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
 
         KeyguardClockSwitchController keyguardClockSwitchController =
-                mKeyguardClockSwitchControllerProvider.get();
-        keyguardClockSwitchController.attach(mView.findViewById(R.id.keyguard_clock_container));
+                mKeyguardStatusViewComponentFactory
+                        .build(mKeyguardStatusView)
+                        .getKeyguardClockSwitchController();
+        keyguardClockSwitchController.init();
         mBigClockContainer = mView.findViewById(R.id.big_clock_container);
         keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
 
@@ -733,8 +737,10 @@
         // Re-associate the clock container with the keyguard clock switch.
         mBigClockContainer.removeAllViews();
         KeyguardClockSwitchController keyguardClockSwitchController =
-                mKeyguardClockSwitchControllerProvider.get();
-        keyguardClockSwitchController.attach(mView.findViewById(R.id.keyguard_clock_container));
+                mKeyguardStatusViewComponentFactory
+                        .build(mKeyguardStatusView)
+                        .getKeyguardClockSwitchController();
+        keyguardClockSwitchController.init();
         keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
 
         // Update keyguard bottom area
@@ -1264,7 +1270,7 @@
     }
 
     private boolean flingExpandsQs(float vel) {
-        if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
+        if (mFalsingManager.isUnlockingDisabled() || isFalseTouch(QUICK_SETTINGS)) {
             return false;
         }
         if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
@@ -1274,12 +1280,12 @@
         }
     }
 
-    private boolean isFalseTouch() {
+    private boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
         if (!mKeyguardAffordanceHelperCallback.needsAntiFalsing()) {
             return false;
         }
         if (mFalsingManager.isClassifierEnabled()) {
-            return mFalsingManager.isFalseTouch();
+            return mFalsingManager.isFalseTouch(interactionType);
         }
         return !mQsTouchAboveFalsingThreshold;
     }
@@ -3066,9 +3072,6 @@
         if (mKeyguardStatusBar != null) {
             mKeyguardStatusBar.dump(fd, pw, args);
         }
-        if (mKeyguardStatusView != null) {
-            mKeyguardStatusView.dump(fd, pw, args);
-        }
     }
 
     public boolean hasActiveClearableNotifications() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 965368e..0e72506 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+import static com.android.systemui.classifier.Classifier.UNLOCK;
+
 import static java.lang.Float.isNaN;
 
 import android.animation.Animator;
@@ -41,6 +45,7 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.classifier.Classifier;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -397,7 +402,12 @@
                 mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
                 mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
             }
-            fling(vel, expand, isFalseTouch(x, y));
+            @Classifier.InteractionType int interactionType = vel > 0
+                    ? QUICK_SETTINGS : (
+                            mKeyguardStateController.canDismissLockScreen()
+                                    ? UNLOCK : BOUNCER_UNLOCK);
+
+            fling(vel, expand, isFalseTouch(x, y, interactionType));
             onTrackingStopped(expand);
             mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
             if (mUpdateFlingOnLayout) {
@@ -492,7 +502,11 @@
             return true;
         }
 
-        if (isFalseTouch(x, y)) {
+        @Classifier.InteractionType int interactionType = vel > 0
+                ? QUICK_SETTINGS : (
+                        mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK);
+
+        if (isFalseTouch(x, y, interactionType)) {
             return true;
         }
         if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
@@ -511,12 +525,13 @@
      * @param y the final y-coordinate when the finger was lifted
      * @return whether this motion should be regarded as a false touch
      */
-    private boolean isFalseTouch(float x, float y) {
+    private boolean isFalseTouch(float x, float y,
+            @Classifier.InteractionType int interactionType) {
         if (!mStatusBar.isFalsingThresholdNeeded()) {
             return false;
         }
         if (mFalsingManager.isClassifierEnabled()) {
-            return mFalsingManager.isFalseTouch();
+            return mFalsingManager.isFalseTouch(interactionType);
         }
         if (!mTouchAboveFalsingThreshold) {
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
index 37aac11..df741a0 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.tv;
 
+import com.android.systemui.dagger.GlobalModule;
 import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.dagger.WMModule;
 
 import javax.inject.Singleton;
 
@@ -26,7 +28,11 @@
  * Root component for Dagger injection.
  */
 @Singleton
-@Component(modules = {TvSysUIComponentModule.class})
+@Component(modules = {
+        GlobalModule.class,
+        TvSysUIComponentModule.class,
+        WMModule.class
+})
 public interface TvGlobalRootComponent extends GlobalRootComponent {
     /**
      * Component Builder interface. This allows to bind Context instance in the component
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index 302301d..3577bc0 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -21,7 +21,6 @@
 import com.android.systemui.dagger.DependencyProvider;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.SystemServicesModule;
 import com.android.systemui.dagger.SystemUIBinder;
 import com.android.systemui.dagger.SystemUIModule;
 
@@ -35,7 +34,6 @@
         DefaultComponentBinder.class,
         DependencyProvider.class,
         DependencyBinder.class,
-        SystemServicesModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
         TvSystemUIModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index d278905..eb8f065 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -24,7 +24,6 @@
 import android.view.View;
 
 import com.android.keyguard.KeyguardMessageArea;
-import com.android.keyguard.KeyguardSliceView;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSFooterImpl;
 import com.android.systemui.qs.QSPanel;
@@ -109,11 +108,6 @@
         NotificationStackScrollLayout createNotificationStackScrollLayout();
 
         /**
-         * Creates the KeyguardSliceView.
-         */
-        KeyguardSliceView createKeyguardSliceView();
-
-        /**
          * Creates the KeyguardMessageArea.
          */
         KeyguardMessageArea createKeyguardMessageArea();
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 7b45476..6a2ca44 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -22,8 +22,6 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.pip.phone.PipMenuActivity;
-import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
 import com.android.systemui.stackdivider.SplitScreen;
 import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -50,14 +48,6 @@
         return new DisplayImeController(wmService, displayController, mainHandler, transactionPool);
     }
 
-    /** TODO(b/150319024): PipMenuActivity will move to a Window */
-    @SysUISingleton
-    @PipMenuActivityClass
-    @Provides
-    static Class<?> providePipMenuActivityClass() {
-        return PipMenuActivity.class;
-    }
-
     @SysUISingleton
     @Provides
     static SplitScreen provideSplitScreen(Context context,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 657e4fb..3aa6ec0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -62,6 +62,8 @@
     private ClockPlugin mClockPlugin;
     @Mock
     ColorExtractor.GradientColors mGradientColors;
+    @Mock
+    KeyguardSliceViewController mKeyguardSliceViewController;
 
     private KeyguardClockSwitchController mController;
 
@@ -69,28 +71,30 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mController = new KeyguardClockSwitchController(
-                mStatusBarStateController, mColorExtractor, mClockManager);
-
         when(mView.isAttachedToWindow()).thenReturn(true);
+
+        mController = new KeyguardClockSwitchController(
+                mView, mStatusBarStateController, mColorExtractor, mClockManager,
+                mKeyguardSliceViewController);
+
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
         when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
     }
 
     @Test
-    public void testAttach_viewAlreadyAttached() {
-        mController.attach(mView);
+    public void testInit_viewAlreadyAttached() {
+        mController.init();
 
         verifyAttachment(times(1));
     }
 
     @Test
-    public void testAttach_viewNotYetAttached() {
+    public void testInit_viewNotYetAttached() {
         ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
 
         when(mView.isAttachedToWindow()).thenReturn(false);
-        mController.attach(mView);
+        mController.init();
         verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
 
         verifyAttachment(never());
@@ -100,12 +104,17 @@
         verifyAttachment(times(1));
     }
 
+    @Test
+    public void testInitSubControllers() {
+        mController.init();
+        verify(mKeyguardSliceViewController).init();
+    }
 
     @Test
-    public void testAttach_viewDetached() {
+    public void testInit_viewDetached() {
         ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
-        mController.attach(mView);
+        mController.init();
         verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
 
         verifyAttachment(times(1));
@@ -122,7 +131,7 @@
     public void testBigClockPassesStatusBarState() {
         ViewGroup testView = new FrameLayout(mContext);
 
-        mController.attach(mView);
+        mController.init();
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
         mController.setBigClockContainer(testView);
         verify(mView).setBigClockContainer(testView, StatusBarState.SHADE);
@@ -143,7 +152,7 @@
         ArgumentCaptor<ClockManager.ClockChangedListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(ClockManager.ClockChangedListener.class);
 
-        mController.attach(mView);
+        mController.init();
         verify(mClockManager).addOnClockChangedListener(listenerArgumentCaptor.capture());
 
         listenerArgumentCaptor.getValue().onClockChanged(mClockPlugin);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index 446b122..559284a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -28,6 +29,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardDisplayManager.KeyguardPresentation;
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
@@ -51,6 +53,12 @@
     KeyguardSliceView mMockKeyguardSliceView;
     @Mock
     KeyguardStatusView mMockKeyguardStatusView;
+    @Mock
+    private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+    @Mock
+    private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
+    @Mock
+    private KeyguardClockSwitchController mKeyguardClockSwitchController;
 
     LayoutInflater mLayoutInflater;
 
@@ -62,6 +70,11 @@
         when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
         when(mMockKeyguardStatusView.getContext()).thenReturn(mContext);
         when(mMockKeyguardStatusView.findViewById(R.id.clock)).thenReturn(mMockKeyguardStatusView);
+        when(mKeyguardStatusViewComponentFactory.build(any(KeyguardStatusView.class)))
+                .thenReturn(mKeyguardStatusViewComponent);
+        when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController())
+                .thenReturn(mKeyguardClockSwitchController);
+
         allowTestableLooperAsMainThread();
 
         InjectionInflationController inflationController = new InjectionInflationController(
@@ -99,7 +112,8 @@
     @Test
     public void testInflation_doesntCrash() {
         KeyguardPresentation keyguardPresentation = new KeyguardPresentation(mContext,
-                mContext.getDisplayNoVerify(), mLayoutInflater);
+                mContext.getDisplayNoVerify(), mKeyguardStatusViewComponentFactory,
+                mLayoutInflater);
         keyguardPresentation.onCreate(null /*savedInstanceState */);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
new file mode 100644
index 0000000..b7bcaa3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+public class KeyguardSliceViewControllerTest extends SysuiTestCase {
+    @Mock
+    private KeyguardSliceView mView;;
+    @Mock
+    private KeyguardStatusView mKeyguardStatusView;
+    @Mock
+    private TunerService mTunerService;
+    @Mock
+    private ConfigurationController mConfigurationController;
+    @Mock
+    private ActivityStarter mActivityStarter;
+    private DumpManager mDumpManager = new DumpManager();
+
+    private KeyguardSliceViewController mController;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mView.isAttachedToWindow()).thenReturn(true);
+        when(mView.getContext()).thenReturn(mContext);
+        mController = new KeyguardSliceViewController(
+                mView, mKeyguardStatusView, mActivityStarter, mConfigurationController,
+                mTunerService, mDumpManager);
+        mController.setupUri(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
+    }
+
+    @Test
+    public void refresh_replacesSliceContentAndNotifiesListener() {
+        mController.refresh();
+        verify(mView).hideSlice();
+    }
+
+    @Test
+    public void onAttachedToWindow_registersListeners() {
+        mController.init();
+        verify(mTunerService).addTunable(any(TunerService.Tunable.class), anyString());
+        verify(mConfigurationController).addCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+    }
+
+    @Test
+    public void onDetachedFromWindow_unregistersListeners() {
+        ArgumentCaptor<View.OnAttachStateChangeListener> attachListenerArgumentCaptor =
+                ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+
+        mController.init();
+        verify(mView).addOnAttachStateChangeListener(attachListenerArgumentCaptor.capture());
+
+        attachListenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
+
+        verify(mTunerService).removeTunable(any(TunerService.Tunable.class));
+        verify(mConfigurationController).removeCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 06552b9..1ab08c2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -15,37 +15,27 @@
  */
 package com.android.keyguard;
 
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Color;
 import android.net.Uri;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.AttributeSet;
 import android.view.LayoutInflater;
-import android.view.View;
 
+import androidx.slice.Slice;
 import androidx.slice.SliceProvider;
 import androidx.slice.SliceSpecs;
 import androidx.slice.builders.ListBuilder;
+import androidx.slice.widget.RowContent;
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.tuner.TunerService;
 
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Collections;
@@ -53,46 +43,18 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @SmallTest
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner.class)
 public class KeyguardSliceViewTest extends SysuiTestCase {
     private KeyguardSliceView mKeyguardSliceView;
     private Uri mSliceUri;
 
-    @Mock
-    private TunerService mTunerService;
-    @Mock
-    private ConfigurationController mConfigurationController;
-    @Mock
-    private ActivityStarter mActivityStarter;
-    @Mock
-    private Resources mResources;
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        allowTestableLooperAsMainThread();
         LayoutInflater layoutInflater = LayoutInflater.from(getContext());
-        layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
-
-            @Override
-            public View onCreateView(View parent, String name, Context context,
-                    AttributeSet attrs) {
-                return onCreateView(name, context, attrs);
-            }
-
-            @Override
-            public View onCreateView(String name, Context context, AttributeSet attrs) {
-                if ("com.android.keyguard.KeyguardSliceView".equals(name)) {
-                    return new KeyguardSliceView(getContext(), attrs, mActivityStarter,
-                            mConfigurationController, mTunerService, mResources);
-                }
-                return null;
-            }
-        });
         mKeyguardSliceView = (KeyguardSliceView) layoutInflater
                 .inflate(R.layout.keyguard_status_area, null);
-        mKeyguardSliceView.setupUri(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
         mSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
         SliceProvider.setSpecs(new HashSet<>(Collections.singletonList(SliceSpecs.LIST)));
     }
@@ -100,9 +62,13 @@
     @Test
     public void showSlice_notifiesListener() {
         ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
+        builder.setHeader(new ListBuilder.HeaderBuilder().setTitle("header title!"));
+        Slice slice = builder.build();
+        RowContent rowContent = new RowContent(slice.getItemArray()[0], 0);
+
         AtomicBoolean notified = new AtomicBoolean();
         mKeyguardSliceView.setContentChangeListener(()-> notified.set(true));
-        mKeyguardSliceView.onChanged(builder.build());
+        mKeyguardSliceView.showSlice(rowContent, Collections.EMPTY_LIST);
         Assert.assertTrue("Listener should be notified about slice changes.",
                 notified.get());
     }
@@ -111,7 +77,7 @@
     public void showSlice_emptySliceNotifiesListener() {
         AtomicBoolean notified = new AtomicBoolean();
         mKeyguardSliceView.setContentChangeListener(()-> notified.set(true));
-        mKeyguardSliceView.onChanged(null);
+        mKeyguardSliceView.showSlice(null, Collections.EMPTY_LIST);
         Assert.assertTrue("Listener should be notified about slice changes.",
                 notified.get());
     }
@@ -119,24 +85,17 @@
     @Test
     public void hasHeader_readsSliceData() {
         ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
-        mKeyguardSliceView.onChanged(builder.build());
+        mKeyguardSliceView.showSlice(null, Collections.EMPTY_LIST);
         Assert.assertFalse("View should not have a header", mKeyguardSliceView.hasHeader());
 
         builder.setHeader(new ListBuilder.HeaderBuilder().setTitle("header title!"));
-        mKeyguardSliceView.onChanged(builder.build());
+        Slice slice = builder.build();
+        RowContent rowContent = new RowContent(slice.getItemArray()[0], 0);
+        mKeyguardSliceView.showSlice(rowContent, Collections.EMPTY_LIST);
         Assert.assertTrue("View should have a header", mKeyguardSliceView.hasHeader());
     }
 
     @Test
-    public void refresh_replacesSliceContentAndNotifiesListener() {
-        AtomicBoolean notified = new AtomicBoolean();
-        mKeyguardSliceView.setContentChangeListener(()-> notified.set(true));
-        mKeyguardSliceView.refresh();
-        Assert.assertTrue("Listener should be notified about slice changes.",
-                notified.get());
-    }
-
-    @Test
     public void getTextColor_whiteTextWhenAOD() {
         // Set text color to red since the default is white and test would always pass
         mKeyguardSliceView.setTextColor(Color.RED);
@@ -147,18 +106,4 @@
         Assert.assertEquals("Should be using AOD text color", Color.WHITE,
                 mKeyguardSliceView.getTextColor());
     }
-
-    @Test
-    public void onAttachedToWindow_registersListeners() {
-        mKeyguardSliceView.onAttachedToWindow();
-        verify(mTunerService).addTunable(eq(mKeyguardSliceView), anyString());
-        verify(mConfigurationController).addCallback(eq(mKeyguardSliceView));
-    }
-
-    @Test
-    public void onDetachedFromWindow_unregistersListeners() {
-        mKeyguardSliceView.onDetachedFromWindow();
-        verify(mTunerService).removeTunable(eq(mKeyguardSliceView));
-        verify(mConfigurationController).removeCallback(eq(mKeyguardSliceView));
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
index 0bf1376..0431704 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
@@ -40,7 +40,8 @@
 public class KeyguardStatusViewTest extends SysuiTestCase {
 
     @Mock
-    KeyguardSliceView mKeyguardSlice;
+    KeyguardSliceViewController mKeyguardSliceViewController;
+
     @Mock
     KeyguardClockSwitch mClockView;
     @InjectMocks
@@ -64,7 +65,7 @@
     @Test
     public void dozeTimeTick_updatesSlice() {
         mKeyguardStatusView.dozeTimeTick();
-        verify(mKeyguardSlice).refresh();
+        verify(mKeyguardSliceViewController).refresh();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index c874b1f..f6d6f562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -37,6 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -46,7 +47,6 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
-import com.android.systemui.util.InjectionInflationController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -72,7 +72,7 @@
     private @Mock PowerManager mPowerManager;
     private @Mock TrustManager mTrustManager;
     private @Mock NavigationModeController mNavigationModeController;
-    private @Mock InjectionInflationController mInjectionInflationController;
+    private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -91,7 +91,7 @@
                 () -> mStatusBarKeyguardViewManager,
                 mDismissCallbackRegistry, mUpdateMonitor, mDumpManager, mUiBgExecutor,
                 mPowerManager, mTrustManager, mDeviceConfig, mNavigationModeController,
-                mInjectionInflationController);
+                mKeyguardDisplayManager);
         mViewMediator.start();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 04e870d..a9484af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -54,6 +54,7 @@
 import com.android.keyguard.KeyguardClockSwitchController;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -187,10 +188,16 @@
     @Mock
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock
+    private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+    @Mock
+    private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
+    @Mock
     private KeyguardClockSwitchController mKeyguardClockSwitchController;
     @Mock
     private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
 
+    private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
 
@@ -241,6 +248,10 @@
                 mock(NotificationRoundnessManager.class),
                 mStatusBarStateController,
                 new FalsingManagerFake());
+        when(mKeyguardStatusViewComponentFactory.build(any()))
+                .thenReturn(mKeyguardStatusViewComponent);
+        when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController())
+                .thenReturn(mKeyguardClockSwitchController);
         mNotificationPanelViewController = new NotificationPanelViewController(mView,
                 mResources,
                 mInjectionInflationController,
@@ -254,9 +265,9 @@
                 flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
                 mConversationNotificationManager, mMediaHiearchyManager,
                 mBiometricUnlockController, mStatusBarKeyguardViewManager,
-                () -> mKeyguardClockSwitchController,
                 mNotificationStackScrollLayoutController,
-                mNotificationAreaController);
+                mNotificationAreaController,
+                mKeyguardStatusViewComponentFactory);
         mNotificationPanelViewController.initDependencies(
                 mStatusBar,
                 mGroupManager,
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index b587dd3..adccf6c 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -594,15 +594,23 @@
                     if (pointerIndex < 0) {
                         return;
                     }
-                    final float deltaX =
-                            mReceivedPointerTracker.getReceivedPointerDownX(pointerId)
-                                    - rawEvent.getX(pointerIndex);
-                    final float deltaY =
-                            mReceivedPointerTracker.getReceivedPointerDownY(pointerId)
-                                    - rawEvent.getY(pointerIndex);
-                    final double moveDelta = Math.hypot(deltaX, deltaY);
-                    if (moveDelta < mTouchSlop) {
-                        return;
+                    // Require both fingers to have moved a certain amount before starting a drag.
+                    for (int index = 0; index < event.getPointerCount(); ++index) {
+                        int id = event.getPointerId(index);
+                        if (!mReceivedPointerTracker.isReceivedPointerDown(id)) {
+                            // Something is wrong with the event stream.
+                            Slog.e(LOG_TAG, "Invalid pointer id: " + id);
+                        }
+                        final float deltaX =
+                                mReceivedPointerTracker.getReceivedPointerDownX(id)
+                                        - rawEvent.getX(index);
+                        final float deltaY =
+                                mReceivedPointerTracker.getReceivedPointerDownY(id)
+                                        - rawEvent.getY(index);
+                        final double moveDelta = Math.hypot(deltaX, deltaY);
+                        if (moveDelta < mTouchSlop) {
+                            return;
+                        }
                     }
                 }
                 // More than one pointer so the user is not touch exploring
@@ -612,12 +620,20 @@
                 if (isDraggingGesture(event)) {
                     // Two pointers moving in the same direction within
                     // a given distance perform a drag.
-                    mState.startDragging();
                     computeDraggingPointerIdIfNeeded(event);
                     pointerIdBits = 1 << mDraggingPointerId;
                     event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags());
-                    mDispatcher.sendMotionEvent(
-                            event, ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
+                    MotionEvent downEvent = computeDownEventForDrag(event);
+                    if (downEvent != null) {
+                        mDispatcher.sendMotionEvent(
+                                downEvent, ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
+                        mDispatcher.sendMotionEvent(
+                                event, ACTION_MOVE, rawEvent, pointerIdBits, policyFlags);
+                    } else {
+                        mDispatcher.sendMotionEvent(
+                                event, ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
+                    }
+                    mState.startDragging();
                 } else {
                     // Two pointers moving arbitrary are delegated to the view hierarchy.
                     mState.startDelegating();
@@ -628,18 +644,9 @@
                 if (mGestureDetector.isMultiFingerGesturesEnabled()) {
                     if (mGestureDetector.isTwoFingerPassthroughEnabled()) {
                         if (event.getPointerCount() == 3) {
-                            boolean isOnBottomEdge = true;
                             // If three fingers went down on the bottom edge of the screen, delegate
                             // immediately.
-                            final long screenHeight =
-                                    mContext.getResources().getDisplayMetrics().heightPixels;
-                            for (int i = 0; i < TouchState.MAX_POINTER_COUNT; ++i) {
-                                if (mReceivedPointerTracker.getReceivedPointerDownY(i)
-                                        < (screenHeight - mEdgeSwipeHeightPixels)) {
-                                    isOnBottomEdge = false;
-                                }
-                            }
-                            if (isOnBottomEdge) {
+                            if (allPointersDownOnBottomEdge(event)) {
                                 if (DEBUG) {
                                     Slog.d(LOG_TAG, "Three-finger edge swipe detected.");
                                 }
@@ -1004,6 +1011,65 @@
         return distance;
     }
 
+    /**
+     * Creates a down event using the down coordinates of the dragging pointer and other information
+     * from the supplied event. The supplied event's down time is adjusted to reflect the time when
+     * the dragging pointer initially went down.
+     */
+    private MotionEvent computeDownEventForDrag(MotionEvent event) {
+        // Creating a down event only  makes sense if we haven't started touch exploring yet.
+        if (mState.isTouchExploring()
+                || mDraggingPointerId == INVALID_POINTER_ID
+                || event == null) {
+            return null;
+        }
+        final float x = mReceivedPointerTracker.getReceivedPointerDownX(mDraggingPointerId);
+        final float y = mReceivedPointerTracker.getReceivedPointerDownY(mDraggingPointerId);
+        final long time = mReceivedPointerTracker.getReceivedPointerDownTime(mDraggingPointerId);
+        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[1];
+        coords[0] = new MotionEvent.PointerCoords();
+        coords[0].x = x;
+        coords[0].y = y;
+        MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
+        properties[0] = new MotionEvent.PointerProperties();
+        properties[0].id = mDraggingPointerId;
+        properties[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
+        MotionEvent downEvent =
+                MotionEvent.obtain(
+                        time,
+                        time,
+                        ACTION_DOWN,
+                        1,
+                        properties,
+                        coords,
+                        event.getMetaState(),
+                        event.getButtonState(),
+                        event.getXPrecision(),
+                        event.getYPrecision(),
+                        event.getDeviceId(),
+                        event.getEdgeFlags(),
+                        event.getSource(),
+                        event.getFlags());
+        event.setDownTime(time);
+        return downEvent;
+    }
+
+    private boolean allPointersDownOnBottomEdge(MotionEvent event) {
+        final long screenHeight =
+                mContext.getResources().getDisplayMetrics().heightPixels;
+        for (int i = 0; i < event.getPointerCount(); ++i) {
+            final int pointerId = event.getPointerId(i);
+            final float pointerDownY = mReceivedPointerTracker.getReceivedPointerDownY(pointerId);
+            if (pointerDownY < (screenHeight - mEdgeSwipeHeightPixels)) {
+                if (DEBUG) {
+                    Slog.d(LOG_TAG, "The pointer is not on the bottom edge" + pointerDownY);
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
     public TouchState getState() {
         return mState;
     }
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index 1c4db12..59ba82e 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -34,6 +34,7 @@
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.util.Slog;
@@ -108,9 +109,9 @@
 
         @Override
         public void createPredictionSession(@NonNull AppPredictionContext context,
-                @NonNull AppPredictionSessionId sessionId) {
-            runForUserLocked("createPredictionSession", sessionId,
-                    (service) -> service.onCreatePredictionSessionLocked(context, sessionId));
+                @NonNull AppPredictionSessionId sessionId, @NonNull IBinder token) {
+            runForUserLocked("createPredictionSession", sessionId, (service) ->
+                    service.onCreatePredictionSessionLocked(context, sessionId, token));
         }
 
         @Override
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 7ee607c..735f420 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -30,6 +30,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ServiceInfo;
+import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.provider.DeviceConfig;
@@ -44,8 +45,6 @@
 import com.android.server.infra.AbstractPerUserSystemService;
 import com.android.server.people.PeopleServiceInternal;
 
-import java.util.function.Consumer;
-
 /**
  * Per-user instance of {@link AppPredictionManagerService}.
  */
@@ -112,17 +111,24 @@
      */
     @GuardedBy("mLock")
     public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
-            @NonNull AppPredictionSessionId sessionId) {
-        if (!mSessionInfos.containsKey(sessionId)) {
-            mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context,
-                    DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
-                            PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false),
-                    this::removeAppPredictionSessionInfo));
-        }
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.onCreatePredictionSession(context, sessionId), true);
-        if (!serviceExists) {
-            mSessionInfos.remove(sessionId);
+            @NonNull AppPredictionSessionId sessionId, @NonNull IBinder token) {
+        final boolean usesPeopleService = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+                PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false);
+        final boolean serviceExists = resolveService(sessionId, false,
+                usesPeopleService, s -> s.onCreatePredictionSession(context, sessionId));
+        if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
+            final AppPredictionSessionInfo sessionInfo = new AppPredictionSessionInfo(
+                    sessionId, context, usesPeopleService, token, () -> {
+                synchronized (mLock) {
+                    onDestroyPredictionSessionLocked(sessionId);
+                }
+            });
+            if (sessionInfo.linkToDeath()) {
+                mSessionInfos.put(sessionId, sessionInfo);
+            } else {
+                // destroy the session if calling process is already dead
+                onDestroyPredictionSessionLocked(sessionId);
+            }
         }
     }
 
@@ -132,7 +138,10 @@
     @GuardedBy("mLock")
     public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull AppTargetEvent event) {
-        resolveService(sessionId, s -> s.notifyAppTargetEvent(sessionId, event), false);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, false, sessionInfo.mUsesPeopleService,
+                s -> s.notifyAppTargetEvent(sessionId, event));
     }
 
     /**
@@ -141,8 +150,10 @@
     @GuardedBy("mLock")
     public void notifyLaunchLocationShownLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
-        resolveService(sessionId, s ->
-                s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds), false);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, false, sessionInfo.mUsesPeopleService,
+                s -> s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds));
     }
 
     /**
@@ -151,7 +162,10 @@
     @GuardedBy("mLock")
     public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
-        resolveService(sessionId, s -> s.sortAppTargets(sessionId, targets, callback), true);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, true, sessionInfo.mUsesPeopleService,
+                s -> s.sortAppTargets(sessionId, targets, callback));
     }
 
     /**
@@ -160,10 +174,12 @@
     @GuardedBy("mLock")
     public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull IPredictionCallback callback) {
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.registerPredictionUpdates(sessionId, callback), false);
         final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (serviceExists && sessionInfo != null) {
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId, false,
+                sessionInfo.mUsesPeopleService,
+                s -> s.registerPredictionUpdates(sessionId, callback));
+        if (serviceExists) {
             sessionInfo.addCallbackLocked(callback);
         }
     }
@@ -174,10 +190,12 @@
     @GuardedBy("mLock")
     public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull IPredictionCallback callback) {
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.unregisterPredictionUpdates(sessionId, callback), false);
         final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (serviceExists && sessionInfo != null) {
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId, false,
+                sessionInfo.mUsesPeopleService,
+                s -> s.unregisterPredictionUpdates(sessionId, callback));
+        if (serviceExists) {
             sessionInfo.removeCallbackLocked(callback);
         }
     }
@@ -187,7 +205,10 @@
      */
     @GuardedBy("mLock")
     public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) {
-        resolveService(sessionId, s -> s.requestPredictionUpdate(sessionId), true);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, true, sessionInfo.mUsesPeopleService,
+                s -> s.requestPredictionUpdate(sessionId));
     }
 
     /**
@@ -195,12 +216,14 @@
      */
     @GuardedBy("mLock")
     public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) {
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.onDestroyPredictionSession(sessionId), false);
-        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (serviceExists && sessionInfo != null) {
-            sessionInfo.destroy();
+        if (isDebug()) {
+            Slog.d(TAG, "onDestroyPredictionSessionLocked(): sessionId=" + sessionId);
         }
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.remove(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, false, sessionInfo.mUsesPeopleService,
+                s -> s.onDestroyPredictionSession(sessionId));
+        sessionInfo.destroy();
     }
 
     @Override
@@ -291,27 +314,18 @@
         }
 
         for (AppPredictionSessionInfo sessionInfo : mSessionInfos.values()) {
-            sessionInfo.resurrectSessionLocked(this);
-        }
-    }
-
-    private void removeAppPredictionSessionInfo(AppPredictionSessionId sessionId) {
-        if (isDebug()) {
-            Slog.d(TAG, "removeAppPredictionSessionInfo(): sessionId=" + sessionId);
-        }
-        synchronized (mLock) {
-            mSessionInfos.remove(sessionId);
+            sessionInfo.resurrectSessionLocked(this, sessionInfo.mToken);
         }
     }
 
     @GuardedBy("mLock")
     @Nullable
-    protected boolean resolveService(@NonNull final AppPredictionSessionId sessionId,
-            @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb,
-            boolean sendImmediately) {
-        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (sessionInfo == null) return false;
-        if (sessionInfo.mUsesPeopleService) {
+    protected boolean resolveService(
+            @NonNull final AppPredictionSessionId sessionId,
+            boolean sendImmediately,
+            boolean usesPeopleService,
+            @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb) {
+        if (usesPeopleService) {
             final IPredictionService service =
                     LocalServices.getService(PeopleServiceInternal.class);
             if (service != null) {
@@ -368,7 +382,9 @@
         private final AppPredictionContext mPredictionContext;
         private final boolean mUsesPeopleService;
         @NonNull
-        private final Consumer<AppPredictionSessionId> mRemoveSessionInfoAction;
+        final IBinder mToken;
+        @NonNull
+        final IBinder.DeathRecipient mDeathRecipient;
 
         private final RemoteCallbackList<IPredictionCallback> mCallbacks =
                 new RemoteCallbackList<IPredictionCallback>() {
@@ -388,14 +404,16 @@
                 @NonNull final AppPredictionSessionId id,
                 @NonNull final AppPredictionContext predictionContext,
                 final boolean usesPeopleService,
-                @NonNull final Consumer<AppPredictionSessionId> removeSessionInfoAction) {
+                @NonNull final IBinder token,
+                @NonNull final IBinder.DeathRecipient deathRecipient) {
             if (DEBUG) {
                 Slog.d(TAG, "Creating AppPredictionSessionInfo for session Id=" + id);
             }
             mSessionId = id;
             mPredictionContext = predictionContext;
             mUsesPeopleService = usesPeopleService;
-            mRemoveSessionInfoAction = removeSessionInfoAction;
+            mToken = token;
+            mDeathRecipient = deathRecipient;
         }
 
         void addCallbackLocked(IPredictionCallback callback) {
@@ -414,23 +432,38 @@
             mCallbacks.unregister(callback);
         }
 
+        boolean linkToDeath() {
+            try {
+                mToken.linkToDeath(mDeathRecipient, 0);
+            } catch (RemoteException e) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Caller is dead before session can be started, sessionId: "
+                            + mSessionId);
+                }
+                return false;
+            }
+            return true;
+        }
+
         void destroy() {
             if (DEBUG) {
                 Slog.d(TAG, "Removing all callbacks for session Id=" + mSessionId
                         + " and " + mCallbacks.getRegisteredCallbackCount() + " callbacks.");
             }
+            if (mToken != null) {
+                mToken.unlinkToDeath(mDeathRecipient, 0);
+            }
             mCallbacks.kill();
-            mRemoveSessionInfoAction.accept(mSessionId);
         }
 
-        void resurrectSessionLocked(AppPredictionPerUserService service) {
+        void resurrectSessionLocked(AppPredictionPerUserService service, IBinder token) {
             int callbackCount = mCallbacks.getRegisteredCallbackCount();
             if (DEBUG) {
                 Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
                         + ") for session Id=" + mSessionId + " and "
                         + callbackCount + " callbacks.");
             }
-            service.onCreatePredictionSessionLocked(mPredictionContext, mSessionId);
+            service.onCreatePredictionSessionLocked(mPredictionContext, mSessionId, token);
             mCallbacks.broadcast(
                     callback -> service.registerPredictionUpdatesLocked(mSessionId, callback));
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6e1e3d0..2d803437 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -83,6 +83,7 @@
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
@@ -117,7 +118,6 @@
 import static com.android.server.am.MemoryStatUtil.hasMemcg;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK;
@@ -319,6 +319,7 @@
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.os.Zygote;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastPrintWriter;
@@ -4946,9 +4947,8 @@
                 notifyPackageUse(instr.mClass.getPackageName(),
                                  PackageManager.NOTIFY_PACKAGE_USE_INSTRUMENTATION);
             }
-            if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Binding proc "
-                    + processName + " with config "
-                    + app.getWindowProcessController().getConfiguration());
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Binding proc %s with config %s",
+                    processName, app.getWindowProcessController().getConfiguration());
             ApplicationInfo appInfo = instr != null ? instr.mTargetInfo : app.info;
             app.compat = compatibilityInfoForPackage(appInfo);
 
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 06ef58f..5447605 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -500,8 +500,8 @@
         sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
     }
 
-    /*package*/ void postSetModeOwnerPid(int pid) {
-        sendIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid);
+    /*package*/ void postSetModeOwnerPid(int pid, int mode) {
+        sendIIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid, mode);
     }
 
     /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
@@ -977,7 +977,9 @@
                         synchronized (mDeviceStateLock) {
                             if (mModeOwnerPid != msg.arg1) {
                                 mModeOwnerPid = msg.arg1;
-                                updateSpeakerphoneOn("setNewModeOwner");
+                                if (msg.arg2 != AudioSystem.MODE_RINGTONE) {
+                                    updateSpeakerphoneOn("setNewModeOwner");
+                                }
                                 if (mModeOwnerPid != 0) {
                                     mBtHelper.disconnectBluetoothSco(mModeOwnerPid);
                                 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 673ca1f..d59780d 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3747,13 +3747,15 @@
         private final IBinder mCb; // To be notified of client's death
         private final int mPid;
         private final int mUid;
-        private String mPackage;
+        private final boolean mIsPrivileged;
+        private final String mPackage;
         private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
 
-        SetModeDeathHandler(IBinder cb, int pid, int uid, String caller) {
+        SetModeDeathHandler(IBinder cb, int pid, int uid, boolean isPrivileged, String caller) {
             mCb = cb;
             mPid = pid;
             mUid = uid;
+            mIsPrivileged = isPrivileged;
             mPackage = caller;
         }
 
@@ -3765,12 +3767,13 @@
                 if (index < 0) {
                     Log.w(TAG, "unregistered setMode() client died");
                 } else {
-                    newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, mUid, TAG);
+                    newModeOwnerPid = setModeInt(
+                            AudioSystem.MODE_NORMAL, mCb, mPid, mUid, mIsPrivileged, TAG);
                 }
             }
             // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
             // SCO connections not started by the application changing the mode when pid changes
-            mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
+            mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid, AudioService.this.getMode());
         }
 
         public int getPid() {
@@ -3796,6 +3799,10 @@
         public String getPackage() {
             return mPackage;
         }
+
+        public boolean isPrivileged() {
+            return mIsPrivileged;
+        }
     }
 
     /** @see AudioManager#setMode(int) */
@@ -3847,18 +3854,19 @@
                         + " without permission or being mode owner");
                 return;
             }
-            newModeOwnerPid = setModeInt(
-                mode, cb, callingPid, Binder.getCallingUid(), callingPackage);
+            newModeOwnerPid = setModeInt(mode, cb, callingPid, Binder.getCallingUid(),
+                    hasModifyPhoneStatePermission, callingPackage);
         }
         // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
         // SCO connections not started by the application changing the mode when pid changes
-        mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
+        mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid, getMode());
     }
 
     // setModeInt() returns a valid PID if the audio mode was successfully set to
     // any mode other than NORMAL.
     @GuardedBy("mDeviceBroker.mSetModeLock")
-    private int setModeInt(int mode, IBinder cb, int pid, int uid, String caller) {
+    private int setModeInt(
+            int mode, IBinder cb, int pid, int uid, boolean isPrivileged, String caller) {
         if (DEBUG_MODE) {
             Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid
                     + ", uid=" + uid + ", caller=" + caller + ")");
@@ -3910,7 +3918,7 @@
                 }
             } else {
                 if (hdlr == null) {
-                    hdlr = new SetModeDeathHandler(cb, pid, uid, caller);
+                    hdlr = new SetModeDeathHandler(cb, pid, uid, isPrivileged, caller);
                 }
                 // Register for client death notification
                 try {
@@ -3969,7 +3977,8 @@
             // change of mode may require volume to be re-applied on some devices
             updateAbsVolumeMultiModeDevices(oldMode, actualMode);
 
-            if (actualMode == AudioSystem.MODE_IN_COMMUNICATION) {
+            if (actualMode == AudioSystem.MODE_IN_COMMUNICATION
+                    && !hdlr.isPrivileged()) {
                 sendMsg(mAudioHandler,
                         MSG_CHECK_MODE_FOR_UID,
                         SENDMSG_QUEUE,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 3f949ba..653323d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -25,7 +25,7 @@
  * A helper class to build {@link HdmiCecMessage} from various cec commands.
  */
 public class HdmiCecMessageBuilder {
-    private static final int OSD_NAME_MAX_LENGTH = 13;
+    private static final int OSD_NAME_MAX_LENGTH = 14;
 
     private HdmiCecMessageBuilder() {}
 
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
index 05aa315..06105bf 100644
--- a/services/core/java/com/android/server/location/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -314,6 +314,14 @@
         }
 
         @Override
+        public final <Listener> void onOperationFailure(ListenerOperation<Listener> operation,
+                Exception e) {
+            synchronized (mLock) {
+                super.onOperationFailure(operation, e);
+            }
+        }
+
+        @Override
         public final LocationRequest getRequest() {
             return mProviderLocationRequest;
         }
diff --git a/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java b/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java
new file mode 100644
index 0000000..1500cfa
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 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.timezonedetector;
+
+import android.annotation.UserIdInt;
+import android.os.Binder;
+import android.os.UserHandle;
+
+/**
+ * An interface to wrap various difficult-to-intercept calls that services make to access / manage
+ * caller identity, e.g. {@link Binder#clearCallingIdentity()}.
+ */
+public interface CallerIdentityInjector {
+
+    /** A singleton for the real implementation of {@link CallerIdentityInjector}. */
+    CallerIdentityInjector REAL = new Real();
+
+    /** A {@link UserHandle#getCallingUserId()} call. */
+    @UserIdInt int getCallingUserId();
+
+    /** A {@link Binder#clearCallingIdentity()} call. */
+    long clearCallingIdentity();
+
+    /** A {@link Binder#restoreCallingIdentity(long)} ()} call. */
+    void restoreCallingIdentity(long token);
+
+    /** The real implementation of {@link CallerIdentityInjector}. */
+    class Real implements CallerIdentityInjector {
+
+        protected Real() {
+        }
+
+        @Override
+        public int getCallingUserId() {
+            return UserHandle.getCallingUserId();
+        }
+
+        @Override
+        public long clearCallingIdentity() {
+            return Binder.clearCallingIdentity();
+        }
+
+        @Override
+        public void restoreCallingIdentity(long token) {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
copy to services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
index 114c30e..4c7b1f3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 2019 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.
@@ -14,17 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.pip.phone.dagger;
+package com.android.server.timezonedetector;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Qualifier;
-
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface PipMenuActivityClass {
+/**
+ * A listener used to receive notification that time zone configuration has changed.
+ */
+@FunctionalInterface
+public interface ConfigurationChangeListener {
+    /** Called when the current user or a configuration value has changed. */
+    void onChange();
 }
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
new file mode 100644
index 0000000..aee3d8d
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2020 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.timezonedetector;
+
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.timezonedetector.TimeZoneCapabilities;
+import android.app.timezonedetector.TimeZoneConfiguration;
+
+import java.util.Objects;
+
+/**
+ * Holds all configuration values that affect time zone behavior and some associated logic, e.g.
+ * {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link
+ * #createCapabilities()}.
+ */
+public final class ConfigurationInternal {
+
+    private final @UserIdInt int mUserId;
+    private final boolean mUserConfigAllowed;
+    private final boolean mAutoDetectionSupported;
+    private final boolean mAutoDetectionEnabled;
+    private final boolean mLocationEnabled;
+    private final boolean mGeoDetectionEnabled;
+
+    private ConfigurationInternal(Builder builder) {
+        mUserId = builder.mUserId;
+        mUserConfigAllowed = builder.mUserConfigAllowed;
+        mAutoDetectionSupported = builder.mAutoDetectionSupported;
+        mAutoDetectionEnabled = builder.mAutoDetectionEnabled;
+        mLocationEnabled = builder.mLocationEnabled;
+        mGeoDetectionEnabled = builder.mGeoDetectionEnabled;
+    }
+
+    /** Returns the ID of the user this configuration is associated with. */
+    public @UserIdInt int getUserId() {
+        return mUserId;
+    }
+
+    /** Returns true if the user allowed to modify time zone configuration. */
+    public boolean isUserConfigAllowed() {
+        return mUserConfigAllowed;
+    }
+
+    /** Returns true if the device supports some form of auto time zone detection. */
+    public boolean isAutoDetectionSupported() {
+        return mAutoDetectionSupported;
+    }
+
+    /** Returns the value of the auto time zone detection enabled setting. */
+    public boolean getAutoDetectionEnabledSetting() {
+        return mAutoDetectionEnabled;
+    }
+
+    /**
+     * Returns true if auto time zone detection behavior is actually enabled, which can be distinct
+     * from the raw setting value. */
+    public boolean getAutoDetectionEnabledBehavior() {
+        return mAutoDetectionSupported && mAutoDetectionEnabled;
+    }
+
+    /** Returns true if user's location can be used generally. */
+    public boolean isLocationEnabled() {
+        return mLocationEnabled;
+    }
+
+    /** Returns the value of the geolocation time zone detection enabled setting. */
+    public boolean getGeoDetectionEnabledSetting() {
+        return mGeoDetectionEnabled;
+    }
+
+    /**
+     * Returns true if geolocation time zone detection behavior is actually enabled, which can be
+     * distinct from the raw setting value.
+     */
+    public boolean getGeoDetectionEnabledBehavior() {
+        if (getAutoDetectionEnabledBehavior()) {
+            return mLocationEnabled && mGeoDetectionEnabled;
+        }
+        return false;
+    }
+
+    /** Creates a {@link TimeZoneCapabilities} object using the configuration values. */
+    public TimeZoneCapabilities createCapabilities() {
+        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
+                .setConfiguration(asConfiguration());
+
+        boolean allowConfigDateTime = isUserConfigAllowed();
+
+        // Automatic time zone detection is only supported on devices if there is a telephony
+        // network available or geolocation time zone detection is possible.
+        boolean deviceHasTimeZoneDetection = isAutoDetectionSupported();
+
+        final int configureAutoDetectionEnabledCapability;
+        if (!deviceHasTimeZoneDetection) {
+            configureAutoDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
+        } else if (!allowConfigDateTime) {
+            configureAutoDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
+        } else {
+            configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED;
+        }
+        builder.setConfigureAutoDetectionEnabled(configureAutoDetectionEnabledCapability);
+
+        final int configureGeolocationDetectionEnabledCapability;
+        if (!deviceHasTimeZoneDetection) {
+            configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
+        } else if (!allowConfigDateTime) {
+            configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
+        } else if (!isLocationEnabled()) {
+            configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE;
+        } else {
+            configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED;
+        }
+        builder.setConfigureGeoDetectionEnabled(configureGeolocationDetectionEnabledCapability);
+
+        // The ability to make manual time zone suggestions can also be restricted by policy. With
+        // the current logic above, this could lead to a situation where a device hardware does not
+        // support auto detection, the device has been forced into "auto" mode by an admin and the
+        // user is unable to disable auto detection.
+        final int suggestManualTimeZoneCapability;
+        if (!allowConfigDateTime) {
+            suggestManualTimeZoneCapability = CAPABILITY_NOT_ALLOWED;
+        } else if (getAutoDetectionEnabledBehavior()) {
+            suggestManualTimeZoneCapability = CAPABILITY_NOT_APPLICABLE;
+        } else {
+            suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
+        }
+        builder.setSuggestManualTimeZone(suggestManualTimeZoneCapability);
+
+        return builder.build();
+    }
+
+    /** Returns a {@link TimeZoneConfiguration} from the configuration values. */
+    public TimeZoneConfiguration asConfiguration() {
+        return new TimeZoneConfiguration.Builder(mUserId)
+                .setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
+                .setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
+                .build();
+    }
+
+    /**
+     * Merges the configuration values from this with any properties set in {@code
+     * newConfiguration}. The new configuration has precedence. Used to apply user updates to
+     * internal configuration.
+     */
+    public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) {
+        Builder builder = new Builder(this);
+        if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED)) {
+            builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled());
+        }
+        if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED)) {
+            builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled());
+        }
+        return builder.build();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        ConfigurationInternal that = (ConfigurationInternal) o;
+        return mUserId == that.mUserId
+                && mUserConfigAllowed == that.mUserConfigAllowed
+                && mAutoDetectionSupported == that.mAutoDetectionSupported
+                && mAutoDetectionEnabled == that.mAutoDetectionEnabled
+                && mLocationEnabled == that.mLocationEnabled
+                && mGeoDetectionEnabled == that.mGeoDetectionEnabled;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mUserId, mUserConfigAllowed, mAutoDetectionSupported,
+                mAutoDetectionEnabled, mLocationEnabled, mGeoDetectionEnabled);
+    }
+
+    @Override
+    public String toString() {
+        return "TimeZoneDetectorConfiguration{"
+                + "mUserId=" + mUserId
+                + "mUserConfigAllowed=" + mUserConfigAllowed
+                + "mAutoDetectionSupported=" + mAutoDetectionSupported
+                + "mAutoDetectionEnabled=" + mAutoDetectionEnabled
+                + "mLocationEnabled=" + mLocationEnabled
+                + "mGeoDetectionEnabled=" + mGeoDetectionEnabled
+                + '}';
+    }
+
+    /**
+     * A Builder for {@link ConfigurationInternal}.
+     */
+    public static class Builder {
+
+        private final @UserIdInt int mUserId;
+        private boolean mUserConfigAllowed;
+        private boolean mAutoDetectionSupported;
+        private boolean mAutoDetectionEnabled;
+        private boolean mLocationEnabled;
+        private boolean mGeoDetectionEnabled;
+
+        /**
+         * Creates a new Builder with only the userId set.
+         */
+        public Builder(@UserIdInt int userId) {
+            mUserId = userId;
+        }
+
+        /**
+         * Creates a new Builder by copying values from an existing instance.
+         */
+        public Builder(ConfigurationInternal toCopy) {
+            this.mUserId = toCopy.mUserId;
+            this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
+            this.mAutoDetectionSupported = toCopy.mAutoDetectionSupported;
+            this.mAutoDetectionEnabled = toCopy.mAutoDetectionEnabled;
+            this.mLocationEnabled = toCopy.mLocationEnabled;
+            this.mGeoDetectionEnabled = toCopy.mGeoDetectionEnabled;
+        }
+
+        /**
+         * Sets whether the user is allowed to configure time zone settings on this device.
+         */
+        public Builder setUserConfigAllowed(boolean configAllowed) {
+            mUserConfigAllowed = configAllowed;
+            return this;
+        }
+
+        /**
+         * Sets whether automatic time zone detection is supported on this device.
+         */
+        public Builder setAutoDetectionSupported(boolean supported) {
+            mAutoDetectionSupported = supported;
+            return this;
+        }
+
+        /**
+         * Sets the value of the automatic time zone detection enabled setting for this device.
+         */
+        public Builder setAutoDetectionEnabled(boolean enabled) {
+            mAutoDetectionEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Sets the value of the location mode setting for this user.
+         */
+        public Builder setLocationEnabled(boolean enabled) {
+            mLocationEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Sets the value of the geolocation time zone detection setting for this user.
+         */
+        public Builder setGeoDetectionEnabled(boolean enabled) {
+            mGeoDetectionEnabled = enabled;
+            return this;
+        }
+
+        /** Returns a new {@link ConfigurationInternal}. */
+        @NonNull
+        public ConfigurationInternal build() {
+            return new ConfigurationInternal(this);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
new file mode 100644
index 0000000..91e172c
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.timezonedetector."
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index 0ca36e0..d640323 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -16,24 +16,30 @@
 
 package com.android.server.timezonedetector;
 
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.content.Intent.ACTION_USER_SWITCHED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
-import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.location.LocationManager;
 import android.net.ConnectivityManager;
+import android.os.Handler;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
 
 import java.util.Objects;
 
@@ -42,103 +48,87 @@
  */
 public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategyImpl.Callback {
 
+    private static final String LOG_TAG = "TimeZoneDetectorCallbackImpl";
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
-    private final Context mContext;
-    private final ContentResolver mCr;
-    private final UserManager mUserManager;
+    @NonNull private final Context mContext;
+    @NonNull private final Handler mHandler;
+    @NonNull private final ContentResolver mCr;
+    @NonNull private final UserManager mUserManager;
+    @NonNull private final boolean mGeoDetectionFeatureEnabled;
+    @NonNull private final LocationManager mLocationManager;
+    // @NonNull after setConfigChangeListener() is called.
+    private ConfigurationChangeListener mConfigChangeListener;
 
-    TimeZoneDetectorCallbackImpl(Context context) {
-        mContext = context;
+    TimeZoneDetectorCallbackImpl(@NonNull Context context, @NonNull Handler handler,
+            boolean geoDetectionFeatureEnabled) {
+        mContext = Objects.requireNonNull(context);
+        mHandler = Objects.requireNonNull(handler);
         mCr = context.getContentResolver();
         mUserManager = context.getSystemService(UserManager.class);
+        mLocationManager = context.getSystemService(LocationManager.class);
+        mGeoDetectionFeatureEnabled = geoDetectionFeatureEnabled;
+
+        // Wire up the change listener. All invocations are performed on the mHandler thread.
+
+        // Listen for the user changing / the user's location mode changing.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_USER_SWITCHED);
+        filter.addAction(LocationManager.MODE_CHANGED_ACTION);
+        mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                handleConfigChangeOnHandlerThread();
+            }
+        }, filter, null, mHandler);
+
+        // Add async callbacks for global settings being changed.
+        ContentResolver contentResolver = mContext.getContentResolver();
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
+                new ContentObserver(mHandler) {
+                    public void onChange(boolean selfChange) {
+                        handleConfigChangeOnHandlerThread();
+                    }
+                });
+
+        // Add async callbacks for user scoped location settings being changed.
+        contentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED),
+                true,
+                new ContentObserver(mHandler) {
+                    public void onChange(boolean selfChange) {
+                        handleConfigChangeOnHandlerThread();
+                    }
+                }, UserHandle.USER_ALL);
+    }
+
+    private void handleConfigChangeOnHandlerThread() {
+        if (mConfigChangeListener == null) {
+            Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null");
+        }
+        mConfigChangeListener.onChange();
     }
 
     @Override
-    public TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
-        UserHandle userHandle = UserHandle.of(userId);
-        boolean disallowConfigDateTime =
-                mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
-
-        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userId);
-
-        // Automatic time zone detection is only supported (currently) on devices if there is a
-        // telephony network available.
-        if (!deviceHasTelephonyNetwork()) {
-            builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_SUPPORTED);
-        } else if (disallowConfigDateTime) {
-            builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
-        } else {
-            builder.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED);
-        }
-
-        // TODO(b/149014708) Replace this with real logic when the settings storage is fully
-        // implemented.
-        builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_SUPPORTED);
-
-        // The ability to make manual time zone suggestions can also be restricted by policy. With
-        // the current logic above, this could lead to a situation where a device hardware does not
-        // support auto detection, the device has been forced into "auto" mode by an admin and the
-        // user is unable to disable auto detection.
-        if (disallowConfigDateTime) {
-            builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
-        } else if (isAutoDetectionEnabled()) {
-            builder.setSuggestManualTimeZone(CAPABILITY_NOT_APPLICABLE);
-        } else {
-            builder.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
-        }
-        return builder.build();
+    public void setConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
+        mConfigChangeListener = Objects.requireNonNull(listener);
     }
 
     @Override
-    public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
-        return new TimeZoneConfiguration.Builder()
+    public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
+        return new ConfigurationInternal.Builder(userId)
+                .setUserConfigAllowed(isUserConfigAllowed(userId))
+                .setAutoDetectionSupported(isAutoDetectionSupported())
                 .setAutoDetectionEnabled(isAutoDetectionEnabled())
-                .setGeoDetectionEnabled(isGeoDetectionEnabled())
+                .setLocationEnabled(isLocationEnabled(userId))
+                .setGeoDetectionEnabled(isGeoDetectionEnabled(userId))
                 .build();
     }
 
     @Override
-    public void setConfiguration(
-            @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) {
-        Objects.requireNonNull(configuration);
-        if (!configuration.isComplete()) {
-            throw new IllegalArgumentException("configuration=" + configuration + " not complete");
-        }
-
-        // Avoid writing auto detection config for devices that do not support auto time zone
-        // detection: if we wrote it down then we'd set the default explicitly. That might influence
-        // what happens on later releases that do support auto detection on the same hardware.
-        if (isAutoDetectionSupported()) {
-            final int autoEnabledValue = configuration.isAutoDetectionEnabled() ? 1 : 0;
-            Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, autoEnabledValue);
-
-            final boolean geoTzDetectionEnabledValue = configuration.isGeoDetectionEnabled();
-            // TODO(b/149014708) Write this down to user-scoped settings once implemented.
-        }
-    }
-
-    @Override
-    public boolean isAutoDetectionEnabled() {
-        // To ensure that TimeZoneConfiguration is "complete" for simplicity, devices that do not
-        // support auto detection have safe, hard coded configuration values that make it look like
-        // auto detection is turned off. It is therefore important that false is returned from this
-        // method for devices that do not support auto time zone detection. Such devices will not
-        // have a UI to turn the auto detection on/off. Returning true could prevent the user
-        // entering information manually. On devices that do support auto time detection the default
-        // is to turn auto detection on.
-        if (isAutoDetectionSupported()) {
-            return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean isGeoDetectionEnabled() {
-        // TODO(b/149014708) Read this from user-scoped settings once implemented. The user's
-        //  location toggle will act as an override for this setting, i.e. so that the setting will
-        //  return false if the location toggle is disabled.
-        return false;
+    public @UserIdInt int getCurrentUserId() {
+        return LocalServices.getService(ActivityManagerInternal.class).getCurrentUserId();
     }
 
     @Override
@@ -165,8 +155,55 @@
         alarmManager.setTimeZone(zoneId);
     }
 
+    @Override
+    public void storeConfiguration(TimeZoneConfiguration configuration) {
+        Objects.requireNonNull(configuration);
+
+        // Avoid writing the auto detection enabled setting for devices that do not support auto
+        // time zone detection: if we wrote it down then we'd set the value explicitly, which would
+        // prevent detecting "default" later. That might influence what happens on later releases
+        // that support new types of auto detection on the same hardware.
+        if (isAutoDetectionSupported()) {
+            final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled();
+            setAutoDetectionEnabled(autoDetectionEnabled);
+
+            final int userId = configuration.getUserId();
+            final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
+            setGeoDetectionEnabled(userId, geoTzDetectionEnabled);
+        }
+    }
+
+    private boolean isUserConfigAllowed(@UserIdInt int userId) {
+        UserHandle userHandle = UserHandle.of(userId);
+        return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
+    }
+
     private boolean isAutoDetectionSupported() {
-        return deviceHasTelephonyNetwork();
+        return deviceHasTelephonyNetwork() || mGeoDetectionFeatureEnabled;
+    }
+
+    private boolean isAutoDetectionEnabled() {
+        return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
+    }
+
+    private void setAutoDetectionEnabled(boolean enabled) {
+        Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, enabled ? 1 : 0);
+    }
+
+    private boolean isLocationEnabled(@UserIdInt int userId) {
+        return mLocationManager.isLocationEnabledForUser(UserHandle.of(userId));
+    }
+
+    private boolean isGeoDetectionEnabled(@UserIdInt int userId) {
+        final boolean locationEnabled = isLocationEnabled(userId);
+        return Settings.Secure.getIntForUser(mCr,
+                Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
+                locationEnabled ? 1 : 0 /* defaultValue */, userId) != 0;
+    }
+
+    private void setGeoDetectionEnabled(@UserIdInt int userId, boolean enabled) {
+        Settings.Secure.putIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
+                enabled ? 1 : 0, userId);
     }
 
     private boolean deviceHasTelephonyNetwork() {
@@ -174,4 +211,4 @@
         return mContext.getSystemService(ConnectivityManager.class)
                 .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
     }
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
index fb7a73d..2d50390 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
@@ -27,6 +27,12 @@
  */
 public interface TimeZoneDetectorInternal extends Dumpable.Container {
 
+    /** Adds a listener that will be invoked when time zone detection configuration is changed. */
+    void addConfigurationListener(ConfigurationChangeListener listener);
+
+    /** Returns the {@link ConfigurationInternal} for the current user. */
+    ConfigurationInternal getCurrentUserConfigurationInternal();
+
     /**
      * Suggests the current time zone, determined using geolocation, to the detector. The
      * detector may ignore the signal based on system settings, whether better information is
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index 15412a0..f0ce827 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -20,8 +20,8 @@
 import android.content.Context;
 import android.os.Handler;
 
-import com.android.internal.annotations.VisibleForTesting;
-
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -34,18 +34,26 @@
     @NonNull private final Context mContext;
     @NonNull private final Handler mHandler;
     @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
+    @NonNull private final List<ConfigurationChangeListener> mConfigurationListeners =
+            new ArrayList<>();
 
-    static TimeZoneDetectorInternalImpl create(@NonNull Context context, @NonNull Handler handler,
-            @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
-        return new TimeZoneDetectorInternalImpl(context, handler, timeZoneDetectorStrategy);
-    }
-
-    @VisibleForTesting
     public TimeZoneDetectorInternalImpl(@NonNull Context context, @NonNull Handler handler,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
         mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
+
+        // Wire up a change listener so that any downstream listeners can be notified when
+        // the configuration changes for any reason.
+        mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged);
+    }
+
+    private void handleConfigurationChanged() {
+        synchronized (mConfigurationListeners) {
+            for (ConfigurationChangeListener listener : mConfigurationListeners) {
+                listener.onChange();
+            }
+        }
     }
 
     @Override
@@ -54,6 +62,19 @@
     }
 
     @Override
+    public void addConfigurationListener(ConfigurationChangeListener listener) {
+        synchronized (mConfigurationListeners) {
+            mConfigurationListeners.add(Objects.requireNonNull(listener));
+        }
+    }
+
+    @Override
+    @NonNull
+    public ConfigurationInternal getCurrentUserConfigurationInternal() {
+        return mTimeZoneDetectorStrategy.getCurrentUserConfigurationInternal();
+    }
+
+    @Override
     public void suggestGeolocationTimeZone(
             @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
         Objects.requireNonNull(timeZoneSuggestion);
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index d81f949..7501d9f 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -24,32 +24,24 @@
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
-import android.content.ContentResolver;
 import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
-import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
 import com.android.server.SystemService;
-import com.android.server.timezonedetector.TimeZoneDetectorStrategy.StrategyListener;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.Objects;
 
 /**
@@ -65,6 +57,9 @@
 
     private static final String TAG = "TimeZoneDetectorService";
 
+    /** A compile time switch for enabling / disabling geolocation-based time zone detection. */
+    private static final boolean GEOLOCATION_TIME_ZONE_DETECTION_ENABLED = false;
+
     /**
      * Handles the service lifecycle for {@link TimeZoneDetectorService} and
      * {@link TimeZoneDetectorInternalImpl}.
@@ -80,18 +75,20 @@
             // Obtain / create the shared dependencies.
             Context context = getContext();
             Handler handler = FgThread.getHandler();
+
             TimeZoneDetectorStrategy timeZoneDetectorStrategy =
-                    TimeZoneDetectorStrategyImpl.create(context);
+                    TimeZoneDetectorStrategyImpl.create(
+                            context, handler, GEOLOCATION_TIME_ZONE_DETECTION_ENABLED);
 
             // Create and publish the local service for use by internal callers.
             TimeZoneDetectorInternal internal =
-                    TimeZoneDetectorInternalImpl.create(context, handler, timeZoneDetectorStrategy);
+                    new TimeZoneDetectorInternalImpl(context, handler, timeZoneDetectorStrategy);
             publishLocalService(TimeZoneDetectorInternal.class, internal);
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
-            TimeZoneDetectorService service =
-                    TimeZoneDetectorService.create(context, handler, timeZoneDetectorStrategy);
+            TimeZoneDetectorService service = TimeZoneDetectorService.create(
+                    context, handler, timeZoneDetectorStrategy);
             publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service);
         }
     }
@@ -103,52 +100,38 @@
     private final Handler mHandler;
 
     @NonNull
+    private final CallerIdentityInjector mCallerIdentityInjector;
+
+    @NonNull
     private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
 
-    /**
-     * This sparse array acts as a map from userId to listeners running as that userId. User scoped
-     * as time zone detection configuration is partially user-specific, so different users can
-     * get different configuration.
-     */
     @GuardedBy("mConfigurationListeners")
     @NonNull
-    private final SparseArray<ArrayList<ITimeZoneConfigurationListener>> mConfigurationListeners =
-            new SparseArray<>();
+    private final ArrayList<ITimeZoneConfigurationListener> mConfigurationListeners =
+            new ArrayList<>();
 
     private static TimeZoneDetectorService create(
             @NonNull Context context, @NonNull Handler handler,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
 
-        TimeZoneDetectorService service =
-                new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
-
-        ContentResolver contentResolver = context.getContentResolver();
-        contentResolver.registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
-                new ContentObserver(handler) {
-                    public void onChange(boolean selfChange) {
-                        service.handleAutoTimeZoneConfigChanged();
-                    }
-                });
-        // TODO(b/149014708) Listen for changes to geolocation time zone detection enabled config.
-        //  This should also include listening to the current user and the current user's location
-        //  toggle since the config is user-scoped and the location toggle overrides the geolocation
-        //  time zone enabled setting.
+        CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
+        TimeZoneDetectorService service = new TimeZoneDetectorService(
+                context, handler, callerIdentityInjector, timeZoneDetectorStrategy);
         return service;
     }
 
     @VisibleForTesting
     public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
+            @NonNull CallerIdentityInjector callerIdentityInjector,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
+        mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
         mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
-        mTimeZoneDetectorStrategy.setStrategyListener(new StrategyListener() {
-            @Override
-            public void onConfigurationChanged() {
-                handleConfigurationChanged();
-            }
-        });
+
+        // Wire up a change listener so that ITimeZoneConfigurationListeners can be notified when
+        // the configuration changes for any reason.
+        mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged);
     }
 
     @Override
@@ -156,26 +139,12 @@
     public TimeZoneCapabilities getCapabilities() {
         enforceManageTimeZoneDetectorConfigurationPermission();
 
-        int userId = UserHandle.getCallingUserId();
-        long token = Binder.clearCallingIdentity();
+        int userId = mCallerIdentityInjector.getCallingUserId();
+        long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            return mTimeZoneDetectorStrategy.getCapabilities(userId);
+            return mTimeZoneDetectorStrategy.getConfigurationInternal(userId).createCapabilities();
         } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    @NonNull
-    public TimeZoneConfiguration getConfiguration() {
-        enforceManageTimeZoneDetectorConfigurationPermission();
-
-        int userId = UserHandle.getCallingUserId();
-        long token = Binder.clearCallingIdentity();
-        try {
-            return mTimeZoneDetectorStrategy.getConfiguration(userId);
-        } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
@@ -184,12 +153,16 @@
         enforceManageTimeZoneDetectorConfigurationPermission();
         Objects.requireNonNull(configuration);
 
-        int userId = UserHandle.getCallingUserId();
-        long token = Binder.clearCallingIdentity();
+        int callingUserId = mCallerIdentityInjector.getCallingUserId();
+        if (callingUserId != configuration.getUserId()) {
+            return false;
+        }
+
+        long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            return mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration);
+            return mTimeZoneDetectorStrategy.updateConfiguration(configuration);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
@@ -197,25 +170,17 @@
     public void addConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
         enforceManageTimeZoneDetectorConfigurationPermission();
         Objects.requireNonNull(listener);
-        int userId = UserHandle.getCallingUserId();
 
         synchronized (mConfigurationListeners) {
-            ArrayList<ITimeZoneConfigurationListener> listeners =
-                    mConfigurationListeners.get(userId);
-            if (listeners != null && listeners.contains(listener)) {
+            if (mConfigurationListeners.contains(listener)) {
                 return;
             }
             try {
-                if (listeners == null) {
-                    listeners = new ArrayList<>(1);
-                    mConfigurationListeners.put(userId, listeners);
-                }
-
                 // Ensure the reference to the listener will be removed if the client process dies.
                 listener.asBinder().linkToDeath(this, 0 /* flags */);
 
                 // Only add the listener if we can linkToDeath().
-                listeners.add(listener);
+                mConfigurationListeners.add(listener);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e);
             }
@@ -226,19 +191,16 @@
     public void removeConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
         enforceManageTimeZoneDetectorConfigurationPermission();
         Objects.requireNonNull(listener);
-        int userId = UserHandle.getCallingUserId();
 
         synchronized (mConfigurationListeners) {
             boolean removedListener = false;
-            ArrayList<ITimeZoneConfigurationListener> userListeners =
-                    mConfigurationListeners.get(userId);
-            if (userListeners.remove(listener)) {
+            if (mConfigurationListeners.remove(listener)) {
                 // Stop listening for the client process to die.
                 listener.asBinder().unlinkToDeath(this, 0 /* flags */);
                 removedListener = true;
             }
             if (!removedListener) {
-                Slog.w(TAG, "Client asked to remove listenener=" + listener
+                Slog.w(TAG, "Client asked to remove listener=" + listener
                         + ", but no listeners were removed."
                         + " mConfigurationListeners=" + mConfigurationListeners);
             }
@@ -259,19 +221,14 @@
     public void binderDied(IBinder who) {
         synchronized (mConfigurationListeners) {
             boolean removedListener = false;
-            final int userCount = mConfigurationListeners.size();
-            for (int i = 0; i < userCount; i++) {
-                ArrayList<ITimeZoneConfigurationListener> userListeners =
-                        mConfigurationListeners.valueAt(i);
-                Iterator<ITimeZoneConfigurationListener> userListenerIterator =
-                        userListeners.iterator();
-                while (userListenerIterator.hasNext()) {
-                    ITimeZoneConfigurationListener userListener = userListenerIterator.next();
-                    if (userListener.asBinder().equals(who)) {
-                        userListenerIterator.remove();
-                        removedListener = true;
-                        break;
-                    }
+            final int listenerCount = mConfigurationListeners.size();
+            for (int listenerIndex = listenerCount - 1; listenerIndex >= 0; listenerIndex--) {
+                ITimeZoneConfigurationListener listener =
+                        mConfigurationListeners.get(listenerIndex);
+                if (listener.asBinder().equals(who)) {
+                    mConfigurationListeners.remove(listenerIndex);
+                    removedListener = true;
+                    break;
                 }
             }
             if (!removedListener) {
@@ -283,42 +240,25 @@
     }
 
     void handleConfigurationChanged() {
-        // Note: we could trigger an async time zone detection operation here via a call to
-        // handleAutoTimeZoneConfigChanged(), but that is triggered in response to the underlying
-        // setting value changing so it is currently unnecessary. If we get to a point where all
-        // configuration changes are guaranteed to happen in response to an updateConfiguration()
-        // call, then we can remove that path and call it here instead.
-
         // Configuration has changed, but each user may have a different view of the configuration.
         // It's possible that this will cause unnecessary notifications but that shouldn't be a
         // problem.
         synchronized (mConfigurationListeners) {
-            final int userCount = mConfigurationListeners.size();
-            for (int userIndex = 0; userIndex < userCount; userIndex++) {
-                int userId = mConfigurationListeners.keyAt(userIndex);
-                TimeZoneConfiguration configuration =
-                        mTimeZoneDetectorStrategy.getConfiguration(userId);
-
-                ArrayList<ITimeZoneConfigurationListener> listeners =
-                        mConfigurationListeners.valueAt(userIndex);
-                final int listenerCount = listeners.size();
-                for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
-                    ITimeZoneConfigurationListener listener = listeners.get(listenerIndex);
-                    try {
-                        listener.onChange(configuration);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Unable to notify listener=" + listener
-                                + " for userId=" + userId
-                                + " of updated configuration=" + configuration, e);
-                    }
+            final int listenerCount = mConfigurationListeners.size();
+            for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
+                ITimeZoneConfigurationListener listener =
+                        mConfigurationListeners.get(listenerIndex);
+                try {
+                    listener.onChange();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to notify listener=" + listener, e);
                 }
             }
         }
     }
 
     /** Provided for command-line access. This is not exposed as a binder API. */
-    void suggestGeolocationTimeZone(
-            @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
+    void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
         enforceSuggestGeolocationTimeZonePermission();
         Objects.requireNonNull(timeZoneSuggestion);
 
@@ -331,12 +271,12 @@
         enforceSuggestManualTimeZonePermission();
         Objects.requireNonNull(timeZoneSuggestion);
 
-        int userId = UserHandle.getCallingUserId();
-        long token = Binder.clearCallingIdentity();
+        int userId = mCallerIdentityInjector.getCallingUserId();
+        long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
             return mTimeZoneDetectorStrategy.suggestManualTimeZone(userId, timeZoneSuggestion);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
@@ -358,12 +298,6 @@
         ipw.flush();
     }
 
-    /** Internal method for handling the auto time zone configuration being changed. */
-    @VisibleForTesting
-    public void handleAutoTimeZoneConfigChanged() {
-        mHandler.post(mTimeZoneDetectorStrategy::handleAutoTimeZoneConfigChanged);
-    }
-
     private void enforceManageTimeZoneDetectorConfigurationPermission() {
         // TODO Switch to a dedicated MANAGE_TIME_AND_ZONE_CONFIGURATION permission.
         mContext.enforceCallingPermission(
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index c5b7e39..f944c56 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -19,51 +19,84 @@
 import android.annotation.UserIdInt;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
 import android.util.IndentingPrintWriter;
 
 /**
- * The interface for the class that implements the time detection algorithm used by the
- * {@link TimeZoneDetectorService}.
+ * The interface for the class that is responsible for setting the time zone on a device, used by
+ * {@link TimeZoneDetectorService} and {@link TimeZoneDetectorInternal}.
  *
- * <p>The strategy uses suggestions to decide whether to modify the device's time zone setting
- * and what to set it to.
+ * <p>The strategy receives suggestions, which it may use to modify the device's time zone setting.
+ * Suggestions are acted on or ignored as needed, depending on previously received suggestions and
+ * the current user's configuration (see {@link ConfigurationInternal}).
  *
- * <p>Most calls will be handled by a single thread, but that is not true for all calls. For example
- * {@link #dump(IndentingPrintWriter, String[])}) may be called on a different thread concurrently
- * with other operations so implementations must still handle thread safety.
+ * <p>Devices can have zero, one or two automatic time zone detection algorithm available at any
+ * point in time.
+ *
+ * <p>The two automatic detection algorithms supported are "telephony" and "geolocation". Algorithm
+ * availability and use depends on several factors:
+ * <ul>
+ * <li>Telephony is only available on devices with a telephony stack.
+ * <li>Geolocation is also optional and configured at image creation time. When enabled on a
+ * device, its availability depends on the current user's settings, so switching between users can
+ * change the automatic algorithm used by the device.</li>
+ * </ul>
+ *
+ * <p>If there are no automatic time zone detections algorithms available then the user can usually
+ * change the device time zone manually. Under most circumstances the current user can turn
+ * automatic time zone detection on or off, or choose the algorithm via settings.
+ *
+ * <p>Telephony detection is independent of the current user. The device keeps track of the most
+ * recent telephony suggestion from each slotIndex. When telephony detection is in use, the highest
+ * scoring suggestion is used to set the device time zone based on a scoring algorithm. If several
+ * slotIndexes provide the same score then the slotIndex with the lowest numeric value "wins". If
+ * the situation changes and it is no longer possible to be confident about the time zone,
+ * slotIndexes must have an empty suggestion submitted in order to "withdraw" their previous
+ * suggestion otherwise it will remain in use.
+ *
+ * <p>Geolocation detection is dependent on the current user and their settings. The device retains
+ * at most one geolocation suggestion. Generally, use of a device's location is dependent on the
+ * user's "location toggle", but even when that is enabled the user may choose to enable / disable
+ * the use of geolocation for device time zone detection. If the current user changes to one that
+ * does not have geolocation detection enabled, or the user turns off geolocation detection, then
+ * the strategy discards the latest geolocation suggestion. Devices that lose a location fix must
+ * have an empty suggestion submitted in order to "withdraw" their previous suggestion otherwise it
+ * will remain in use.
+ *
+ * <p>Threading:
+ *
+ * <p>Suggestion calls with a void return type may be handed off to a separate thread and handled
+ * asynchronously. Synchronous calls like {@link #getCurrentUserConfigurationInternal()}, and debug
+ * calls like {@link #dump(IndentingPrintWriter, String[])}, may be called on a different thread
+ * concurrently with other operations.
  *
  * @hide
  */
 public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container {
 
-    /** A listener for strategy events. */
-    interface StrategyListener {
-        /**
-         * Invoked when configuration has been changed.
-         */
-        void onConfigurationChanged();
-    }
+    /**
+     * Sets a listener that will be triggered whenever time zone detection configuration is
+     * changed.
+     */
+    void addConfigChangeListener(@NonNull ConfigurationChangeListener listener);
 
-    /** Sets the listener that enables the strategy to communicate with the surrounding service. */
-    void setStrategyListener(@NonNull StrategyListener listener);
-
-    /** Returns the user's time zone capabilities. */
+    /** Returns the user's time zone configuration. */
     @NonNull
-    TimeZoneCapabilities getCapabilities(@UserIdInt int userId);
+    ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
 
     /**
-     * Returns the configuration that controls time zone detector behavior.
+     * Returns the configuration that controls time zone detector behavior for the current user.
      */
     @NonNull
-    TimeZoneConfiguration getConfiguration(@UserIdInt int userId);
+    ConfigurationInternal getCurrentUserConfigurationInternal();
 
     /**
-     * Updates the configuration settings that control time zone detector behavior.
+     * Updates the configuration properties that control a device's time zone behavior.
+     *
+     * <p>This method returns {@code true} if the configuration was changed,
+     * {@code false} otherwise.
      */
-    boolean updateConfiguration(
-            @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration);
+    boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration);
 
     /**
      * Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
@@ -85,9 +118,4 @@
      * suggestion.
      */
     void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
-
-    /**
-     * Called when there has been a change to the automatic time zone detection configuration.
-     */
-    void handleAutoTimeZoneConfigChanged();
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index d1369a2..8a42b18 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -20,10 +20,7 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
 import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.TimeZoneConfiguration.PROPERTY_AUTO_DETECTION_ENABLED;
-import static android.app.timezonedetector.TimeZoneConfiguration.PROPERTY_GEO_DETECTION_ENABLED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,6 +30,7 @@
 import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
 import android.content.Context;
+import android.os.Handler;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
 import android.util.Slog;
@@ -45,15 +43,7 @@
 import java.util.Objects;
 
 /**
- * An implementation of {@link TimeZoneDetectorStrategy} that handle telephony and manual
- * suggestions. Suggestions are acted on or ignored as needed, dependent on the current "auto time
- * zone detection" setting.
- *
- * <p>For automatic detection, it keeps track of the most recent telephony suggestion from each
- * slotIndex and it uses the best suggestion based on a scoring algorithm. If several slotIndexes
- * provide the same score then the slotIndex with the lowest numeric value "wins". If the situation
- * changes and it is no longer possible to be confident about the time zone, slotIndexes must have
- * an empty suggestion submitted in order to "withdraw" their previous suggestion.
+ * The real implementation of {@link TimeZoneDetectorStrategy}.
  *
  * <p>Most public methods are marked synchronized to ensure thread safety around internal state.
  */
@@ -61,48 +51,27 @@
 
     /**
      * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device configuration / settings
-     * / system properties. It can be faked for testing different scenarios.
+     * / system properties. It can be faked for testing.
      *
      * <p>Note: Because the settings / system properties-derived values can currently be modified
-     * independently and from different threads (and processes!), their use are prone to race
-     * conditions. That will be true until the responsibility for setting their values is moved to
-     * {@link TimeZoneDetectorStrategyImpl} (which is thread safe).
+     * independently and from different threads (and processes!), their use is prone to race
+     * conditions.
      */
     @VisibleForTesting
     public interface Callback {
 
         /**
-         * Returns the capabilities for the user.
+         * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
+         * changes that could affect time zone detection. This is invoked during system server
+         * setup.
          */
-        @NonNull
-        TimeZoneCapabilities getCapabilities(@UserIdInt int userId);
+        void setConfigChangeListener(@NonNull ConfigurationChangeListener listener);
 
-        /**
-         * Returns the configuration for the user.
-         * @param userId
-         */
-        @NonNull
-        TimeZoneConfiguration getConfiguration(int userId);
+        /** Returns the current user at the instant it is called. */
+        @UserIdInt int getCurrentUserId();
 
-        /**
-         * Sets the configuration for the user. This method handles storage only, the configuration
-         * must have been validated by the caller and be complete.
-         *
-         * @throws IllegalArgumentException if {@link TimeZoneConfiguration#isComplete()}
-         *     returns {@code false}
-         */
-        void setConfiguration(@UserIdInt int userId, @NonNull TimeZoneConfiguration configuration);
-
-        /**
-         * Returns true if automatic time zone detection is currently enabled.
-         */
-        boolean isAutoDetectionEnabled();
-
-        /**
-         * Returns whether geolocation can be used for time zone detection when {@link
-         * #isAutoDetectionEnabled()} returns {@code true}.
-         */
-        boolean isGeoDetectionEnabled();
+        /** Returns the {@link ConfigurationInternal} for the specified user. */
+        ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
 
         /**
          * Returns true if the device has had an explicit time zone set.
@@ -118,6 +87,13 @@
          * Sets the device's time zone.
          */
         void setDeviceTimeZone(@NonNull String zoneId);
+
+        /**
+         * Stores the configuration properties contained in {@code newConfiguration}.
+         * All checks about user capabilities must be done by the caller and
+         * {@link TimeZoneConfiguration#isComplete()} must be {@code true}.
+         */
+        void storeConfiguration(TimeZoneConfiguration newConfiguration);
     }
 
     private static final String LOG_TAG = "TimeZoneDetectorStrategy";
@@ -189,9 +165,9 @@
     @NonNull
     private final Callback mCallback;
 
-    /** Non-null after {@link #setStrategyListener(StrategyListener)} is called. */
-    @Nullable
-    private StrategyListener mListener;
+    @GuardedBy("this")
+    @NonNull
+    private List<ConfigurationChangeListener> mConfigChangeListeners = new ArrayList<>();
 
     /**
      * A log that records the decisions / decision metadata that affected the device's time zone.
@@ -211,7 +187,8 @@
             new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
     /**
-     * The latest geolocation suggestion received.
+     * The latest geolocation suggestion received. If the user disabled geolocation time zone
+     * detection then the latest suggestion is cleared.
      */
     @GuardedBy("this")
     private ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion =
@@ -223,113 +200,120 @@
     /**
      * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
      */
-    public static TimeZoneDetectorStrategyImpl create(Context context) {
-        Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
-        return new TimeZoneDetectorStrategyImpl(timeZoneDetectionServiceHelper);
+    public static TimeZoneDetectorStrategyImpl create(
+            @NonNull Context context, @NonNull Handler handler,
+            boolean geolocationTimeZoneDetectionEnabled) {
+
+        TimeZoneDetectorCallbackImpl callback = new TimeZoneDetectorCallbackImpl(
+                context, handler, geolocationTimeZoneDetectionEnabled);
+        return new TimeZoneDetectorStrategyImpl(callback);
     }
 
     @VisibleForTesting
-    public TimeZoneDetectorStrategyImpl(Callback callback) {
+    public TimeZoneDetectorStrategyImpl(@NonNull Callback callback) {
         mCallback = Objects.requireNonNull(callback);
+        mCallback.setConfigChangeListener(this::handleConfigChanged);
     }
 
     /**
-     * Sets a listener that allows the strategy to communicate with the surrounding service. This
-     * must be called before the instance is used and must only be called once.
+     * Adds a listener that allows the strategy to communicate with the surrounding service /
+     * internal. This must be called before the instance is used.
      */
     @Override
-    public synchronized void setStrategyListener(@NonNull StrategyListener listener) {
-        if (mListener != null) {
-            throw new IllegalStateException("Strategy already has a listener");
-        }
-        mListener = Objects.requireNonNull(listener);
+    public synchronized void addConfigChangeListener(
+            @NonNull ConfigurationChangeListener listener) {
+        Objects.requireNonNull(listener);
+        mConfigChangeListeners.add(listener);
     }
 
     @Override
     @NonNull
-    public synchronized TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
-        return mCallback.getCapabilities(userId);
+    public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
+        return mCallback.getConfigurationInternal(userId);
     }
 
     @Override
     @NonNull
-    public synchronized TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
-        return mCallback.getConfiguration(userId);
+    public synchronized ConfigurationInternal getCurrentUserConfigurationInternal() {
+        int currentUserId = mCallback.getCurrentUserId();
+        return getConfigurationInternal(currentUserId);
     }
 
     @Override
     public synchronized boolean updateConfiguration(
-            @UserIdInt int userId, @NonNull TimeZoneConfiguration configurationChanges) {
-        Objects.requireNonNull(configurationChanges);
+            @NonNull TimeZoneConfiguration requestedConfiguration) {
+        Objects.requireNonNull(requestedConfiguration);
 
-        // Validate the requested configuration changes before applying any of them.
-        TimeZoneCapabilities capabilities = mCallback.getCapabilities(userId);
-        boolean canManageTimeZoneDetection =
-                capabilities.getConfigureAutoDetectionEnabled() >= CAPABILITY_NOT_APPLICABLE;
-        if (!canManageTimeZoneDetection
-                && containsAutoTimeDetectionProperties(configurationChanges)) {
+        int userId = requestedConfiguration.getUserId();
+        TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
+
+        // Create a new configuration builder, and copy across the mutable properties users are
+        // able to modify. Other properties are therefore ignored.
+        final TimeZoneConfiguration newConfiguration =
+                capabilities.applyUpdate(requestedConfiguration);
+        if (newConfiguration == null) {
+            // The changes could not be made due to
             return false;
         }
 
-        // Create a complete configuration by merging the existing and new (possibly partial)
-        // configuration.
-        final TimeZoneConfiguration oldConfiguration = mCallback.getConfiguration(userId);
-        final TimeZoneConfiguration newConfiguration =
-                new TimeZoneConfiguration.Builder(oldConfiguration)
-                        .mergeProperties(configurationChanges)
-                        .build();
+        // Store the configuration / notify as needed. This will cause the mCallback to invoke
+        // handleConfigChanged() asynchronously.
+        mCallback.storeConfiguration(newConfiguration);
 
-        // Set the configuration / notify as needed.
-        boolean configurationChanged = !oldConfiguration.equals(newConfiguration);
-        if (configurationChanged) {
-            mCallback.setConfiguration(userId, newConfiguration);
-
-            String logMsg = "Configuration changed:"
-                    + "oldConfiguration=" + oldConfiguration
-                    + ", configuration=" + configurationChanges
-                    + ", newConfiguration=" + newConfiguration;
-            mTimeZoneChangesLog.log(logMsg);
-            if (DBG) {
-                Slog.d(LOG_TAG, logMsg);
-            }
-            mListener.onConfigurationChanged();
+        TimeZoneConfiguration oldConfiguration = capabilities.getConfiguration();
+        String logMsg = "Configuration changed:"
+                + " oldConfiguration=" + oldConfiguration
+                + ", newConfiguration=" + newConfiguration;
+        mTimeZoneChangesLog.log(logMsg);
+        if (DBG) {
+            Slog.d(LOG_TAG, logMsg);
         }
         return true;
     }
 
-    private static boolean containsAutoTimeDetectionProperties(
-            @NonNull TimeZoneConfiguration configuration) {
-        return configuration.hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
-                || configuration.hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
-    }
-
     @Override
     public synchronized void suggestGeolocationTimeZone(
             @NonNull GeolocationTimeZoneSuggestion suggestion) {
+
+        int currentUserId = mCallback.getCurrentUserId();
+        ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
         if (DBG) {
-            Slog.d(LOG_TAG, "Geolocation suggestion received. newSuggestion=" + suggestion);
+            Slog.d(LOG_TAG, "Geolocation suggestion received."
+                    + " currentUserConfig=" + currentUserConfig
+                    + " newSuggestion=" + suggestion);
         }
-
         Objects.requireNonNull(suggestion);
-        mLatestGeoLocationSuggestion.set(suggestion);
 
-        // Now perform auto time zone detection. The new suggestion may be used to modify the time
-        // zone setting.
-        if (mCallback.isGeoDetectionEnabled()) {
+        if (currentUserConfig.getGeoDetectionEnabledBehavior()) {
+            // Only store a geolocation suggestion if geolocation detection is currently enabled.
+            mLatestGeoLocationSuggestion.set(suggestion);
+
+            // Now perform auto time zone detection. The new suggestion may be used to modify the
+            // time zone setting.
             String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
-            doAutoTimeZoneDetection(reason);
+            doAutoTimeZoneDetection(currentUserConfig, reason);
         }
     }
 
     @Override
     public synchronized boolean suggestManualTimeZone(
             @UserIdInt int userId, @NonNull ManualTimeZoneSuggestion suggestion) {
+
+        int currentUserId = mCallback.getCurrentUserId();
+        if (userId != currentUserId) {
+            Slog.w(LOG_TAG, "Manual suggestion received but user != current user, userId=" + userId
+                    + " suggestion=" + suggestion);
+
+            // Only listen to changes from the current user.
+            return false;
+        }
+
         Objects.requireNonNull(suggestion);
 
         String timeZoneId = suggestion.getZoneId();
         String cause = "Manual time suggestion received: suggestion=" + suggestion;
 
-        TimeZoneCapabilities capabilities = mCallback.getCapabilities(userId);
+        TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
         if (capabilities.getSuggestManualTimeZone() != CAPABILITY_POSSESSED) {
             Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
                     + ", capabilities=" + capabilities
@@ -345,8 +329,12 @@
     @Override
     public synchronized void suggestTelephonyTimeZone(
             @NonNull TelephonyTimeZoneSuggestion suggestion) {
+
+        int currentUserId = mCallback.getCurrentUserId();
+        ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
         if (DBG) {
-            Slog.d(LOG_TAG, "Telephony suggestion received. newSuggestion=" + suggestion);
+            Slog.d(LOG_TAG, "Telephony suggestion received. currentUserConfig=" + currentUserConfig
+                    + " newSuggestion=" + suggestion);
         }
         Objects.requireNonNull(suggestion);
 
@@ -360,9 +348,9 @@
 
         // Now perform auto time zone detection. The new suggestion may be used to modify the time
         // zone setting.
-        if (!mCallback.isGeoDetectionEnabled()) {
+        if (!currentUserConfig.getGeoDetectionEnabledBehavior()) {
             String reason = "New telephony time zone suggested. suggestion=" + suggestion;
-            doAutoTimeZoneDetection(reason);
+            doAutoTimeZoneDetection(currentUserConfig, reason);
         }
     }
 
@@ -392,15 +380,15 @@
      * Performs automatic time zone detection.
      */
     @GuardedBy("this")
-    private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
-        if (!mCallback.isAutoDetectionEnabled()) {
-            // Avoid doing unnecessary work with this (race-prone) check.
+    private void doAutoTimeZoneDetection(
+            @NonNull ConfigurationInternal currentUserConfig, @NonNull String detectionReason) {
+        if (!currentUserConfig.getAutoDetectionEnabledBehavior()) {
+            // Avoid doing unnecessary work.
             return;
         }
 
-        // Use the right suggestions based on the current configuration. This check is potentially
-        // race-prone until this value is set via a call to TimeZoneDetectorStrategy.
-        if (mCallback.isGeoDetectionEnabled()) {
+        // Use the right suggestions based on the current configuration.
+        if (currentUserConfig.getGeoDetectionEnabledBehavior()) {
             doGeolocationTimeZoneDetection(detectionReason);
         } else  {
             doTelephonyTimeZoneDetection(detectionReason);
@@ -480,35 +468,18 @@
 
         // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
         // zone ID.
-        String newZoneId = bestTelephonySuggestion.suggestion.getZoneId();
-        if (newZoneId == null) {
+        String zoneId = bestTelephonySuggestion.suggestion.getZoneId();
+        if (zoneId == null) {
             Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
                     + " bestTelephonySuggestion=" + bestTelephonySuggestion
                     + " detectionReason=" + detectionReason);
             return;
         }
 
-        String zoneId = bestTelephonySuggestion.suggestion.getZoneId();
         String cause = "Found good suggestion."
                 + ", bestTelephonySuggestion=" + bestTelephonySuggestion
                 + ", detectionReason=" + detectionReason;
-        setAutoDeviceTimeZoneIfRequired(zoneId, cause);
-    }
-
-    @GuardedBy("this")
-    private void setAutoDeviceTimeZoneIfRequired(@NonNull String newZoneId, @NonNull String cause) {
-        Objects.requireNonNull(newZoneId);
-        Objects.requireNonNull(cause);
-
-        if (!mCallback.isAutoDetectionEnabled()) {
-            if (DBG) {
-                Slog.d(LOG_TAG, "Auto time zone detection is not enabled."
-                        + ", newZoneId=" + newZoneId
-                        + ", cause=" + cause);
-            }
-            return;
-        }
-        setDeviceTimeZoneIfRequired(newZoneId, cause);
+        setDeviceTimeZoneIfRequired(zoneId, cause);
     }
 
     @GuardedBy("this")
@@ -582,13 +553,39 @@
         return findBestTelephonySuggestion();
     }
 
-    @Override
-    public synchronized void handleAutoTimeZoneConfigChanged() {
+    private synchronized void handleConfigChanged() {
         if (DBG) {
-            Slog.d(LOG_TAG, "handleAutoTimeZoneConfigChanged()");
+            Slog.d(LOG_TAG, "handleConfigChanged()");
         }
 
-        doAutoTimeZoneDetection("handleAutoTimeZoneConfigChanged()");
+        clearGeolocationSuggestionIfNeeded();
+
+        for (ConfigurationChangeListener listener : mConfigChangeListeners) {
+            listener.onChange();
+        }
+    }
+
+    @GuardedBy("this")
+    private void clearGeolocationSuggestionIfNeeded() {
+        // This method is called whenever the user changes or the config for any user changes. We
+        // don't know what happened, so we capture the current user's config, check to see if we
+        // need to clear state associated with a previous user, and rerun detection.
+        int currentUserId = mCallback.getCurrentUserId();
+        ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
+
+        GeolocationTimeZoneSuggestion latestGeoLocationSuggestion =
+                mLatestGeoLocationSuggestion.get();
+        if (latestGeoLocationSuggestion != null
+                && !currentUserConfig.getGeoDetectionEnabledBehavior()) {
+            // The current user's config has geodetection disabled, so clear the latest suggestion.
+            // This is done to ensure we only ever keep a geolocation suggestion if the user has
+            // said it is ok to do so.
+            mLatestGeoLocationSuggestion.set(null);
+            mTimeZoneChangesLog.log(
+                    "clearGeolocationSuggestionIfNeeded: Cleared latest Geolocation suggestion.");
+        }
+
+        doAutoTimeZoneDetection(currentUserConfig, "clearGeolocationSuggestionIfNeeded()");
     }
 
     @Override
@@ -604,11 +601,14 @@
         ipw.println("TimeZoneDetectorStrategy:");
 
         ipw.increaseIndent(); // level 1
-        ipw.println("mCallback.isAutoDetectionEnabled()=" + mCallback.isAutoDetectionEnabled());
+        int currentUserId = mCallback.getCurrentUserId();
+        ipw.println("mCallback.getCurrentUserId()=" + currentUserId);
+        ConfigurationInternal configuration = mCallback.getConfigurationInternal(currentUserId);
+        ipw.println("mCallback.getConfiguration(currentUserId)=" + configuration);
+        ipw.println("[Capabilities=" + configuration.createCapabilities() + "]");
         ipw.println("mCallback.isDeviceTimeZoneInitialized()="
                 + mCallback.isDeviceTimeZoneInitialized());
         ipw.println("mCallback.getDeviceTimeZone()=" + mCallback.getDeviceTimeZone());
-        ipw.println("mCallback.isGeoDetectionEnabled()=" + mCallback.isGeoDetectionEnabled());
 
         ipw.println("Time zone change log:");
         ipw.increaseIndent(); // level 2
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 964de13..56261c4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -110,9 +110,13 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTAINERS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityRecordProto.ALL_DRAWN;
@@ -145,9 +149,6 @@
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SAVED_STATE;
@@ -1096,15 +1097,15 @@
 
     private void scheduleActivityMovedToDisplay(int displayId, Configuration config) {
         if (!attachedToProcess()) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.w(TAG,
-                    "Can't report activity moved to display - client not running, activityRecord="
-                            + this + ", displayId=" + displayId);
+            ProtoLog.w(WM_DEBUG_SWITCH, "Can't report activity moved "
+                    + "to display - client not running, activityRecord=%s, displayId=%d",
+                    this, displayId);
             return;
         }
         try {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
-                    "Reporting activity moved to display" + ", activityRecord=" + this
-                            + ", displayId=" + displayId + ", config=" + config);
+            ProtoLog.v(WM_DEBUG_SWITCH, "Reporting activity moved to "
+                    + "display, activityRecord=%s, displayId=%d, config=%s", this, displayId,
+                    config);
 
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                     MoveToDisplayItem.obtain(displayId, config));
@@ -1115,14 +1116,13 @@
 
     private void scheduleConfigurationChanged(Configuration config) {
         if (!attachedToProcess()) {
-            if (DEBUG_CONFIGURATION) Slog.w(TAG,
-                    "Can't report activity configuration update - client not running"
-                            + ", activityRecord=" + this);
+            ProtoLog.w(WM_DEBUG_CONFIGURATION, "Can't report activity configuration "
+                    + "update - client not running, activityRecord=%s", this);
             return;
         }
         try {
-            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + ", config: "
-                    + config);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
+                    + "config: %s", this, config);
 
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                     ActivityConfigurationChangeItem.obtain(config));
@@ -1949,10 +1949,8 @@
             startingWindow = null;
             startingDisplayed = false;
             if (surface == null) {
-                ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
-                        "startingWindow was set but startingSurface==null, couldn't "
-                                + "remove");
-
+                ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but "
+                        + "startingSurface==null, couldn't remove");
                 return;
             }
         } else {
@@ -1962,9 +1960,10 @@
             return;
         }
 
+
         ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Schedule remove starting %s startingWindow=%s"
-                        + " startingView=%s Callers=%s",
-                this, startingWindow, startingSurface, Debug.getCallers(5));
+                + " startingView=%s Callers=%s", this, startingWindow, startingSurface,
+                Debug.getCallers(5));
 
 
         // Use the same thread to remove the window as we used to add it, as otherwise we end up
@@ -2399,9 +2398,8 @@
      */
     boolean moveFocusableActivityToTop(String reason) {
         if (!isFocusable()) {
-            if (DEBUG_FOCUS) {
-                Slog.d(TAG_FOCUS, "moveActivityStackToFront: unfocusable activity=" + this);
-            }
+            ProtoLog.d(WM_DEBUG_FOCUS, "moveActivityStackToFront: unfocusable "
+                    + "activity=%s", this);
             return false;
         }
 
@@ -2414,15 +2412,11 @@
 
         if (mRootWindowContainer.getTopResumedActivity() == this
                 && getDisplayContent().mFocusedApp == this) {
-            if (DEBUG_FOCUS) {
-                Slog.d(TAG_FOCUS, "moveActivityStackToFront: already on top, activity=" + this);
-            }
+            ProtoLog.d(WM_DEBUG_FOCUS, "moveActivityStackToFront: already on top, "
+                    + "activity=%s", this);
             return !isState(RESUMED);
         }
-
-        if (DEBUG_FOCUS) {
-            Slog.d(TAG_FOCUS, "moveActivityStackToFront: activity=" + this);
-        }
+        ProtoLog.d(WM_DEBUG_FOCUS, "moveActivityStackToFront: activity=%s", this);
 
         stack.moveToFront(reason, task);
         // Report top activity change to tracking services and WM
@@ -2798,10 +2792,8 @@
             mRootWindowContainer.resumeFocusedStacksTopActivities();
         }
 
-        if (DEBUG_CONTAINERS) {
-            Slog.d(TAG_CONTAINERS, "destroyIfPossible: r=" + this + " destroy returned removed="
-                    + activityRemoved);
-        }
+        ProtoLog.d(WM_DEBUG_CONTAINERS, "destroyIfPossible: r=%s destroy returned "
+                + "removed=%s", this, activityRemoved);
 
         return activityRemoved;
     }
@@ -2935,10 +2927,9 @@
         finishActivityResults(Activity.RESULT_CANCELED,
                 null /* resultData */, null /* resultGrants */);
         makeFinishingLocked();
-        if (ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE) {
-            Slog.i(TAG_ADD_REMOVE, "Removing activity " + this + " from stack, reason="
-                    + reason + ", callers=" + Debug.getCallers(5));
-        }
+
+        ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing activity %s from stack, reason= %s "
+                        + "callers=%s", this, reason, Debug.getCallers(5));
 
         takeFromHistory();
         removeTimeouts();
@@ -2978,7 +2969,7 @@
     void destroyed(String reason) {
         removeDestroyTimeout();
 
-        if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS, "activityDestroyedLocked: r=" + this);
+        ProtoLog.d(WM_DEBUG_CONTAINERS, "activityDestroyedLocked: r=%s", this);
 
         if (!isState(DESTROYING, DESTROYED)) {
             throw new IllegalStateException(
@@ -3179,12 +3170,9 @@
             remove = false;
         }
         if (remove) {
-            if (ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE || DEBUG_CLEANUP) {
-                Slog.i(TAG_ADD_REMOVE, "Removing activity " + this
-                        + " hasSavedState=" + mHaveState + " stateNotNeeded=" + stateNotNeeded
-                        + " finishing=" + finishing + " state=" + mState
-                        + " callers=" + Debug.getCallers(5));
-            }
+            ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing activity %s hasSavedState=%b "
+                    + "stateNotNeeded=%s finishing=%b state=%s callers=%s", this,
+                    mHaveState, stateNotNeeded, finishing, mState, Debug.getCallers(5));
             if (!finishing || (app != null && app.isRemoved())) {
                 Slog.w(TAG, "Force removing " + this + ": app died, no saved state");
                 EventLogTags.writeWmFinishActivity(mUserId, System.identityHashCode(this),
@@ -7007,27 +6995,27 @@
             boolean ignoreVisibility) {
         final Task stack = getRootTask();
         if (stack.mConfigWillChange) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Skipping config check (will change): " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
+                    + "(will change): %s", this);
             return true;
         }
 
         // We don't worry about activities that are finishing.
         if (finishing) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Configuration doesn't matter in finishing " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration doesn't matter "
+                    + "in finishing %s", this);
             stopFreezingScreenLocked(false);
             return true;
         }
 
         if (!ignoreVisibility && (mState == STOPPING || mState == STOPPED || !shouldBeVisible())) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Skipping config check invisible: " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
+                    + "invisible: %s", this);
             return true;
         }
 
-        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                "Ensuring correct configuration: " + this);
+        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Ensuring correct "
+                + "configuration: %s", this);
 
         final int newDisplayId = getDisplayId();
         final boolean displayChanged = mLastReportedDisplayId != newDisplayId;
@@ -7043,8 +7031,8 @@
         // the combine configurations are equal, but would otherwise differ in the override config
         mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration());
         if (getConfiguration().equals(mTmpConfig) && !forceNewConfig && !displayChanged) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Configuration & display unchanged in " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display "
+                    + "unchanged in %s", this);
             return true;
         }
 
@@ -7064,14 +7052,14 @@
             // No need to relaunch or schedule new config for activity that hasn't been launched
             // yet. We do, however, return after applying the config to activity record, so that
             // it will use it for launch transaction.
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Skipping config check for initializing activity: " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check for "
+                    + "initializing activity: %s", this);
             return true;
         }
 
         if (changes == 0 && !forceNewConfig) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Configuration no differences in " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration no differences in %s",
+                    this);
             // There are no significant differences, so we won't relaunch but should still deliver
             // the new configuration to the client process.
             if (displayChanged) {
@@ -7082,26 +7070,23 @@
             return true;
         }
 
-        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                "Configuration changes for " + this + ", allChanges="
-                        + Configuration.configurationDiffToString(changes));
+        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration changes for %s, "
+                + "allChanges=%s", this, Configuration.configurationDiffToString(changes));
 
         // If the activity isn't currently running, just leave the new configuration and it will
         // pick that up next time it starts.
         if (!attachedToProcess()) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Configuration doesn't matter not running " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration doesn't matter not running %s", this);
             stopFreezingScreenLocked(false);
             forceNewConfig = false;
             return true;
         }
 
         // Figure out how to handle the changes between the configurations.
-        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                "Checking to restart " + info.name + ": changed=0x"
-                        + Integer.toHexString(changes) + ", handles=0x"
-                        + Integer.toHexString(info.getRealConfigChanged())
-                        + ", mLastReportedConfiguration=" + mLastReportedConfiguration);
+        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Checking to restart %s: changed=0x%s, "
+                + "handles=0x%s, mLastReportedConfiguration=%s", info.name,
+                Integer.toHexString(changes), Integer.toHexString(info.getRealConfigChanged()),
+                mLastReportedConfiguration);
 
         if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
             // Aha, the activity isn't handling the change, so DIE DIE DIE.
@@ -7118,20 +7103,20 @@
                 mRelaunchReason = RELAUNCH_REASON_NONE;
             }
             if (!attachedToProcess()) {
-                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                        "Config is destroying non-running " + this);
+                ProtoLog.v(WM_DEBUG_CONFIGURATION,
+                        "Config is destroying non-running %s", this);
                 destroyImmediately("config");
             } else if (mState == PAUSING) {
                 // A little annoying: we are waiting for this activity to finish pausing. Let's not
                 // do anything now, but just flag that it needs to be restarted when done pausing.
-                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                        "Config is skipping already pausing " + this);
+                ProtoLog.v(WM_DEBUG_CONFIGURATION,
+                        "Config is skipping already pausing %s", this);
                 deferRelaunchUntilPaused = true;
                 preserveWindowOnDeferredRelaunch = preserveWindow;
                 return true;
             } else {
-                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                        "Config is relaunching " + this);
+                ProtoLog.v(WM_DEBUG_CONFIGURATION, "Config is relaunching %s",
+                        this);
                 if (DEBUG_STATES && !mVisibleRequested) {
                     Slog.v(TAG_STATES, "Config is relaunching invisible activity " + this
                             + " called by " + Debug.getCallers(4));
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 4c93b9e..be7a6ae 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -56,12 +56,12 @@
 import static android.os.Process.INVALID_UID;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
@@ -116,6 +116,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.pm.InstantAppResolver;
 import com.android.server.power.ShutdownCheckPoints;
@@ -669,10 +670,8 @@
                 if (stack != null) {
                     stack.mConfigWillChange = globalConfigWillChange;
                 }
-                if (DEBUG_CONFIGURATION) {
-                    Slog.v(TAG_CONFIGURATION, "Starting activity when config will change = "
-                            + globalConfigWillChange);
-                }
+                ProtoLog.v(WM_DEBUG_CONFIGURATION, "Starting activity when config "
+                        + "will change = %b", globalConfigWillChange);
 
                 final long origId = Binder.clearCallingIdentity();
 
@@ -695,10 +694,9 @@
                     if (stack != null) {
                         stack.mConfigWillChange = false;
                     }
-                    if (DEBUG_CONFIGURATION) {
-                        Slog.v(TAG_CONFIGURATION,
+                    ProtoLog.v(WM_DEBUG_CONFIGURATION,
                                 "Updating to new configuration after starting activity.");
-                    }
+
                     mService.updateConfigurationLocked(mRequest.globalConfig, null, false);
                 }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java b/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
index da0bfd6..3c562a6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
@@ -43,12 +43,6 @@
     // Enable all debug log categories for activities.
     private static final boolean DEBUG_ALL_ACTIVITIES = DEBUG_ALL || false;
 
-    static final boolean DEBUG_ADD_REMOVE = DEBUG_ALL_ACTIVITIES || false;
-    public static final boolean DEBUG_CONFIGURATION = DEBUG_ALL || false;
-    static final boolean DEBUG_CONTAINERS = DEBUG_ALL_ACTIVITIES || false;
-    static final boolean DEBUG_FOCUS = false;
-    static final boolean DEBUG_IMMERSIVE = DEBUG_ALL || false;
-    static final boolean DEBUG_LOCKTASK = DEBUG_ALL || false;
     static final boolean DEBUG_PAUSE = DEBUG_ALL || false;
     static final boolean DEBUG_RECENTS = DEBUG_ALL || false;
     static final boolean DEBUG_RECENTS_TRIM_TASKS = DEBUG_RECENTS || false;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2adaa52..6a8cbfb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -66,6 +66,10 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_NONE;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
 import static com.android.server.am.ActivityManagerService.ANR_TRACE_DIR;
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
@@ -92,10 +96,6 @@
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_IMMERSIVE;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
@@ -242,6 +242,7 @@
 import com.android.internal.os.TransferPipe;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.KeyguardDismissCallback;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FrameworkStatsLog;
@@ -813,7 +814,7 @@
             // in-place.
             updateConfigurationLocked(configuration, null, true);
             final Configuration globalConfig = getGlobalConfiguration();
-            if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Initial config: " + globalConfig);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Initial config: %s", globalConfig);
 
             // Load resources only after the current configuration has been set.
             final Resources res = mContext.getResources();
@@ -1960,7 +1961,7 @@
 
             // update associated state if we're frontmost
             if (r.isFocusedActivityOnDisplay()) {
-                if (DEBUG_IMMERSIVE) Slog.d(TAG_IMMERSIVE, "Frontmost changed immersion: "+ r);
+                ProtoLog.d(WM_DEBUG_IMMERSIVE, "Frontmost changed immersion: %s", r);
                 applyUpdateLockStateLocked(r);
             }
         }
@@ -1974,8 +1975,8 @@
         final boolean nextState = r != null && r.immersive;
         mH.post(() -> {
             if (mUpdateLock.isHeld() != nextState) {
-                if (DEBUG_IMMERSIVE) Slog.d(TAG_IMMERSIVE,
-                        "Applying new update lock state '" + nextState + "' for " + r);
+                ProtoLog.d(WM_DEBUG_IMMERSIVE, "Applying new update lock state '%s' for %s",
+                        nextState, r);
                 if (nextState) {
                     mUpdateLock.acquire();
                 } else {
@@ -2176,7 +2177,7 @@
     @Override
     public void setFocusedStack(int stackId) {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setFocusedStack()");
-        if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedStack: stackId=" + stackId);
+        ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedStack: stackId=%d", stackId);
         final long callingId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -2198,7 +2199,7 @@
     @Override
     public void setFocusedTask(int taskId) {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setFocusedTask()");
-        if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedTask: taskId=" + taskId);
+        ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedTask: taskId=%d", taskId);
         final long callingId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -3013,7 +3014,7 @@
     }
 
     private void startLockTaskModeLocked(@Nullable Task task, boolean isSystemCaller) {
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "startLockTaskModeLocked: " + task);
+        ProtoLog.w(WM_DEBUG_LOCKTASK, "startLockTaskModeLocked: %s", task);
         if (task == null || task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
             return;
         }
@@ -3075,8 +3076,7 @@
                     "updateLockTaskPackages()");
         }
         synchronized (mGlobalLock) {
-            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Allowlisting " + userId + ":"
-                    + Arrays.toString(packages));
+            ProtoLog.w(WM_DEBUG_LOCKTASK, "Allowlisting %d:%s", userId, Arrays.toString(packages));
             getLockTaskController().updateLockTaskPackages(userId, packages);
         }
     }
@@ -4001,9 +4001,9 @@
     @Override
     public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
             int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
-        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Report configuration: " + token + " "
-                + Arrays.toString(horizontalSizeConfiguration) + " "
-                + Arrays.toString(verticalSizeConfigurations));
+        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Report configuration: %s %s %s",
+                token, Arrays.toString(horizontalSizeConfiguration),
+                Arrays.toString(verticalSizeConfigurations));
         synchronized (mGlobalLock) {
             ActivityRecord record = ActivityRecord.isInStackLocked(token);
             if (record == null) {
@@ -4497,8 +4497,8 @@
                     "updateLockTaskFeatures()");
         }
         synchronized (mGlobalLock) {
-            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Allowing features " + userId + ":0x" +
-                    Integer.toHexString(flags));
+            ProtoLog.w(WM_DEBUG_LOCKTASK, "Allowing features %d:0x%s",
+                    userId, Integer.toHexString(flags));
             getLockTaskController().updateLockTaskFeatures(userId, flags);
         }
     }
@@ -5183,8 +5183,8 @@
             return 0;
         }
 
-        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
-                "Updating global configuration to: " + values);
+        ProtoLog.i(WM_DEBUG_CONFIGURATION, "Updating global configuration "
+                + "to: %s", values);
         writeConfigurationChanged(changes);
         FrameworkStatsLog.write(FrameworkStatsLog.RESOURCE_CONFIGURATION_CHANGED,
                 values.colorMode,
@@ -5262,10 +5262,8 @@
         for (int i = pidMap.size() - 1; i >= 0; i--) {
             final int pid = pidMap.keyAt(i);
             final WindowProcessController app = pidMap.get(pid);
-            if (DEBUG_CONFIGURATION) {
-                Slog.v(TAG_CONFIGURATION, "Update process config of "
-                        + app.mName + " to new config " + configCopy);
-            }
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Update process config of %s to new "
+                    + "config %s", app.mName, configCopy);
             app.onConfigurationChanged(configCopy);
         }
 
@@ -6563,10 +6561,8 @@
             if (InputMethodSystemProperty.MULTI_CLIENT_IME_ENABLED) return;
 
             if (pid == MY_PID || pid < 0) {
-                if (DEBUG_CONFIGURATION) {
-                    Slog.w(TAG,
+                ProtoLog.w(WM_DEBUG_CONFIGURATION,
                             "Trying to update display configuration for system/invalid process.");
-                }
                 return;
             }
             synchronized (mGlobalLock) {
@@ -6574,18 +6570,14 @@
                         mRootWindowContainer.getDisplayContent(displayId);
                 if (displayContent == null) {
                     // Call might come when display is not yet added or has been removed.
-                    if (DEBUG_CONFIGURATION) {
-                        Slog.w(TAG, "Trying to update display configuration for non-existing "
-                                + "displayId=" + displayId);
-                    }
+                    ProtoLog.w(WM_DEBUG_CONFIGURATION, "Trying to update display "
+                            + "configuration for non-existing displayId=%d", displayId);
                     return;
                 }
                 final WindowProcessController process = mProcessMap.getProcess(pid);
                 if (process == null) {
-                    if (DEBUG_CONFIGURATION) {
-                        Slog.w(TAG, "Trying to update display configuration for invalid "
-                                + "process, pid=" + pid);
-                    }
+                    ProtoLog.w(WM_DEBUG_CONFIGURATION, "Trying to update display "
+                            + "configuration for invalid process, pid=%d", pid);
                     return;
                 }
                 process.registerDisplayConfigurationListener(displayContent);
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index 167afab..7e55f0a 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -16,8 +16,8 @@
 
 package com.android.server.wm;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -37,6 +37,7 @@
 import android.util.SparseArray;
 import android.util.Xml;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FastXmlSerializer;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -333,8 +334,8 @@
                 }
                 try {
                     if (app.hasThread()) {
-                        if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
-                                + app.mName + " new compat " + ci);
+                        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s "
+                                + "new compat %s", app.mName, ci);
                         app.getThread().updatePackageCompatibilityInfo(packageName, ci);
                     }
                 } catch (Exception e) {
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 8ef57f7..c8d7693 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -29,7 +29,7 @@
 import static android.os.UserHandle.USER_CURRENT;
 import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
 
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -65,6 +65,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
@@ -448,7 +449,7 @@
      * unlike {@link #stopLockTaskMode(Task, boolean, int)}, it doesn't perform the checks.
      */
     void clearLockedTasks(String reason) {
-        if (DEBUG_LOCKTASK) Slog.i(TAG_LOCKTASK, "clearLockedTasks: " + reason);
+        ProtoLog.i(WM_DEBUG_LOCKTASK, "clearLockedTasks: %s", reason);
         if (!mLockTaskModeTasks.isEmpty()) {
             clearLockedTask(mLockTaskModeTasks.get(0));
         }
@@ -490,10 +491,10 @@
         if (!mLockTaskModeTasks.remove(task)) {
             return;
         }
-        if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: removed " + task);
+        ProtoLog.d(WM_DEBUG_LOCKTASK, "removeLockedTask: removed %s", task);
         if (mLockTaskModeTasks.isEmpty()) {
-            if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: task=" + task +
-                    " last task, reverting locktask mode. Callers=" + Debug.getCallers(3));
+            ProtoLog.d(WM_DEBUG_LOCKTASK, "removeLockedTask: task=%s last task, "
+                    + "reverting locktask mode. Callers=%s", task, Debug.getCallers(3));
             mHandler.post(() -> performStopLockTask(task.mUserId));
         }
     }
@@ -558,7 +559,7 @@
             if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
                 // startLockTask() called by app, but app is not part of lock task allowlist. Show
                 // app pinning request. We will come back here with isSystemCaller true.
-                if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Mode default, asking user");
+                ProtoLog.w(WM_DEBUG_LOCKTASK, "Mode default, asking user");
                 StatusBarManagerInternal statusBarManager = LocalServices.getService(
                         StatusBarManagerInternal.class);
                 if (statusBarManager != null) {
@@ -569,8 +570,7 @@
         }
 
         // System can only initiate screen pinning, not full lock task mode
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
-                isSystemCaller ? "Locking pinned" : "Locking fully");
+        ProtoLog.w(WM_DEBUG_LOCKTASK, "%s", isSystemCaller ? "Locking pinned" : "Locking fully");
         setLockTaskMode(task, isSystemCaller ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
                 "startLockTask", true);
     }
@@ -584,7 +584,7 @@
                                  String reason, boolean andResume) {
         // Should have already been checked, but do it again.
         if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
-            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
+            ProtoLog.w(WM_DEBUG_LOCKTASK,
                     "setLockTaskMode: Can't lock due to auth");
             return;
         }
@@ -602,8 +602,8 @@
                     task.mUserId,
                     lockTaskModeState));
         }
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "setLockTaskMode: Locking to " + task +
-                " Callers=" + Debug.getCallers(4));
+        ProtoLog.w(WM_DEBUG_LOCKTASK, "setLockTaskMode: Locking to %s Callers=%s",
+                task, Debug.getCallers(4));
 
         if (!mLockTaskModeTasks.contains(task)) {
             mLockTaskModeTasks.add(task);
@@ -672,8 +672,8 @@
             }
 
             // Terminate locked tasks that have recently lost allowlist authorization.
-            if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " +
-                    lockedTask + " mLockTaskAuth()=" + lockedTask.lockTaskAuthToString());
+            ProtoLog.d(WM_DEBUG_LOCKTASK, "onLockTaskPackagesUpdated: removing %s"
+                    + " mLockTaskAuth()=%s", lockedTask, lockedTask.lockTaskAuthToString());
             removeLockedTask(lockedTask);
             lockedTask.performClearTaskLocked();
             taskChanged = true;
@@ -686,8 +686,8 @@
         if (mLockTaskModeTasks.isEmpty() && task!= null
                 && task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) {
             // This task must have just been authorized.
-            if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK,
-                    "onLockTaskPackagesUpdated: starting new locktask task=" + task);
+            ProtoLog.d(WM_DEBUG_LOCKTASK, "onLockTaskPackagesUpdated: starting new "
+                    + "locktask task=%s", task);
             setLockTaskMode(task, LOCK_TASK_MODE_LOCKED, "package updated", false);
             taskChanged = true;
         }
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index cc5ed36..c3953b4 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -16,9 +16,8 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
-import static com.android.server.wm.Task.TAG_ADD_REMOVE;
 import static com.android.server.wm.Task.TAG_TASKS;
 
 import android.app.ActivityOptions;
@@ -27,6 +26,7 @@
 import android.os.Debug;
 import android.util.Slog;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -218,8 +218,8 @@
             if (takeOptions) {
                 noOptions = takeOption(p, noOptions);
             }
-            if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE, "Removing activity " + p + " from task="
-                    + mTask + " adding to task=" + targetTask + " Callers=" + Debug.getCallers(4));
+            ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing activity %s from task=%s "
+                    + "adding to task=%s Callers=%s", p, mTask,  targetTask, Debug.getCallers(4));
             if (DEBUG_TASKS) Slog.v(TAG_TASKS,
                     "Pushing next activity " + p + " out to target's task " + target);
             p.reparent(targetTask, position, "resetTargetTaskIfNeeded");
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 6cf9432..7b5b0ad 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -99,9 +99,8 @@
                 // the task's profile
                 return;
             }
-            if (!mAllowed && !task.isActivityTypeHome()) {
-                // Skip if the caller isn't allowed to fetch this task, except for the home
-                // task which we always return.
+            if (!mAllowed) {
+                // Skip if the caller isn't allowed to fetch this task
                 return;
             }
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e77a535..19bf451 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -79,6 +79,7 @@
 import static com.android.internal.policy.DecorView.DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
 import static com.android.internal.policy.DecorView.DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
 import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
@@ -87,9 +88,7 @@
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList;
 import static com.android.server.wm.ActivityStackSupervisor.printThisActivity;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
@@ -1651,8 +1650,8 @@
      * Reorder the history stack so that the passed activity is brought to the front.
      */
     final void moveActivityToFrontLocked(ActivityRecord newTop) {
-        if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE, "Removing and adding activity "
-                + newTop + " to stack at top callers=" + Debug.getCallers(4));
+        ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing and adding activity %s to stack at top "
+                + "callers=%s", newTop, Debug.getCallers(4));
 
         positionChildAtTop(newTop);
         updateEffectiveIntent();
@@ -1951,8 +1950,8 @@
                         ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
                 break;
         }
-        if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "setLockTaskAuth: task=" + this
-                + " mLockTaskAuth=" + lockTaskAuthToString());
+        ProtoLog.d(WM_DEBUG_LOCKTASK, "setLockTaskAuth: task=%s mLockTaskAuth=%s", this,
+                lockTaskAuthToString());
     }
 
     @Override
@@ -6370,7 +6369,8 @@
                 // Here it is!  Now, if this is not yet visible (occluded by another task) to the
                 // user, then just add it without starting; it will get started when the user
                 // navigates back to it.
-                if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to task " + task,
+                ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s "
+                                + "callers: %s", r, task,
                         new RuntimeException("here").fillInStackTrace());
                 rTask.positionChildAtTop(r);
                 ActivityOptions.abort(options);
@@ -6392,8 +6392,8 @@
         task = activityTask;
 
         // Slot the activity into the history stack and proceed
-        if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to stack to task " + task,
-                new RuntimeException("here").fillInStackTrace());
+        ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to stack to task %s "
+                        + "callers: %s", r, task, new RuntimeException("here").fillInStackTrace());
         task.positionChildAtTop(r);
 
         // The transition animation and starting window are not needed if {@code allowMoveToFront}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index bb9cf2e..c5ebace 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -22,9 +22,9 @@
 import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
@@ -72,6 +72,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.HeavyWeightSwitcherActivity;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.Watchdog;
 import com.android.server.wm.ActivityTaskManagerService.HotPath;
@@ -1348,9 +1349,8 @@
             }
             return;
         }
-        if (DEBUG_CONFIGURATION) {
-            Slog.v(TAG_CONFIGURATION, "Sending to proc " + mName + " new config " + config);
-        }
+        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
+                config);
         if (Build.IS_DEBUGGABLE && mHasImeService) {
             // TODO (b/135719017): Temporary log for debugging IME service.
             Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 763654d..dda81ff 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -187,7 +187,7 @@
         moveEachPointers(mLastEvent, p(10, 10), p(10, 10));
         send(mLastEvent);
         goToStateClearFrom(STATE_DRAGGING_2FINGERS);
-        assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_UP);
+        assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_MOVE, ACTION_UP);
     }
 
     @Test
@@ -288,7 +288,7 @@
         assertState(STATE_DRAGGING);
         goToStateClearFrom(STATE_DRAGGING_2FINGERS);
         assertState(STATE_CLEAR);
-        assertCapturedEvents(ACTION_DOWN, ACTION_UP);
+        assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
         assertCapturedEventsNoHistory();
     }
 
@@ -301,6 +301,7 @@
         assertState(STATE_CLEAR);
         assertCapturedEvents(
                 /* goto dragging state */ ACTION_DOWN,
+                ACTION_MOVE,
                 /* leave dragging state */ ACTION_UP,
                 ACTION_DOWN,
                 ACTION_POINTER_DOWN,
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
index 53c4d6f..f17173f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
@@ -62,6 +62,32 @@
         assertThat(message).isEqualTo(buildMessage("5F:81:21:00"));
     }
 
+    @Test
+    public void buildSetOsdName_short() {
+        String deviceName = "abc";
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildSetOsdNameCommand(ADDR_PLAYBACK_1,
+                ADDR_TV, deviceName);
+        assertThat(message).isEqualTo(buildMessage("40:47:61:62:63"));
+    }
+
+    @Test
+    public void buildSetOsdName_maximumLength() {
+        String deviceName = "abcdefghijklmn";
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildSetOsdNameCommand(ADDR_PLAYBACK_1,
+                ADDR_TV, deviceName);
+        assertThat(message).isEqualTo(
+                buildMessage("40:47:61:62:63:64:65:66:67:68:69:6A:6B:6C:6D:6E"));
+    }
+
+    @Test
+    public void buildSetOsdName_tooLong() {
+        String deviceName = "abcdefghijklmnop";
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildSetOsdNameCommand(ADDR_PLAYBACK_1,
+                ADDR_TV, deviceName);
+        assertThat(message).isEqualTo(
+                buildMessage("40:47:61:62:63:64:65:66:67:68:69:6A:6B:6C:6D:6E"));
+    }
+
     /**
      * Build a CEC message from a hex byte string with bytes separated by {@code :}.
      *
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
new file mode 100644
index 0000000..d7ed96f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2020 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.timezonedetector;
+
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.timezonedetector.TimeZoneCapabilities;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilities} and
+ * {@link android.app.timezonedetector.TimeZoneConfiguration} that can be generated from it.
+ */
+public class ConfigurationInternalTest {
+
+    private static final int ARBITRARY_USER_ID = 99999;
+
+    /**
+     * Tests when {@link ConfigurationInternal#isUserConfigAllowed()} and
+     * {@link ConfigurationInternal#isAutoDetectionSupported()} are both true.
+     */
+    @Test
+    public void test_unrestricted() {
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(true)
+                .setAutoDetectionSupported(true)
+                .setAutoDetectionEnabled(true)
+                .setLocationEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        {
+            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(true)
+                    .build();
+            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
+            assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
+            assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+
+        {
+            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(false)
+                    .build();
+            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
+            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
+            assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+    }
+
+    /** Tests when {@link ConfigurationInternal#isUserConfigAllowed()} is false */
+    @Test
+    public void test_restricted() {
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(false)
+                .setAutoDetectionSupported(true)
+                .setAutoDetectionEnabled(true)
+                .setLocationEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        {
+            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(true)
+                    .build();
+            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
+            assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
+            assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+
+        {
+            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(false)
+                    .build();
+            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
+            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
+            assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+    }
+
+    /** Tests when {@link ConfigurationInternal#isAutoDetectionSupported()} is false. */
+    @Test
+    public void test_autoDetectNotSupported() {
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(true)
+                .setAutoDetectionSupported(false)
+                .setAutoDetectionEnabled(true)
+                .setLocationEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        {
+            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(true)
+                    .build();
+            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
+            assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
+            assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+        {
+            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(false)
+                    .build();
+            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
+            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
+            assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index e5e9311..4ef2082 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -18,6 +18,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -32,56 +33,64 @@
 
 class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
 
-    private StrategyListener mListener;
+    private ConfigurationChangeListener mConfigurationChangeListener;
 
     // Fake state
-    private TimeZoneCapabilities mCapabilities;
-    private TimeZoneConfiguration mConfiguration;
+    private ConfigurationInternal mConfigurationInternal;
 
     // Call tracking.
     private GeolocationTimeZoneSuggestion mLastGeolocationSuggestion;
     private ManualTimeZoneSuggestion mLastManualSuggestion;
     private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
-    private boolean mHandleAutoTimeZoneConfigChangedCalled;
     private boolean mDumpCalled;
     private final List<Dumpable> mDumpables = new ArrayList<>();
 
     @Override
-    public void setStrategyListener(@NonNull StrategyListener listener) {
-        mListener = listener;
+    public void addConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
+        if (mConfigurationChangeListener != null) {
+            fail("Fake only supports one listener");
+        }
+        mConfigurationChangeListener = listener;
     }
 
     @Override
-    public TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
-        return mCapabilities;
+    public ConfigurationInternal getConfigurationInternal(int userId) {
+        if (mConfigurationInternal.getUserId() != userId) {
+            fail("Fake only supports one user");
+        }
+        return mConfigurationInternal;
     }
 
     @Override
-    public boolean updateConfiguration(
-            @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) {
-        assertNotNull(mConfiguration);
-        assertNotNull(configuration);
+    public ConfigurationInternal getCurrentUserConfigurationInternal() {
+        return mConfigurationInternal;
+    }
 
-        // Simulate the strategy's behavior: the new configuration will be the old configuration
-        // merged with the new.
-        TimeZoneConfiguration oldConfiguration = mConfiguration;
-        TimeZoneConfiguration newConfiguration =
-                new TimeZoneConfiguration.Builder(mConfiguration)
-                        .mergeProperties(configuration)
-                        .build();
+    @Override
+    public boolean updateConfiguration(@NonNull TimeZoneConfiguration requestedChanges) {
+        assertNotNull(mConfigurationInternal);
+        assertNotNull(requestedChanges);
 
-        if (newConfiguration.equals(oldConfiguration)) {
+        // Simulate the real strategy's behavior: the new configuration will be updated to be the
+        // old configuration merged with the new if the user has the capability to up the settings.
+        // Then, if the configuration changed, the change listener is invoked.
+        TimeZoneCapabilities capabilities = mConfigurationInternal.createCapabilities();
+        TimeZoneConfiguration newConfiguration = capabilities.applyUpdate(requestedChanges);
+        if (newConfiguration == null) {
             return false;
         }
-        mConfiguration = newConfiguration;
-        mListener.onConfigurationChanged();
+
+        if (!newConfiguration.equals(capabilities.getConfiguration())) {
+            mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
+
+            // Note: Unlike the real strategy, the listeners is invoked synchronously.
+            mConfigurationChangeListener.onChange();
+        }
         return true;
     }
 
-    @Override
-    @NonNull
-    public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
-        return mConfiguration;
+    public void simulateConfigurationChangeForTests() {
+        mConfigurationChangeListener.onChange();
     }
 
     @Override
@@ -103,11 +112,6 @@
     }
 
     @Override
-    public void handleAutoTimeZoneConfigChanged() {
-        mHandleAutoTimeZoneConfigChangedCalled = true;
-    }
-
-    @Override
     public void addDumpable(Dumpable dumpable) {
         mDumpables.add(dumpable);
     }
@@ -117,19 +121,14 @@
         mDumpCalled = true;
     }
 
-    void initializeConfiguration(TimeZoneConfiguration configuration) {
-        mConfiguration = configuration;
-    }
-
-    void initializeCapabilities(TimeZoneCapabilities capabilities) {
-        mCapabilities = capabilities;
+    void initializeConfiguration(ConfigurationInternal configurationInternal) {
+        mConfigurationInternal = configurationInternal;
     }
 
     void resetCallTracking() {
         mLastGeolocationSuggestion = null;
         mLastManualSuggestion = null;
         mLastTelephonySuggestion = null;
-        mHandleAutoTimeZoneConfigChangedCalled = false;
         mDumpCalled = false;
     }
 
@@ -146,10 +145,6 @@
         assertEquals(expectedSuggestion, mLastTelephonySuggestion);
     }
 
-    void verifyHandleAutoTimeZoneConfigChangedCalled() {
-        assertTrue(mHandleAutoTimeZoneConfigChangedCalled);
-    }
-
     void verifyDumpCalled() {
         assertTrue(mDumpCalled);
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java
new file mode 100644
index 0000000..f45b3a8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 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.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.annotation.UserIdInt;
+
+/** A fake {@link CallerIdentityInjector} used in tests. */
+public class TestCallerIdentityInjector implements CallerIdentityInjector {
+
+    private long mToken = 9999L;
+    private int mCallingUserId;
+    private Integer mCurrentCallingUserId;
+
+    public void initializeCallingUserId(@UserIdInt int userId) {
+        mCallingUserId = userId;
+        mCurrentCallingUserId = userId;
+    }
+
+    @Override
+    public int getCallingUserId() {
+        assertNotNull("callingUserId has been cleared", mCurrentCallingUserId);
+        return mCurrentCallingUserId;
+    }
+
+    @Override
+    public long clearCallingIdentity() {
+        mCurrentCallingUserId = null;
+        return mToken;
+    }
+
+    @Override
+    public void restoreCallingIdentity(long token) {
+        assertEquals(token, mToken);
+        mCurrentCallingUserId = mCallingUserId;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index e9d57e5..918babc 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.timezonedetector;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
 import android.content.Context;
@@ -85,6 +86,18 @@
         mFakeTimeZoneDetectorStrategy.verifyHasDumpable(stubbedDumpable);
     }
 
+    @Test
+    public void testAddConfigurationListener() throws Exception {
+        boolean[] changeCalled = new boolean[2];
+        mTimeZoneDetectorInternal.addConfigurationListener(() -> changeCalled[0] = true);
+        mTimeZoneDetectorInternal.addConfigurationListener(() -> changeCalled[1] = true);
+
+        mFakeTimeZoneDetectorStrategy.simulateConfigurationChangeForTests();
+
+        assertTrue(changeCalled[0]);
+        assertTrue(changeCalled[1]);
+    }
+
     private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
         return new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS);
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 3a1ec4f..27b04b6 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.timezonedetector;
 
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -36,7 +34,6 @@
 import android.app.timezonedetector.ITimeZoneConfigurationListener;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -65,6 +62,7 @@
     private TimeZoneDetectorService mTimeZoneDetectorService;
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
+    private TestCallerIdentityInjector mTestCallerIdentityInjector;
 
 
     @Before
@@ -76,10 +74,14 @@
         mHandlerThread.start();
         mTestHandler = new TestHandler(mHandlerThread.getLooper());
 
+        mTestCallerIdentityInjector = new TestCallerIdentityInjector();
+        mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
+
         mFakeTimeZoneDetectorStrategy = new FakeTimeZoneDetectorStrategy();
 
         mTimeZoneDetectorService = new TimeZoneDetectorService(
-                mMockContext, mTestHandler, mFakeTimeZoneDetectorStrategy);
+                mMockContext, mTestHandler, mTestCallerIdentityInjector,
+                mFakeTimeZoneDetectorStrategy);
     }
 
     @After
@@ -107,40 +109,12 @@
     public void testGetCapabilities() {
         doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        TimeZoneCapabilities capabilities = createTimeZoneCapabilities();
-        mFakeTimeZoneDetectorStrategy.initializeCapabilities(capabilities);
-
-        assertEquals(capabilities, mTimeZoneDetectorService.getCapabilities());
-
-        verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
-                anyString());
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testGetConfiguration_withoutPermission() {
-        doThrow(new SecurityException("Mock"))
-                .when(mMockContext).enforceCallingPermission(anyString(), any());
-
-        try {
-            mTimeZoneDetectorService.getConfiguration();
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
-                    anyString());
-        }
-    }
-
-    @Test
-    public void testGetConfiguration() {
-        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
-
-        TimeZoneConfiguration configuration =
-                createTimeZoneConfiguration(false /* autoDetectionEnabled */);
+        ConfigurationInternal configuration =
+                createConfigurationInternal(true /* autoDetectionEnabled*/);
         mFakeTimeZoneDetectorStrategy.initializeConfiguration(configuration);
 
-        assertEquals(configuration, mTimeZoneDetectorService.getConfiguration());
+        assertEquals(configuration.createCapabilities(),
+                mTimeZoneDetectorService.getCapabilities());
 
         verify(mMockContext).enforceCallingPermission(
                 eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
@@ -181,10 +155,9 @@
 
     @Test
     public void testConfigurationChangeListenerRegistrationAndCallbacks() throws Exception {
-        TimeZoneConfiguration autoDetectDisabledConfiguration =
-                createTimeZoneConfiguration(false /* autoDetectionEnabled */);
-
-        mFakeTimeZoneDetectorStrategy.initializeConfiguration(autoDetectDisabledConfiguration);
+        ConfigurationInternal initialConfiguration =
+                createConfigurationInternal(false /* autoDetectionEnabled */);
+        mFakeTimeZoneDetectorStrategy.initializeConfiguration(initialConfiguration);
 
         IBinder mockListenerBinder = mock(IBinder.class);
         ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
@@ -210,13 +183,12 @@
             // Simulate the configuration being changed and verify the mockListener was notified.
             TimeZoneConfiguration autoDetectEnabledConfiguration =
                     createTimeZoneConfiguration(true /* autoDetectionEnabled */);
-
             mTimeZoneDetectorService.updateConfiguration(autoDetectEnabledConfiguration);
 
             verify(mMockContext).enforceCallingPermission(
                     eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
                     anyString());
-            verify(mockListener).onChange(autoDetectEnabledConfiguration);
+            verify(mockListener).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
         }
@@ -242,12 +214,14 @@
         {
             doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
 
+            TimeZoneConfiguration autoDetectDisabledConfiguration =
+                    createTimeZoneConfiguration(false /* autoDetectionEnabled */);
             mTimeZoneDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
 
             verify(mMockContext).enforceCallingPermission(
                     eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
                     anyString());
-            verify(mockListener, never()).onChange(any());
+            verify(mockListener, never()).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
         }
@@ -379,33 +353,22 @@
         mFakeTimeZoneDetectorStrategy.verifyDumpCalled();
     }
 
-    @Test
-    public void testHandleAutoTimeZoneConfigChanged() throws Exception {
-        mTimeZoneDetectorService.handleAutoTimeZoneConfigChanged();
-        mTestHandler.assertTotalMessagesEnqueued(1);
-        mTestHandler.waitForMessagesToBeProcessed();
-        mFakeTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneConfigChangedCalled();
-
-        mFakeTimeZoneDetectorStrategy.resetCallTracking();
-
-        mTimeZoneDetectorService.handleAutoTimeZoneConfigChanged();
-        mTestHandler.assertTotalMessagesEnqueued(2);
-        mTestHandler.waitForMessagesToBeProcessed();
-        mFakeTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneConfigChangedCalled();
-    }
-
-    private static TimeZoneConfiguration createTimeZoneConfiguration(
-            boolean autoDetectionEnabled) {
-        return new TimeZoneConfiguration.Builder()
+    private static TimeZoneConfiguration createTimeZoneConfiguration(boolean autoDetectionEnabled) {
+        return new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
                 .setAutoDetectionEnabled(autoDetectionEnabled)
                 .build();
     }
 
-    private static TimeZoneCapabilities createTimeZoneCapabilities() {
-        return new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
-                .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
+    private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
+        // Default geo detection settings from auto detection settings - they are not important to
+        // the tests.
+        final boolean geoDetectionEnabled = autoDetectionEnabled;
+        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionSupported(true)
+                .setUserConfigAllowed(true)
+                .setAutoDetectionEnabled(autoDetectionEnabled)
+                .setLocationEnabled(geoDetectionEnabled)
+                .setGeoDetectionEnabled(geoDetectionEnabled)
                 .build();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index a6caa42..2bee5e5 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -23,10 +23,6 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
 
 import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH;
 import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST;
@@ -37,9 +33,9 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -47,7 +43,6 @@
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
-import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
 import android.util.IndentingPrintWriter;
 
@@ -61,7 +56,6 @@
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -94,192 +88,149 @@
                     TELEPHONY_SCORE_HIGHEST),
     };
 
-    private static final TimeZoneConfiguration CONFIG_AUTO_ENABLED =
-            new TimeZoneConfiguration.Builder()
-                    .setAutoDetectionEnabled(true)
-                    .build();
-
-    private static final TimeZoneConfiguration CONFIG_AUTO_DISABLED =
-            new TimeZoneConfiguration.Builder()
+    private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setUserConfigAllowed(false)
+                    .setAutoDetectionSupported(true)
                     .setAutoDetectionEnabled(false)
-                    .build();
-
-    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_DISABLED =
-            new TimeZoneConfiguration.Builder()
+                    .setLocationEnabled(true)
                     .setGeoDetectionEnabled(false)
                     .build();
 
-    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_ENABLED =
-            new TimeZoneConfiguration.Builder()
+    private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setUserConfigAllowed(false)
+                    .setAutoDetectionSupported(true)
+                    .setAutoDetectionEnabled(true)
+                    .setLocationEnabled(true)
                     .setGeoDetectionEnabled(true)
                     .build();
 
+    private static final ConfigurationInternal CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionSupported(false)
+                    .setAutoDetectionEnabled(false)
+                    .setLocationEnabled(true)
+                    .setGeoDetectionEnabled(false)
+                    .build();
+
+    private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionSupported(true)
+                    .setAutoDetectionEnabled(false)
+                    .setLocationEnabled(true)
+                    .setGeoDetectionEnabled(false)
+                    .build();
+
+    private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_ENABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionSupported(true)
+                    .setAutoDetectionEnabled(false)
+                    .setLocationEnabled(true)
+                    .setGeoDetectionEnabled(true)
+                    .build();
+
+    private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_DISABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setAutoDetectionSupported(true)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionEnabled(true)
+                    .setLocationEnabled(true)
+                    .setGeoDetectionEnabled(false)
+                    .build();
+
+    private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_ENABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setAutoDetectionSupported(true)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionEnabled(true)
+                    .setLocationEnabled(true)
+                    .setGeoDetectionEnabled(true)
+                    .build();
+
+    private static final TimeZoneConfiguration CONFIG_AUTO_DISABLED =
+            createConfig(false /* autoDetection */, null);
+    private static final TimeZoneConfiguration CONFIG_AUTO_ENABLED =
+            createConfig(true /* autoDetection */, null);
+    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_ENABLED =
+            createConfig(null, true /* geoDetection */);
+    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_DISABLED =
+            createConfig(null, false /* geoDetection */);
+
     private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
     private FakeCallback mFakeCallback;
-    private MockStrategyListener mMockStrategyListener;
+    private MockConfigChangeListener mMockConfigChangeListener;
+
 
     @Before
     public void setUp() {
         mFakeCallback = new FakeCallback();
-        mMockStrategyListener = new MockStrategyListener();
+        mMockConfigChangeListener = new MockConfigChangeListener();
         mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeCallback);
-        mFakeCallback.setStrategyForSettingsCallbacks(mTimeZoneDetectorStrategy);
-        mTimeZoneDetectorStrategy.setStrategyListener(mMockStrategyListener);
+        mTimeZoneDetectorStrategy.addConfigChangeListener(mMockConfigChangeListener);
     }
 
     @Test
-    public void testGetCapabilities() {
-        new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
-        TimeZoneCapabilities expectedCapabilities = mFakeCallback.getCapabilities(USER_ID);
-        assertEquals(expectedCapabilities, mTimeZoneDetectorStrategy.getCapabilities(USER_ID));
-    }
-
-    @Test
-    public void testGetConfiguration() {
-        new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
-        TimeZoneConfiguration expectedConfiguration = mFakeCallback.getConfiguration(USER_ID);
-        assertTrue(expectedConfiguration.isComplete());
-        assertEquals(expectedConfiguration, mTimeZoneDetectorStrategy.getConfiguration(USER_ID));
-    }
-
-    @Test
-    public void testCapabilitiesTestInfra_unrestricted() {
-        Script script = new Script();
-
-        script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
-        {
-            // Check the fake test infra is doing what is expected.
-            TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone());
-        }
-
-        script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
-        {
-            // Check the fake test infra is doing what is expected.
-            TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
-        }
-    }
-
-    @Test
-    public void testCapabilitiesTestInfra_restricted() {
-        Script script = new Script();
-
-        script.initializeUser(USER_ID, UserCase.RESTRICTED,
-                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
-        {
-            // Check the fake test infra is doing what is expected.
-            TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
-        }
-
-        script.initializeUser(USER_ID, UserCase.RESTRICTED,
-                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
-        {
-            // Check the fake test infra is doing what is expected.
-            TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
-        }
-    }
-
-    @Test
-    public void testCapabilitiesTestInfra_autoDetectNotSupported() {
-        Script script = new Script();
-
-        script.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
-        {
-            // Check the fake test infra is doing what is expected.
-            TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
-            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
-        }
-
-        script.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
-        {
-            // Check the fake test infra is doing what is expected.
-            TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
-            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
-        }
+    public void testGetCurrentUserConfiguration() {
+        new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
+        ConfigurationInternal expectedConfiguration =
+                mFakeCallback.getConfigurationInternal(USER_ID);
+        assertEquals(expectedConfiguration,
+                mTimeZoneDetectorStrategy.getCurrentUserConfigurationInternal());
     }
 
     @Test
     public void testUpdateConfiguration_unrestricted() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         // Set the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
+        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
 
         // Nothing should have happened: it was initialized in this state.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
+        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */);
 
         // The settings should have been changed and the StrategyListener onChange() called.
-        script.verifyConfigurationChangedAndReset(USER_ID,
-                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED);
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
+        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
 
         // The settings should have been changed and the StrategyListener onChange() called.
-        script.verifyConfigurationChangedAndReset(USER_ID,
-                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         // Update the configuration to enable geolocation time zone detection.
         script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_GEO_DETECTION_ENABLED,  true /* expectedResult */);
+                CONFIG_GEO_DETECTION_ENABLED,  true /* expectedResult */);
 
         // The settings should have been changed and the StrategyListener onChange() called.
-        script.verifyConfigurationChangedAndReset(USER_ID,
-                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED));
+        script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED);
     }
 
     @Test
     public void testUpdateConfiguration_restricted() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        Script script = new Script().initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED);
 
         // Try to update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
+        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_AUTO_ENABLED,  false /* expectedResult */);
+        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED,  false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
-        // Update the configuration to enable geolocation time zone detection.
+        // Try to  update the configuration to enable geolocation time zone detection.
         script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_GEO_DETECTION_ENABLED,  false /* expectedResult */);
+                CONFIG_GEO_DETECTION_ENABLED,  false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
@@ -287,20 +238,16 @@
 
     @Test
     public void testUpdateConfiguration_autoDetectNotSupported() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        Script script = new Script().initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED);
 
         // Try to update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
+        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
+        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
@@ -313,8 +260,7 @@
         TelephonyTimeZoneSuggestion slotIndex2TimeZoneSuggestion =
                 createEmptySlotIndex2Suggestion();
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         script.simulateTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion)
@@ -359,9 +305,7 @@
         TelephonyTestCase testCase2 = newTelephonyTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
                 QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH);
 
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         // A low quality suggestions will not be taken: The device time zone setting is left
         // uninitialized.
@@ -426,8 +370,7 @@
 
         for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
             // Start with the device in a known state.
-            script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                    CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+            script.initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
                     .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
             TelephonyTimeZoneSuggestion suggestion =
@@ -447,8 +390,7 @@
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
             // Toggling the time zone setting on should cause the device setting to be set.
-            script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED,
-                    true /* expectedResult */);
+            script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
 
             // When time zone detection is already enabled the suggestion (if it scores highly
             // enough) should be set immediately.
@@ -465,8 +407,7 @@
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
             // Toggling the time zone setting should off should do nothing.
-            script.simulateUpdateConfiguration(
-                    USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+            script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
                     .verifyTimeZoneNotChanged();
 
             // Assert internal service state.
@@ -480,8 +421,7 @@
     @Test
     public void testTelephonySuggestionsSingleSlotId() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
@@ -546,8 +486,7 @@
                         TELEPHONY_SCORE_NONE);
 
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
                 // Initialize the latest suggestions as empty so we don't need to worry about nulls
                 // below for the first loop.
@@ -632,9 +571,7 @@
      */
     @Test
     public void testTelephonySuggestionStrategyDoesNotAssumeCurrentSetting_autoTelephony() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         TelephonyTestCase testCase = newTelephonyTestCase(
                 MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH);
@@ -652,40 +589,39 @@
 
         // Toggling time zone detection should set the device time zone only if the current setting
         // value is different from the most recent telephony suggestion.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
                 .verifyTimeZoneNotChanged()
-                .simulateUpdateConfiguration(
-                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneNotChanged();
 
         // Simulate a user turning auto detection off, a new suggestion being made while auto
         // detection is off, and the user turning it on again.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
                 .simulateTelephonyTimeZoneSuggestion(newYorkSuggestion)
                 .verifyTimeZoneNotChanged();
         // Latest suggestion should be used.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneChangedAndReset(newYorkSuggestion);
     }
 
     @Test
-    public void testManualSuggestion_autoDetectionEnabled_autoTelephony() {
-        checkManualSuggestion_autoDetectionEnabled(false /* geoDetectionEnabled */);
+    public void testManualSuggestion_unrestricted_autoDetectionEnabled_autoTelephony() {
+        checkManualSuggestion_unrestricted_autoDetectionEnabled(false /* geoDetectionEnabled */);
     }
 
     @Test
-    public void testManualSuggestion_autoDetectionEnabled_autoGeo() {
-        checkManualSuggestion_autoDetectionEnabled(true /* geoDetectionEnabled */);
+    public void testManualSuggestion_unrestricted_autoDetectionEnabled_autoGeo() {
+        checkManualSuggestion_unrestricted_autoDetectionEnabled(true /* geoDetectionEnabled */);
     }
 
-    private void checkManualSuggestion_autoDetectionEnabled(boolean geoDetectionEnabled) {
-        TimeZoneConfiguration geoTzEnabledConfig =
-                new TimeZoneConfiguration.Builder()
+    private void checkManualSuggestion_unrestricted_autoDetectionEnabled(
+            boolean geoDetectionEnabled) {
+        ConfigurationInternal geoTzEnabledConfig =
+                new ConfigurationInternal.Builder(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
                         .setGeoDetectionEnabled(geoDetectionEnabled)
                         .build();
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(geoTzEnabledConfig))
+                .initializeConfig(geoTzEnabledConfig)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is enabled so the manual suggestion should be ignored.
@@ -697,35 +633,19 @@
     @Test
     public void testManualSuggestion_restricted_simulateAutoTimeZoneEnabled() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
-        // Auto time zone detection is enabled so the manual suggestion should be ignored.
+        // User is restricted so the manual suggestion should be ignored.
         script.simulateManualTimeZoneSuggestion(
                 USER_ID, createManualSuggestion("Europe/Paris"), false /* expectedResult */)
-            .verifyTimeZoneNotChanged();
-    }
-
-    @Test
-    public void testManualSuggestion_autoDetectNotSupported_simulateAutoTimeZoneEnabled() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
-
-        // Auto time zone detection is enabled so the manual suggestion should be ignored.
-        ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
-        script.simulateManualTimeZoneSuggestion(
-                USER_ID, manualSuggestion, true /* expectedResult */)
-            .verifyTimeZoneChangedAndReset(manualSuggestion);
+                .verifyTimeZoneNotChanged();
     }
 
     @Test
     public void testManualSuggestion_unrestricted_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is disabled so the manual suggestion should be used.
@@ -738,8 +658,7 @@
     @Test
     public void testManualSuggestion_restricted_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Restricted users do not have the capability.
@@ -750,10 +669,9 @@
     }
 
     @Test
-    public void testManualSuggestion_autoDetectNotSupported_autoTimeZoneDetectionDisabled() {
+    public void testManualSuggestion_autoDetectNotSupported() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Unrestricted users have the capability.
@@ -765,9 +683,7 @@
 
     @Test
     public void testGeoSuggestion_uncertain() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+        Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeoLocationSuggestion();
@@ -783,8 +699,7 @@
     @Test
     public void testGeoSuggestion_noZones() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         GeolocationTimeZoneSuggestion noZonesSuggestion = createGeoLocationSuggestion(list());
@@ -802,8 +717,7 @@
                 createGeoLocationSuggestion(list("Europe/London"));
 
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         script.simulateGeolocationTimeZoneSuggestion(suggestion)
@@ -828,8 +742,7 @@
                 createGeoLocationSuggestion(list("Europe/Paris"));
 
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion)
@@ -856,72 +769,27 @@
                 mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
     }
 
-    /**
-     * Confirms that toggling the auto time zone detection enabled setting has the expected behavior
-     * when the strategy is "opinionated" and "un-opinionated" when in geolocation detection is
-     * enabled.
-     */
     @Test
-    public void testTogglingAutoDetectionEnabled_autoGeo() {
-        GeolocationTimeZoneSuggestion geolocationSuggestion =
+    public void testGeoSuggestion_togglingGeoDetectionClearsLastSuggestion() {
+        GeolocationTimeZoneSuggestion suggestion =
                 createGeoLocationSuggestion(list("Europe/London"));
-        GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
-                createUncertainGeoLocationSuggestion();
-        ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
 
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
-        script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion);
-
-        // When time zone detection is not enabled, the time zone suggestion will not be set.
-        script.verifyTimeZoneNotChanged();
-
-        // Assert internal service state.
-        assertEquals(geolocationSuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
-
-        // Toggling the time zone setting on should cause the device setting to be set.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+        script.simulateGeolocationTimeZoneSuggestion(suggestion)
                 .verifyTimeZoneChangedAndReset("Europe/London");
 
-        // Toggling the time zone setting should off should do nothing because the device is now
-        // set to that time zone.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
-                .verifyTimeZoneNotChanged()
-                .simulateUpdateConfiguration(
-                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
-                .verifyTimeZoneNotChanged();
+        // Assert internal service state.
+        assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
 
-        // Now toggle auto time zone setting, and confirm it is opinionated.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
-                .simulateManualTimeZoneSuggestion(
-                        USER_ID, manualSuggestion, true /* expectedResult */)
-                .verifyTimeZoneChangedAndReset(manualSuggestion)
-                .simulateUpdateConfiguration(
-                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
-                .verifyTimeZoneChangedAndReset("Europe/London");
-
-        // Now withdraw the geolocation suggestion, and assert the strategy is no longer
-        // opinionated.
-        /* expectedResult */
-        script.simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
-                .verifyTimeZoneNotChanged()
-                .simulateUpdateConfiguration(
-                        USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
-                .verifyTimeZoneNotChanged()
-                .simulateManualTimeZoneSuggestion(
-                        USER_ID, manualSuggestion, true /* expectedResult */)
-                .verifyTimeZoneChangedAndReset(manualSuggestion)
-                .simulateUpdateConfiguration(
-                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
-                .verifyTimeZoneNotChanged();
+        // Turn off geo detection and verify the latest suggestion is cleared.
+        script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true)
+                .verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         // Assert internal service state.
-        assertEquals(uncertainGeolocationSuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
     }
 
     /**
@@ -937,88 +805,48 @@
                 "Europe/Paris");
 
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Add suggestions. Nothing should happen as time zone detection is disabled.
         script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
                 .verifyTimeZoneNotChanged();
+
+        // Geolocation suggestions are only stored when geolocation detection is enabled.
+        assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
         script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
                 .verifyTimeZoneNotChanged();
 
-        // Assert internal service state.
-        assertEquals(geolocationSuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        // Telephony suggestions are always stored.
         assertEquals(telephonySuggestion,
                 mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1).suggestion);
 
         // Toggling the time zone detection enabled setting on should cause the device setting to be
         // set from the telephony signal, as we've started with geolocation time zone detection
         // disabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneChangedAndReset(telephonySuggestion);
 
-        // Changing the detection to enable geo detection should cause the device tz setting to
-        // change to the geo suggestion.
-        script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
+        // Changing the detection to enable geo detection won't cause the device tz setting to
+        // change because the geo suggestion is empty.
+        script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged()
+                .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
                 .verifyTimeZoneChangedAndReset(geolocationSuggestion.getZoneIds().get(0));
 
         // Changing the detection to disable geo detection should cause the device tz setting to
         // change to the telephony suggestion.
-        script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
                 .verifyTimeZoneChangedAndReset(telephonySuggestion);
-    }
 
-    /**
-     * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time
-     * zone is actually necessary. This test proves that the strategy doesn't assume it knows the
-     * current setting.
-     */
-    @Test
-    public void testTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting_autoGeo() {
-        GeolocationTimeZoneSuggestion losAngelesSuggestion =
-                createGeoLocationSuggestion(list("America/Los_Angeles"));
-        GeolocationTimeZoneSuggestion newYorkSuggestion =
-                createGeoLocationSuggestion(list("America/New_York"));
-
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED));
-
-        // Initialization.
-        script.simulateGeolocationTimeZoneSuggestion(losAngelesSuggestion)
-                .verifyTimeZoneChangedAndReset("America/Los_Angeles");
-        // Suggest it again - it should not be set because it is already set.
-        script.simulateGeolocationTimeZoneSuggestion(losAngelesSuggestion)
-                .verifyTimeZoneNotChanged();
-
-        // Toggling time zone detection should set the device time zone only if the current setting
-        // value is different from the most recent telephony suggestion.
-        /* expectedResult */
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
-                .verifyTimeZoneNotChanged()
-                .simulateUpdateConfiguration(
-                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
-                .verifyTimeZoneNotChanged();
-
-        // Simulate a user turning auto detection off, a new suggestion being made while auto
-        // detection is off, and the user turning it on again.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
-                .simulateGeolocationTimeZoneSuggestion(newYorkSuggestion)
-                .verifyTimeZoneNotChanged();
-        // Latest suggestion should be used.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
-                .verifyTimeZoneChangedAndReset("America/New_York");
+        assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
     }
 
     @Test
     public void testAddDumpable() {
         new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         AtomicBoolean dumpCalled = new AtomicBoolean(false);
@@ -1069,25 +897,26 @@
         return suggestion;
     }
 
+    private static TimeZoneConfiguration createConfig(
+            @Nullable Boolean autoDetection, @Nullable Boolean geoDetection) {
+        TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder(USER_ID);
+        if (autoDetection != null) {
+            builder.setAutoDetectionEnabled(autoDetection);
+        }
+        if (geoDetection != null) {
+            builder.setGeoDetectionEnabled(geoDetection);
+        }
+        return builder.build();
+    }
+
     static class FakeCallback implements TimeZoneDetectorStrategyImpl.Callback {
 
-        private TimeZoneCapabilities mCapabilities;
-        private final TestState<UserConfiguration> mConfiguration = new TestState<>();
+        private final TestState<ConfigurationInternal> mConfigurationInternal = new TestState<>();
         private final TestState<String> mTimeZoneId = new TestState<>();
-        private TimeZoneDetectorStrategyImpl mStrategy;
+        private ConfigurationChangeListener mConfigChangeListener;
 
-        void setStrategyForSettingsCallbacks(TimeZoneDetectorStrategyImpl strategy) {
-            assertNotNull(strategy);
-            mStrategy = strategy;
-        }
-
-        void initializeUser(@UserIdInt int userId, TimeZoneCapabilities capabilities,
-                TimeZoneConfiguration configuration) {
-            assertEquals(userId, capabilities.getUserId());
-            mCapabilities = capabilities;
-            assertTrue("Configuration must be complete when initializing, config=" + configuration,
-                    configuration.isComplete());
-            mConfiguration.init(new UserConfiguration(userId, configuration));
+        void initializeConfig(ConfigurationInternal configurationInternal) {
+            mConfigurationInternal.init(configurationInternal);
         }
 
         void initializeTimeZoneSetting(String zoneId) {
@@ -1095,43 +924,22 @@
         }
 
         @Override
-        public TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
-            assertEquals(userId, mCapabilities.getUserId());
-            return mCapabilities;
+        public void setConfigChangeListener(ConfigurationChangeListener listener) {
+            mConfigChangeListener = listener;
         }
 
         @Override
-        public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
-            UserConfiguration latest = mConfiguration.getLatest();
-            assertEquals(userId, latest.userId);
-            return latest.configuration;
-        }
-
-        @Override
-        public void setConfiguration(@UserIdInt int userId, TimeZoneConfiguration newConfig) {
-            assertNotNull(newConfig);
-            assertTrue(newConfig.isComplete());
-
-            UserConfiguration latestUserConfig = mConfiguration.getLatest();
-            assertEquals(userId, latestUserConfig.userId);
-            TimeZoneConfiguration oldConfig = latestUserConfig.configuration;
-
-            mConfiguration.set(new UserConfiguration(userId, newConfig));
-
-            if (!newConfig.equals(oldConfig)) {
-                // Simulate what happens when the auto detection configuration is changed.
-                mStrategy.handleAutoTimeZoneConfigChanged();
+        public ConfigurationInternal getConfigurationInternal(int userId) {
+            ConfigurationInternal configuration = mConfigurationInternal.getLatest();
+            if (userId != configuration.getUserId()) {
+                fail("FakeCallback does not support multiple users.");
             }
+            return configuration;
         }
 
         @Override
-        public boolean isAutoDetectionEnabled() {
-            return mConfiguration.getLatest().configuration.isAutoDetectionEnabled();
-        }
-
-        @Override
-        public boolean isGeoDetectionEnabled() {
-            return mConfiguration.getLatest().configuration.isGeoDetectionEnabled();
+        public int getCurrentUserId() {
+            return mConfigurationInternal.getLatest().getUserId();
         }
 
         @Override
@@ -1149,9 +957,25 @@
             mTimeZoneId.set(zoneId);
         }
 
+        @Override
+        public void storeConfiguration(TimeZoneConfiguration newConfiguration) {
+            ConfigurationInternal oldConfiguration = mConfigurationInternal.getLatest();
+            if (newConfiguration.getUserId() != oldConfiguration.getUserId()) {
+                fail("FakeCallback does not support multiple users");
+            }
+
+            ConfigurationInternal mergedConfiguration = oldConfiguration.merge(newConfiguration);
+            if (!mergedConfiguration.equals(oldConfiguration)) {
+                mConfigurationInternal.set(mergedConfiguration);
+
+                // Note: Unlike the real callback impl, the listener is invoked synchronously.
+                mConfigChangeListener.onChange();
+            }
+        }
+
         void assertKnownUser(int userId) {
-            assertEquals(userId, mCapabilities.getUserId());
-            assertEquals(userId, mConfiguration.getLatest().userId);
+            assertEquals("FakeCallback does not support multiple users",
+                    mConfigurationInternal.getLatest().getUserId(), userId);
         }
 
         void assertTimeZoneNotChanged() {
@@ -1166,43 +990,7 @@
 
         void commitAllChanges() {
             mTimeZoneId.commitLatest();
-            mConfiguration.commitLatest();
-        }
-    }
-
-    private static final class UserConfiguration {
-        public final @UserIdInt int userId;
-        public final TimeZoneConfiguration configuration;
-
-        UserConfiguration(int userId, TimeZoneConfiguration configuration) {
-            this.userId = userId;
-            this.configuration = configuration;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            UserConfiguration that = (UserConfiguration) o;
-            return userId == that.userId
-                    && Objects.equals(configuration, that.configuration);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(userId, configuration);
-        }
-
-        @Override
-        public String toString() {
-            return "UserConfiguration{"
-                    + "userId=" + userId
-                    + ", configuration=" + configuration
-                    + '}';
+            mConfigurationInternal.commitLatest();
         }
     }
 
@@ -1255,64 +1043,14 @@
         }
     }
 
-    /** Simulated user test cases. */
-    enum UserCase {
-        /** A catch-all for users that can set auto time zone config. */
-        UNRESTRICTED,
-        /** A catch-all for users that can't set auto time zone config. */
-        RESTRICTED,
-        /**
-         * Like {@link #UNRESTRICTED}, but auto tz detection is not
-         * supported on the device.
-         */
-        AUTO_DETECT_NOT_SUPPORTED,
-    }
-
-    /**
-     * Creates a {@link TimeZoneCapabilities} object for a user in the specific role with the
-     * supplied configuration.
-     */
-    private static TimeZoneCapabilities createCapabilities(
-            int userId, UserCase userCase, TimeZoneConfiguration configuration) {
-        switch (userCase) {
-            case UNRESTRICTED: {
-                int suggestManualTimeZoneCapability = configuration.isAutoDetectionEnabled()
-                        ? CAPABILITY_NOT_APPLICABLE : CAPABILITY_POSSESSED;
-                return new TimeZoneCapabilities.Builder(userId)
-                        .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
-                        .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
-                        .setSuggestManualTimeZone(suggestManualTimeZoneCapability)
-                        .build();
-            }
-            case RESTRICTED: {
-                return new TimeZoneCapabilities.Builder(userId)
-                        .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
-                        .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
-                        .setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED)
-                        .build();
-            }
-            case AUTO_DETECT_NOT_SUPPORTED: {
-                return new TimeZoneCapabilities.Builder(userId)
-                        .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_SUPPORTED)
-                        .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_SUPPORTED)
-                        .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
-                        .build();
-            }
-            default:
-                throw new AssertionError(userCase + " not recognized");
-        }
-    }
-
     /**
      * A "fluent" class allows reuse of code in tests: initialization, simulation and verification
      * logic.
      */
     private class Script {
 
-        Script initializeUser(
-                @UserIdInt int userId, UserCase userCase, TimeZoneConfiguration configuration) {
-            TimeZoneCapabilities capabilities = createCapabilities(userId, userCase, configuration);
-            mFakeCallback.initializeUser(userId, capabilities, configuration);
+        Script initializeConfig(ConfigurationInternal configuration) {
+            mFakeCallback.initializeConfig(configuration);
             return this;
         }
 
@@ -1326,10 +1064,9 @@
          * the return value.
          */
         Script simulateUpdateConfiguration(
-                @UserIdInt int userId, TimeZoneConfiguration configuration,
-                boolean expectedResult) {
+                TimeZoneConfiguration configuration, boolean expectedResult) {
             assertEquals(expectedResult,
-                    mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration));
+                    mTimeZoneDetectorStrategy.updateConfiguration(configuration));
             return this;
         }
 
@@ -1392,16 +1129,14 @@
         /**
          * Verifies that the configuration has been changed to the expected value.
          */
-        Script verifyConfigurationChangedAndReset(
-                @UserIdInt int userId, TimeZoneConfiguration expected) {
-            mFakeCallback.mConfiguration.assertHasBeenSet();
-            UserConfiguration expectedUserConfig = new UserConfiguration(userId, expected);
-            assertEquals(expectedUserConfig, mFakeCallback.mConfiguration.getLatest());
+        Script verifyConfigurationChangedAndReset(ConfigurationInternal expected) {
+            mFakeCallback.mConfigurationInternal.assertHasBeenSet();
+            assertEquals(expected, mFakeCallback.mConfigurationInternal.getLatest());
             mFakeCallback.commitAllChanges();
 
             // Also confirm the listener triggered.
-            mMockStrategyListener.verifyOnConfigurationChangedCalled();
-            mMockStrategyListener.reset();
+            mMockConfigChangeListener.verifyOnChangeCalled();
+            mMockConfigChangeListener.reset();
             return this;
         }
 
@@ -1410,10 +1145,10 @@
          * {@link TimeZoneConfiguration} have been changed.
          */
         Script verifyConfigurationNotChanged() {
-            mFakeCallback.mConfiguration.assertHasNotBeenSet();
+            mFakeCallback.mConfigurationInternal.assertHasNotBeenSet();
 
             // Also confirm the listener did not trigger.
-            mMockStrategyListener.verifyOnConfigurationChangedNotCalled();
+            mMockConfigChangeListener.verifyOnChangeNotCalled();
             return this;
         }
 
@@ -1448,24 +1183,24 @@
         return new TelephonyTestCase(matchType, quality, expectedScore);
     }
 
-    private static class MockStrategyListener implements TimeZoneDetectorStrategy.StrategyListener {
-        private boolean mOnConfigurationChangedCalled;
+    private static class MockConfigChangeListener implements ConfigurationChangeListener {
+        private boolean mOnChangeCalled;
 
         @Override
-        public void onConfigurationChanged() {
-            mOnConfigurationChangedCalled = true;
+        public void onChange() {
+            mOnChangeCalled = true;
         }
 
-        void verifyOnConfigurationChangedCalled() {
-            assertTrue(mOnConfigurationChangedCalled);
+        void verifyOnChangeCalled() {
+            assertTrue(mOnChangeCalled);
         }
 
-        void verifyOnConfigurationChangedNotCalled() {
-            assertFalse(mOnConfigurationChangedCalled);
+        void verifyOnChangeNotCalled() {
+            assertFalse(mOnChangeCalled);
         }
 
         void reset() {
-            mOnConfigurationChangedCalled = false;
+            mOnChangeCalled = false;
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index bce1142..ca3f815 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -53,7 +53,6 @@
 import androidx.test.filters.MediumTest;
 
 import com.android.compatibility.common.util.SystemUtil;
-import com.android.internal.annotations.GuardedBy;
 
 import org.junit.After;
 import org.junit.Before;
@@ -76,14 +75,10 @@
 
     private static final int WAIT_TIMEOUT_MS = 5000;
     private static final Object sLock = new Object();
-    @GuardedBy("sLock")
-    private static boolean sTaskStackChangedCalled;
-    private static boolean sActivityBResumed;
 
     @Before
     public void setUp() throws Exception {
         mService = ActivityManager.getService();
-        sTaskStackChangedCalled = false;
     }
 
     @After
@@ -94,47 +89,33 @@
 
     @Test
     @Presubmit
-    @FlakyTest(bugId = 130388819)
     public void testTaskStackChanged_afterFinish() throws Exception {
+        final TestActivity activity = startTestActivity(ActivityA.class);
+        final CountDownLatch latch = new CountDownLatch(1);
         registerTaskStackChangedListener(new TaskStackListener() {
             @Override
             public void onTaskStackChanged() throws RemoteException {
-                synchronized (sLock) {
-                    sTaskStackChangedCalled = true;
-                }
+                latch.countDown();
             }
         });
 
-        Context context = getInstrumentation().getContext();
-        context.startActivity(
-                new Intent(context, ActivityA.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-        UiDevice.getInstance(getInstrumentation()).waitForIdle();
-        synchronized (sLock) {
-            assertTrue(sTaskStackChangedCalled);
-        }
-        assertTrue(sActivityBResumed);
+        activity.finish();
+        waitForCallback(latch);
     }
 
     @Test
     @Presubmit
     public void testTaskStackChanged_resumeWhilePausing() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
         registerTaskStackChangedListener(new TaskStackListener() {
             @Override
             public void onTaskStackChanged() throws RemoteException {
-                synchronized (sLock) {
-                    sTaskStackChangedCalled = true;
-                }
+                latch.countDown();
             }
         });
 
-        final Context context = getInstrumentation().getContext();
-        context.startActivity(new Intent(context, ResumeWhilePausingActivity.class).addFlags(
-                Intent.FLAG_ACTIVITY_NEW_TASK));
-        UiDevice.getInstance(getInstrumentation()).waitForIdle();
-
-        synchronized (sLock) {
-            assertTrue(sTaskStackChangedCalled);
-        }
+        startTestActivity(ResumeWhilePausingActivity.class);
+        waitForCallback(latch);
     }
 
     @Test
@@ -512,7 +493,7 @@
         try {
             final boolean result = latch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
             if (!result) {
-                throw new RuntimeException("Timed out waiting for task stack change notification");
+                throw new AssertionError("Timed out waiting for task stack change notification");
             }
         } catch (InterruptedException e) {
         }
@@ -569,19 +550,6 @@
     }
 
     public static class ActivityA extends TestActivity {
-
-        private boolean mActivityBLaunched = false;
-
-        @Override
-        protected void onPostResume() {
-            super.onPostResume();
-            if (mActivityBLaunched) {
-                return;
-            }
-            mActivityBLaunched = true;
-            finish();
-            startActivity(new Intent(this, ActivityB.class));
-        }
     }
 
     public static class ActivityB extends TestActivity {
@@ -589,10 +557,6 @@
         @Override
         protected void onPostResume() {
             super.onPostResume();
-            synchronized (sLock) {
-                sTaskStackChangedCalled = false;
-            }
-            sActivityBResumed = true;
             finish();
         }
     }