Merge "Add DTMB Frontend Status Support in Tuner JNI"
diff --git a/apex/Android.bp b/apex/Android.bp
index 266e672..c5b4901 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -107,7 +107,7 @@
 
     // Hide impl library and stub sources
     impl_library_visibility: [
-        ":__package__",
+        ":__pkg__",
         "//frameworks/base", // For framework-all
     ],
     stubs_source_visibility: ["//visibility:private"],
diff --git a/api/system-current.txt b/api/system-current.txt
index b3819cd..ff84613 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5293,7 +5293,7 @@
     method @NonNull public static android.media.tv.tuner.filter.IpFilterConfiguration.Builder builder();
     method @NonNull @Size(min=4, max=16) public byte[] getDstIpAddress();
     method public int getDstPort();
-    method public int getIpFilterContextId();
+    method @IntRange(from=0, to=61439) public int getIpFilterContextId();
     method @NonNull @Size(min=4, max=16) public byte[] getSrcIpAddress();
     method public int getSrcPort();
     method public int getType();
@@ -5329,6 +5329,7 @@
     method public boolean isPrivateData();
     method public boolean isPtsPresent();
     method public boolean isSecureMemory();
+    method public void release();
   }
 
   public final class MmtpFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration {
@@ -6308,7 +6309,9 @@
     method public void onHierarchyReported(int);
     method public void onInputStreamIdsReported(@NonNull int[]);
     method public void onLocked();
+    method public default void onModulationReported(int);
     method public void onPlpIdsReported(@NonNull int[]);
+    method public default void onPriorityReported(boolean);
     method public void onProgress(@IntRange(from=0, to=100) int);
     method public void onScanStopped();
     method public void onSignalTypeReported(int);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 3acafaa..b2c0b32 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -17,14 +17,17 @@
 #define DEBUG false
 
 #include "Log.h"
+
 #include "DurationMetricProducer.h"
-#include "guardrail/StatsdStats.h"
-#include "stats_util.h"
-#include "stats_log_util.h"
 
 #include <limits.h>
 #include <stdlib.h>
 
+#include "guardrail/StatsdStats.h"
+#include "metrics/parsing_utils/metrics_manager_util.h"
+#include "stats_log_util.h"
+#include "stats_util.h"
+
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
 using android::util::FIELD_TYPE_FLOAT;
@@ -64,8 +67,8 @@
 
 DurationMetricProducer::DurationMetricProducer(
         const ConfigKey& key, const DurationMetric& metric, const int conditionIndex,
-        const vector<ConditionState>& initialConditionCache, const size_t startIndex,
-        const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
+        const vector<ConditionState>& initialConditionCache, const int startIndex,
+        const int stopIndex, const int stopAllIndex, const bool nesting,
         const sp<ConditionWizard>& wizard, const uint64_t protoHash,
         const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
@@ -143,6 +146,84 @@
     VLOG("~DurationMetric() called");
 }
 
+bool DurationMetricProducer::onConfigUpdatedLocked(
+        const StatsdConfig& config, const int configIndex, const int metricIndex,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+        const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+        const sp<EventMatcherWizard>& matcherWizard,
+        const vector<sp<ConditionTracker>>& allConditionTrackers,
+        const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard,
+        const unordered_map<int64_t, int>& metricToActivationMap,
+        unordered_map<int, vector<int>>& trackerToMetricMap,
+        unordered_map<int, vector<int>>& conditionToMetricMap,
+        unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+        unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+        vector<int>& metricsWithActivation) {
+    if (!MetricProducer::onConfigUpdatedLocked(
+                config, configIndex, metricIndex, allAtomMatchingTrackers,
+                oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+                allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap,
+                trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
+                deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
+        return false;
+    }
+
+    const DurationMetric& metric = config.duration_metric(configIndex);
+    const auto& what_it = conditionTrackerMap.find(metric.what());
+    if (what_it == conditionTrackerMap.end()) {
+        ALOGE("DurationMetric's \"what\" is not present in the config");
+        return false;
+    }
+
+    const Predicate& durationWhat = config.predicate(what_it->second);
+    if (durationWhat.contents_case() != Predicate::ContentsCase::kSimplePredicate) {
+        ALOGE("DurationMetric's \"what\" must be a simple condition");
+        return false;
+    }
+
+    const SimplePredicate& simplePredicate = durationWhat.simple_predicate();
+
+    // Update indices: mStartIndex, mStopIndex, mStopAllIndex, mConditionIndex and MetricsManager
+    // maps.
+    if (!handleMetricWithAtomMatchingTrackers(simplePredicate.start(), metricIndex,
+                                              metric.has_dimensions_in_what(),
+                                              allAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                              trackerToMetricMap, mStartIndex)) {
+        ALOGE("Duration metrics must specify a valid start event matcher");
+        return false;
+    }
+
+    if (simplePredicate.has_stop() &&
+        !handleMetricWithAtomMatchingTrackers(simplePredicate.stop(), metricIndex,
+                                              metric.has_dimensions_in_what(),
+                                              allAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                              trackerToMetricMap, mStopIndex)) {
+        return false;
+    }
+
+    if (simplePredicate.has_stop_all() &&
+        !handleMetricWithAtomMatchingTrackers(simplePredicate.stop_all(), metricIndex,
+                                              metric.has_dimensions_in_what(),
+                                              allAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                              trackerToMetricMap, mStopAllIndex)) {
+        return false;
+    }
+
+    if (metric.has_condition() &&
+        !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+                                    metric.links(), allConditionTrackers, mConditionTrackerIndex,
+                                    conditionToMetricMap)) {
+        return false;
+    }
+
+    for (const auto& it : mCurrentSlicedDurationTrackerMap) {
+        it.second->onConfigUpdated(wizard, mConditionTrackerIndex);
+    }
+
+    return true;
+}
+
 sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(
         const Alert &alert, const sp<AlarmMonitor>& anomalyAlarmMonitor) {
     std::lock_guard<std::mutex> lock(mMutex);
@@ -550,7 +631,7 @@
     }
 
     // Handles Stopall events.
-    if (matcherIndex == mStopAllIndex) {
+    if ((int)matcherIndex == mStopAllIndex) {
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
             whatIt.second->noteStopAll(event.GetElapsedTimestampNs());
         }
@@ -598,7 +679,7 @@
     }
 
     // Handles Stop events.
-    if (matcherIndex == mStopIndex) {
+    if ((int)matcherIndex == mStopIndex) {
         if (mUseWhatDimensionAsInternalDimension) {
             auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
             if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 3a94d9c..01198a9 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -40,8 +40,8 @@
 public:
     DurationMetricProducer(
             const ConfigKey& key, const DurationMetric& durationMetric, const int conditionIndex,
-            const vector<ConditionState>& initialConditionCache, const size_t startIndex,
-            const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
+            const vector<ConditionState>& initialConditionCache, const int startIndex,
+            const int stopIndex, const int stopAllIndex, const bool nesting,
             const sp<ConditionWizard>& wizard, const uint64_t protoHash,
             const FieldMatcher& internalDimensions, const int64_t timeBaseNs,
             const int64_t startTimeNs,
@@ -112,16 +112,32 @@
     void flushCurrentBucketLocked(const int64_t& eventTimeNs,
                                   const int64_t& nextBucketStartTimeNs) override;
 
+    bool onConfigUpdatedLocked(
+            const StatsdConfig& config, const int configIndex, const int metricIndex,
+            const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+            const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+            const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+            const sp<EventMatcherWizard>& matcherWizard,
+            const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+            const std::unordered_map<int64_t, int>& conditionTrackerMap,
+            const sp<ConditionWizard>& wizard,
+            const std::unordered_map<int64_t, int>& metricToActivationMap,
+            std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+            std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+            std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+            std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+            std::vector<int>& metricsWithActivation) override;
+
     const DurationMetric_AggregationType mAggregationType;
 
     // Index of the SimpleAtomMatcher which defines the start.
-    const size_t mStartIndex;
+    int mStartIndex;
 
     // Index of the SimpleAtomMatcher which defines the stop.
-    const size_t mStopIndex;
+    int mStopIndex;
 
     // Index of the SimpleAtomMatcher which defines the stop all for all dimensions.
-    const size_t mStopAllIndex;
+    int mStopAllIndex;
 
     // nest counting -- for the same key, stops must match the number of starts to make real stop
     const bool mNested;
@@ -167,6 +183,8 @@
                 TestSumDurationWithSplitInFollowingBucket);
     FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDuration);
     FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket);
+
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 18e62d2..92c1a6e 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -569,6 +569,7 @@
     FRIEND_TEST(ConfigUpdateTest, TestUpdateCountMetrics);
     FRIEND_TEST(ConfigUpdateTest, TestUpdateEventMetrics);
     FRIEND_TEST(ConfigUpdateTest, TestUpdateGaugeMetrics);
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics);
     FRIEND_TEST(ConfigUpdateTest, TestUpdateMetricsMultipleTypes);
 };
 
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 8d59d13..657b2e4 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -89,6 +89,12 @@
 
     virtual ~DurationTracker(){};
 
+    void onConfigUpdated(const sp<ConditionWizard>& wizard, const int conditionTrackerIndex) {
+        sp<ConditionWizard> tmpWizard = mWizard;
+        mWizard = wizard;
+        mConditionTrackerIndex = conditionTrackerIndex;
+    };
+
     virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
                            const ConditionKey& conditionKey) = 0;
     virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
@@ -191,7 +197,7 @@
 
     sp<ConditionWizard> mWizard;
 
-    const int mConditionTrackerIndex;
+    int mConditionTrackerIndex;
 
     const int64_t mBucketSizeNs;
 
@@ -217,6 +223,8 @@
     FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
     FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm);
     FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm);
+
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index d32f5a9..cfc6e3f 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -697,6 +697,43 @@
         }
         newMetricProducers.push_back(producer.value());
     }
+    for (int i = 0; i < config.duration_metric_size(); i++, metricIndex++) {
+        const DurationMetric& metric = config.duration_metric(i);
+        newMetricProducerMap[metric.id()] = metricIndex;
+        optional<sp<MetricProducer>> producer;
+        switch (metricsToUpdate[metricIndex]) {
+            case UPDATE_PRESERVE: {
+                producer = updateMetric(
+                        config, i, metricIndex, metric.id(), allAtomMatchingTrackers,
+                        oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+                        allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap,
+                        oldMetricProducers, metricToActivationMap, trackerToMetricMap,
+                        conditionToMetricMap, activationAtomTrackerToMetricMap,
+                        deactivationAtomTrackerToMetricMap, metricsWithActivation);
+                break;
+            }
+            case UPDATE_REPLACE:
+            case UPDATE_NEW: {
+                producer = createDurationMetricProducerAndUpdateMetadata(
+                        key, config, timeBaseNs, currentTimeNs, metric, metricIndex,
+                        allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers,
+                        conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap,
+                        allStateGroupMaps, metricToActivationMap, trackerToMetricMap,
+                        conditionToMetricMap, activationAtomTrackerToMetricMap,
+                        deactivationAtomTrackerToMetricMap, metricsWithActivation);
+                break;
+            }
+            default: {
+                ALOGE("Metric \"%lld\" update state is unknown. This should never happen",
+                      (long long)metric.id());
+                return false;
+            }
+        }
+        if (!producer) {
+            return false;
+        }
+        newMetricProducers.push_back(producer.value());
+    }
     for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) {
         newMetricProducerMap[config.event_metric(i).id()] = metricIndex;
         const EventMetric& metric = config.event_metric(i);
@@ -770,7 +807,7 @@
         }
         newMetricProducers.push_back(producer.value());
     }
-    // TODO: perform update for value, duration metric.
+    // TODO: perform update for value metric.
 
     const set<int> atomsAllowedFromAnyUid(config.whitelisted_atom_ids().begin(),
                                           config.whitelisted_atom_ids().end());
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index 34e265c..b7dc2c7 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -365,7 +365,7 @@
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
         vector<int>& metricsWithActivation) {
     if (!metric.has_id() || !metric.has_what()) {
-        ALOGW("cannot find metric id or \"what\" in CountMetric \"%lld\"", (long long)metric.id());
+        ALOGE("cannot find metric id or \"what\" in CountMetric \"%lld\"", (long long)metric.id());
         return nullopt;
     }
     int trackerIndex;
@@ -423,6 +423,125 @@
                                     eventDeactivationMap, slicedStateAtoms, stateGroupMap)};
 }
 
+optional<sp<MetricProducer>> createDurationMetricProducerAndUpdateMetadata(
+        const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+        const int64_t currentTimeNs, const DurationMetric& metric, const int metricIndex,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap,
+        vector<sp<ConditionTracker>>& allConditionTrackers,
+        const unordered_map<int64_t, int>& conditionTrackerMap,
+        const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+        const unordered_map<int64_t, int>& stateAtomIdMap,
+        const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
+        const unordered_map<int64_t, int>& metricToActivationMap,
+        unordered_map<int, vector<int>>& trackerToMetricMap,
+        unordered_map<int, vector<int>>& conditionToMetricMap,
+        unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+        unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+        vector<int>& metricsWithActivation) {
+    if (!metric.has_id() || !metric.has_what()) {
+        ALOGE("cannot find metric id or \"what\" in DurationMetric \"%lld\"",
+              (long long)metric.id());
+        return nullopt;
+    }
+    const auto& what_it = conditionTrackerMap.find(metric.what());
+    if (what_it == conditionTrackerMap.end()) {
+        ALOGE("DurationMetric's \"what\" is not present in the condition trackers");
+        return nullopt;
+    }
+
+    const Predicate& durationWhat = config.predicate(what_it->second);
+    if (durationWhat.contents_case() != Predicate::ContentsCase::kSimplePredicate) {
+        ALOGE("DurationMetric's \"what\" must be a simple condition");
+        return nullopt;
+    }
+
+    const SimplePredicate& simplePredicate = durationWhat.simple_predicate();
+    bool nesting = simplePredicate.count_nesting();
+
+    int startIndex = -1, stopIndex = -1, stopAllIndex = -1;
+    if (!simplePredicate.has_start() ||
+        !handleMetricWithAtomMatchingTrackers(
+                simplePredicate.start(), metricIndex, metric.has_dimensions_in_what(),
+                allAtomMatchingTrackers, atomMatchingTrackerMap, trackerToMetricMap, startIndex)) {
+        ALOGE("Duration metrics must specify a valid start event matcher");
+        return nullopt;
+    }
+
+    if (simplePredicate.has_stop() &&
+        !handleMetricWithAtomMatchingTrackers(
+                simplePredicate.stop(), metricIndex, metric.has_dimensions_in_what(),
+                allAtomMatchingTrackers, atomMatchingTrackerMap, trackerToMetricMap, stopIndex)) {
+        return nullopt;
+    }
+
+    if (simplePredicate.has_stop_all() &&
+        !handleMetricWithAtomMatchingTrackers(simplePredicate.stop_all(), metricIndex,
+                                              metric.has_dimensions_in_what(),
+                                              allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                              trackerToMetricMap, stopAllIndex)) {
+        return nullopt;
+    }
+
+    FieldMatcher internalDimensions = simplePredicate.dimensions();
+
+    int conditionIndex = -1;
+    if (metric.has_condition()) {
+        if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+                                        metric.links(), allConditionTrackers, conditionIndex,
+                                        conditionToMetricMap)) {
+            return nullopt;
+        }
+    } else if (metric.links_size() > 0) {
+        ALOGW("metrics has a MetricConditionLink but doesn't have a condition");
+        return nullopt;
+    }
+
+    std::vector<int> slicedStateAtoms;
+    unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+    if (metric.slice_by_state_size() > 0) {
+        if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) {
+            ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state");
+            return nullopt;
+        }
+        if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+                                    allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+            return nullopt;
+        }
+    } else if (metric.state_link_size() > 0) {
+        ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state");
+        return nullopt;
+    }
+
+    // Check that all metric state links are a subset of dimensions_in_what fields.
+    std::vector<Matcher> dimensionsInWhat;
+    translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
+    for (const auto& stateLink : metric.state_link()) {
+        if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
+            return nullopt;
+        }
+    }
+
+    unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+    unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+    if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap,
+                                atomMatchingTrackerMap, activationAtomTrackerToMetricMap,
+                                deactivationAtomTrackerToMetricMap, metricsWithActivation,
+                                eventActivationMap, eventDeactivationMap)) {
+        return nullopt;
+    }
+
+    uint64_t metricHash;
+    if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) {
+        return nullopt;
+    }
+
+    return {new DurationMetricProducer(
+            key, metric, conditionIndex, initialConditionCache, startIndex, stopIndex, stopAllIndex,
+            nesting, wizard, metricHash, internalDimensions, timeBaseNs, currentTimeNs,
+            eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap)};
+}
+
 optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata(
         const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
         const EventMetric& metric, const int metricIndex,
@@ -438,7 +557,7 @@
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
         vector<int>& metricsWithActivation) {
     if (!metric.has_id() || !metric.has_what()) {
-        ALOGW("cannot find the metric name or what in config");
+        ALOGE("cannot find the metric name or what in config");
         return nullopt;
     }
     int trackerIndex;
@@ -497,7 +616,7 @@
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
         vector<int>& metricsWithActivation) {
     if (!metric.has_id() || !metric.has_what()) {
-        ALOGW("cannot find metric id or \"what\" in GaugeMetric \"%lld\"", (long long)metric.id());
+        ALOGE("cannot find metric id or \"what\" in GaugeMetric \"%lld\"", (long long)metric.id());
         return nullopt;
     }
 
@@ -760,114 +879,17 @@
         const DurationMetric& metric = config.duration_metric(i);
         metricMap.insert({metric.id(), metricIndex});
 
-        auto what_it = conditionTrackerMap.find(metric.what());
-        if (what_it == conditionTrackerMap.end()) {
-            ALOGE("DurationMetric's \"what\" is invalid");
-            return false;
-        }
-
-        const Predicate& durationWhat = config.predicate(what_it->second);
-
-        if (durationWhat.contents_case() != Predicate::ContentsCase::kSimplePredicate) {
-            ALOGE("DurationMetric's \"what\" must be a simple condition");
-            return false;
-        }
-
-        const auto& simplePredicate = durationWhat.simple_predicate();
-
-        bool nesting = simplePredicate.count_nesting();
-
-        int trackerIndices[3] = {-1, -1, -1};
-        if (!simplePredicate.has_start() ||
-            !handleMetricWithAtomMatchingTrackers(simplePredicate.start(), metricIndex,
-                                                  metric.has_dimensions_in_what(),
-                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
-                                                  trackerToMetricMap, trackerIndices[0])) {
-            ALOGE("Duration metrics must specify a valid the start event matcher");
-            return false;
-        }
-
-        if (simplePredicate.has_stop() &&
-            !handleMetricWithAtomMatchingTrackers(simplePredicate.stop(), metricIndex,
-                                                  metric.has_dimensions_in_what(),
-                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
-                                                  trackerToMetricMap, trackerIndices[1])) {
-            return false;
-        }
-
-        if (simplePredicate.has_stop_all() &&
-            !handleMetricWithAtomMatchingTrackers(simplePredicate.stop_all(), metricIndex,
-                                                  metric.has_dimensions_in_what(),
-                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
-                                                  trackerToMetricMap, trackerIndices[2])) {
-            return false;
-        }
-
-        FieldMatcher internalDimensions = simplePredicate.dimensions();
-
-        int conditionIndex = -1;
-
-        if (metric.has_condition()) {
-            bool good = handleMetricWithConditions(
-                    metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
-                    allConditionTrackers, conditionIndex, conditionToMetricMap);
-            if (!good) {
-                return false;
-            }
-        } else {
-            if (metric.links_size() > 0) {
-                ALOGW("metrics has a MetricConditionLink but doesn't have a condition");
-                return false;
-            }
-        }
-
-        std::vector<int> slicedStateAtoms;
-        unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
-        if (metric.slice_by_state_size() > 0) {
-            if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) {
-                ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state");
-                return false;
-            }
-            if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
-                                        allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
-                return false;
-            }
-        } else {
-            if (metric.state_link_size() > 0) {
-                ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state");
-                return false;
-            }
-        }
-
-        // Check that all metric state links are a subset of dimensions_in_what fields.
-        std::vector<Matcher> dimensionsInWhat;
-        translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
-        for (const auto& stateLink : metric.state_link()) {
-            if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
-                return false;
-            }
-        }
-
-        unordered_map<int, shared_ptr<Activation>> eventActivationMap;
-        unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
-        bool success = handleMetricActivation(
-                config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
+        optional<sp<MetricProducer>> producer = createDurationMetricProducerAndUpdateMetadata(
+                key, config, timeBaseTimeNs, currentTimeNs, metric, metricIndex,
+                allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers,
+                conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap,
+                allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                metricsWithActivation, eventActivationMap, eventDeactivationMap);
-        if (!success) return false;
-
-        uint64_t metricHash;
-        if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) {
+                metricsWithActivation);
+        if (!producer) {
             return false;
         }
-
-        sp<MetricProducer> durationMetric = new DurationMetricProducer(
-                key, metric, conditionIndex, initialConditionCache, trackerIndices[0],
-                trackerIndices[1], trackerIndices[2], nesting, wizard, metricHash,
-                internalDimensions, timeBaseTimeNs, currentTimeNs, eventActivationMap,
-                eventDeactivationMap, slicedStateAtoms, stateGroupMap);
-
-        allMetricProducers.push_back(durationMetric);
+        allMetricProducers.push_back(producer.value());
     }
 
     // build EventMetricProducer
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
index f909aff..6d1e6dd 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
@@ -112,6 +112,25 @@
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
         std::vector<int>& metricsWithActivation);
 
+// Creates a DurationMetricProducer and updates the vectors/maps used by MetricsManager with
+// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
+optional<sp<MetricProducer>> createDurationMetricProducerAndUpdateMetadata(
+        const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+        const int64_t currentTimeNs, const DurationMetric& metric, const int metricIndex,
+        const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+        std::vector<sp<ConditionTracker>>& allConditionTrackers,
+        const std::unordered_map<int64_t, int>& conditionTrackerMap,
+        const std::vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+        const std::unordered_map<int64_t, int>& stateAtomIdMap,
+        const std::unordered_map<int64_t, std::unordered_map<int, int64_t>>& allStateGroupMaps,
+        const std::unordered_map<int64_t, int>& metricToActivationMap,
+        std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+        std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+        std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+        std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+        std::vector<int>& metricsWithActivation);
+
 // Creates an EventMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
 optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata(
diff --git a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
index a20be15..dc951be 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -27,6 +27,7 @@
 #include "src/condition/CombinationConditionTracker.h"
 #include "src/condition/SimpleConditionTracker.h"
 #include "src/matchers/CombinationAtomMatchingTracker.h"
+#include "src/metrics/DurationMetricProducer.h"
 #include "src/metrics/GaugeMetricProducer.h"
 #include "src/metrics/parsing_utils/metrics_manager_util.h"
 #include "tests/statsd_test_util.h"
@@ -2212,6 +2213,352 @@
     EXPECT_EQ(oldMatcherWizard->getStrongCount(), 1);
 }
 
+TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) {
+    StatsdConfig config;
+    // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig.
+    AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher();
+    int64_t matcher1Id = matcher1.id();
+    *config.add_atom_matcher() = matcher1;
+
+    AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher();
+    int64_t matcher2Id = matcher2.id();
+    *config.add_atom_matcher() = matcher2;
+
+    AtomMatcher matcher3 = CreateAcquireWakelockAtomMatcher();
+    int64_t matcher3Id = matcher3.id();
+    *config.add_atom_matcher() = matcher3;
+
+    AtomMatcher matcher4 = CreateReleaseWakelockAtomMatcher();
+    int64_t matcher4Id = matcher4.id();
+    *config.add_atom_matcher() = matcher4;
+
+    AtomMatcher matcher5 = CreateMoveToForegroundAtomMatcher();
+    int64_t matcher5Id = matcher5.id();
+    *config.add_atom_matcher() = matcher5;
+
+    AtomMatcher matcher6 = CreateMoveToBackgroundAtomMatcher();
+    int64_t matcher6Id = matcher6.id();
+    *config.add_atom_matcher() = matcher6;
+
+    AtomMatcher matcher7 = CreateBatteryStateNoneMatcher();
+    int64_t matcher7Id = matcher7.id();
+    *config.add_atom_matcher() = matcher7;
+
+    AtomMatcher matcher8 = CreateBatteryStateUsbMatcher();
+    int64_t matcher8Id = matcher8.id();
+    *config.add_atom_matcher() = matcher8;
+
+    Predicate predicate1 = CreateScreenIsOnPredicate();
+    int64_t predicate1Id = predicate1.id();
+    *config.add_predicate() = predicate1;
+
+    Predicate predicate2 = CreateScreenIsOffPredicate();
+    int64_t predicate2Id = predicate2.id();
+    *config.add_predicate() = predicate2;
+
+    Predicate predicate3 = CreateDeviceUnpluggedPredicate();
+    int64_t predicate3Id = predicate3.id();
+    *config.add_predicate() = predicate3;
+
+    Predicate predicate4 = CreateIsInBackgroundPredicate();
+    *predicate4.mutable_simple_predicate()->mutable_dimensions() =
+            CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1});
+    int64_t predicate4Id = predicate4.id();
+    *config.add_predicate() = predicate4;
+
+    Predicate predicate5 = CreateHoldingWakelockPredicate();
+    *predicate5.mutable_simple_predicate()->mutable_dimensions() =
+            CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    predicate5.mutable_simple_predicate()->set_stop_all(matcher7Id);
+    int64_t predicate5Id = predicate5.id();
+    *config.add_predicate() = predicate5;
+
+    State state1 = CreateScreenStateWithOnOffMap(0x123, 0x321);
+    int64_t state1Id = state1.id();
+    *config.add_state() = state1;
+
+    State state2 = CreateScreenState();
+    int64_t state2Id = state2.id();
+    *config.add_state() = state2;
+
+    // Add a few duration metrics.
+    // Will be preserved.
+    DurationMetric duration1 =
+            createDurationMetric("DURATION1", predicate5Id, predicate4Id, {state2Id});
+    *duration1.mutable_dimensions_in_what() =
+            CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    MetricConditionLink* link = duration1.add_links();
+    link->set_condition(predicate4Id);
+    *link->mutable_fields_in_what() =
+            CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    *link->mutable_fields_in_condition() =
+            CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1} /*uid field*/);
+    int64_t duration1Id = duration1.id();
+    *config.add_duration_metric() = duration1;
+
+    // Will be replaced.
+    DurationMetric duration2 = createDurationMetric("DURATION2", predicate1Id, nullopt, {});
+    int64_t duration2Id = duration2.id();
+    *config.add_duration_metric() = duration2;
+
+    // Will be replaced.
+    DurationMetric duration3 = createDurationMetric("DURATION3", predicate3Id, nullopt, {state1Id});
+    int64_t duration3Id = duration3.id();
+    *config.add_duration_metric() = duration3;
+
+    // Will be replaced.
+    DurationMetric duration4 = createDurationMetric("DURATION4", predicate3Id, predicate2Id, {});
+    int64_t duration4Id = duration4.id();
+    *config.add_duration_metric() = duration4;
+
+    // Will be deleted.
+    DurationMetric duration5 = createDurationMetric("DURATION5", predicate2Id, nullopt, {});
+    int64_t duration5Id = duration5.id();
+    *config.add_duration_metric() = duration5;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Make some sliced conditions true.
+    int uid1 = 10;
+    int uid2 = 11;
+    vector<MatchingState> matchingStates(8, MatchingState::kNotMatched);
+    matchingStates[2] = kMatched;
+    vector<ConditionState> conditionCache(5, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(5, false);
+    unique_ptr<LogEvent> event = CreateAcquireWakelockEvent(timeBaseNs + 3, {uid1}, {"tag"}, "wl1");
+    oldConditionTrackers[4]->evaluateCondition(*event.get(), matchingStates, oldConditionTrackers,
+                                               conditionCache, changedCache);
+    EXPECT_TRUE(oldConditionTrackers[4]->isSliced());
+    EXPECT_TRUE(changedCache[4]);
+    EXPECT_EQ(conditionCache[4], ConditionState::kTrue);
+    oldMetricProducers[0]->onMatchedLogEvent(2, *event.get());
+
+    fill(conditionCache.begin(), conditionCache.end(), ConditionState::kNotEvaluated);
+    fill(changedCache.begin(), changedCache.end(), false);
+    event = CreateAcquireWakelockEvent(timeBaseNs + 3, {uid2}, {"tag"}, "wl2");
+    oldConditionTrackers[4]->evaluateCondition(*event.get(), matchingStates, oldConditionTrackers,
+                                               conditionCache, changedCache);
+    EXPECT_TRUE(changedCache[4]);
+    EXPECT_EQ(conditionCache[4], ConditionState::kTrue);
+    oldMetricProducers[0]->onMatchedLogEvent(2, *event.get());
+
+    // Used later to ensure the condition wizard is replaced. Get it before doing the update.
+    // The duration trackers have a pointer to the wizard, and 2 trackers were created above.
+    sp<ConditionWizard> oldConditionWizard = oldMetricProducers[0]->mWizard;
+    EXPECT_EQ(oldConditionWizard->getStrongCount(), 8);
+
+    // Replace predicate1, predicate3, and state1. Causes duration2/3/4 to be replaced.
+    set<int64_t> replacedConditions({predicate1Id, predicate2Id});
+    set<int64_t> replacedStates({state1Id});
+
+    // New duration metric.
+    DurationMetric duration6 = createDurationMetric("DURATION6", predicate4Id, predicate5Id, {});
+    *duration6.mutable_dimensions_in_what() =
+            CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1} /*uid field*/);
+    link = duration6.add_links();
+    link->set_condition(predicate5Id);
+    *link->mutable_fields_in_what() =
+            CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1} /*uid field*/);
+    *link->mutable_fields_in_condition() =
+            CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    int64_t duration6Id = duration6.id();
+
+    // Map the matchers and predicates in reverse order to force the indices to change.
+    const int matcher8Index = 0, matcher7Index = 1, matcher6Index = 2, matcher5Index = 3,
+              matcher4Index = 4, matcher3Index = 5, matcher2Index = 6, matcher1Index = 7;
+    std::unordered_map<int64_t, int> newAtomMatchingTrackerMap({{matcher8Id, matcher8Index},
+                                                                {matcher7Id, matcher7Index},
+                                                                {matcher6Id, matcher6Index},
+                                                                {matcher5Id, matcher5Index},
+                                                                {matcher4Id, matcher4Index},
+                                                                {matcher3Id, matcher3Index},
+                                                                {matcher2Id, matcher2Index},
+                                                                {matcher1Id, matcher1Index}});
+    // Use the existing matchers. A bit hacky, but saves code and we don't rely on them.
+    vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers(8);
+    reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(),
+                 newAtomMatchingTrackers.begin());
+
+    const int predicate5Index = 0, predicate4Index = 1, predicate3Index = 2, predicate2Index = 3,
+              predicate1Index = 4;
+    std::unordered_map<int64_t, int> newConditionTrackerMap({
+            {predicate5Id, predicate5Index},
+            {predicate4Id, predicate4Index},
+            {predicate3Id, predicate3Index},
+            {predicate2Id, predicate2Index},
+            {predicate1Id, predicate1Index},
+    });
+    // Use the existing conditionTrackers and reinitialize them to get the initial condition cache.
+    vector<sp<ConditionTracker>> newConditionTrackers(5);
+    reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(),
+                 newConditionTrackers.begin());
+    vector<Predicate> conditionProtos(5);
+    reverse_copy(config.predicate().begin(), config.predicate().end(), conditionProtos.begin());
+    for (int i = 0; i < newConditionTrackers.size(); i++) {
+        EXPECT_TRUE(newConditionTrackers[i]->onConfigUpdated(
+                conditionProtos, i, newConditionTrackers, newAtomMatchingTrackerMap,
+                newConditionTrackerMap));
+    }
+    vector<bool> cycleTracker(5, false);
+    fill(conditionCache.begin(), conditionCache.end(), ConditionState::kNotEvaluated);
+    for (int i = 0; i < newConditionTrackers.size(); i++) {
+        EXPECT_TRUE(newConditionTrackers[i]->init(conditionProtos, newConditionTrackers,
+                                                  newConditionTrackerMap, cycleTracker,
+                                                  conditionCache));
+    }
+    // Predicate5 should be true since 2 uids have wakelocks
+    EXPECT_EQ(conditionCache, vector({kTrue, kUnknown, kUnknown, kUnknown, kUnknown}));
+
+    StatsdConfig newConfig;
+    *newConfig.add_duration_metric() = duration6;
+    const int duration6Index = 0;
+    *newConfig.add_duration_metric() = duration3;
+    const int duration3Index = 1;
+    *newConfig.add_duration_metric() = duration1;
+    const int duration1Index = 2;
+    *newConfig.add_duration_metric() = duration4;
+    const int duration4Index = 3;
+    *newConfig.add_duration_metric() = duration2;
+    const int duration2Index = 4;
+
+    for (const Predicate& predicate : conditionProtos) {
+        *newConfig.add_predicate() = predicate;
+    }
+    *newConfig.add_state() = state1;
+    *newConfig.add_state() = state2;
+    unordered_map<int64_t, int> stateAtomIdMap;
+    unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
+    map<int64_t, uint64_t> stateProtoHashes;
+    EXPECT_TRUE(initStates(newConfig, stateAtomIdMap, allStateGroupMaps, stateProtoHashes));
+
+    // Output data structures to validate.
+    unordered_map<int64_t, int> newMetricProducerMap;
+    vector<sp<MetricProducer>> newMetricProducers;
+    unordered_map<int, vector<int>> conditionToMetricMap;
+    unordered_map<int, vector<int>> trackerToMetricMap;
+    set<int64_t> noReportMetricIds;
+    unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+    vector<int> metricsWithActivation;
+    EXPECT_TRUE(updateMetrics(
+            key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
+            oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, /*replacedMatchers=*/{},
+            newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions,
+            newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
+            oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers,
+            conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+            metricsWithActivation));
+
+    unordered_map<int64_t, int> expectedMetricProducerMap = {
+            {duration1Id, duration1Index}, {duration2Id, duration2Index},
+            {duration3Id, duration3Index}, {duration4Id, duration4Index},
+            {duration6Id, duration6Index},
+    };
+    EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+
+    // Make sure preserved metrics are the same.
+    ASSERT_EQ(newMetricProducers.size(), 5);
+    EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(duration1Id)],
+              newMetricProducers[newMetricProducerMap.at(duration1Id)]);
+
+    // Make sure replaced metrics are different.
+    EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(duration2Id)],
+              newMetricProducers[newMetricProducerMap.at(duration2Id)]);
+    EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(duration3Id)],
+              newMetricProducers[newMetricProducerMap.at(duration3Id)]);
+    EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(duration4Id)],
+              newMetricProducers[newMetricProducerMap.at(duration4Id)]);
+
+    // Verify the conditionToMetricMap. Note that the "what" is not in this map.
+    ASSERT_EQ(conditionToMetricMap.size(), 3);
+    const vector<int>& condition2Metrics = conditionToMetricMap[predicate2Index];
+    EXPECT_THAT(condition2Metrics, UnorderedElementsAre(duration4Index));
+    const vector<int>& condition4Metrics = conditionToMetricMap[predicate4Index];
+    EXPECT_THAT(condition4Metrics, UnorderedElementsAre(duration1Index));
+    const vector<int>& condition5Metrics = conditionToMetricMap[predicate5Index];
+    EXPECT_THAT(condition5Metrics, UnorderedElementsAre(duration6Index));
+
+    // Verify the trackerToMetricMap. The start/stop/stopall indices from the "what" should be here.
+    ASSERT_EQ(trackerToMetricMap.size(), 8);
+    const vector<int>& matcher1Metrics = trackerToMetricMap[matcher1Index];
+    EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(duration2Index));
+    const vector<int>& matcher2Metrics = trackerToMetricMap[matcher2Index];
+    EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(duration2Index));
+    const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index];
+    EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(duration1Index));
+    const vector<int>& matcher4Metrics = trackerToMetricMap[matcher4Index];
+    EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(duration1Index));
+    const vector<int>& matcher5Metrics = trackerToMetricMap[matcher5Index];
+    EXPECT_THAT(matcher5Metrics, UnorderedElementsAre(duration6Index));
+    const vector<int>& matcher6Metrics = trackerToMetricMap[matcher6Index];
+    EXPECT_THAT(matcher6Metrics, UnorderedElementsAre(duration6Index));
+    const vector<int>& matcher7Metrics = trackerToMetricMap[matcher7Index];
+    EXPECT_THAT(matcher7Metrics,
+                UnorderedElementsAre(duration1Index, duration3Index, duration4Index));
+    const vector<int>& matcher8Metrics = trackerToMetricMap[matcher8Index];
+    EXPECT_THAT(matcher8Metrics, UnorderedElementsAre(duration3Index, duration4Index));
+
+    // Verify event activation/deactivation maps.
+    ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0);
+    ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0);
+    ASSERT_EQ(metricsWithActivation.size(), 0);
+
+    // Verify tracker indices/ids/conditions are correct.
+    DurationMetricProducer* durationProducer1 =
+            static_cast<DurationMetricProducer*>(newMetricProducers[duration1Index].get());
+    EXPECT_EQ(durationProducer1->getMetricId(), duration1Id);
+    EXPECT_EQ(durationProducer1->mConditionTrackerIndex, predicate4Index);
+    EXPECT_EQ(durationProducer1->mCondition, ConditionState::kUnknown);
+    EXPECT_EQ(durationProducer1->mStartIndex, matcher3Index);
+    EXPECT_EQ(durationProducer1->mStopIndex, matcher4Index);
+    EXPECT_EQ(durationProducer1->mStopAllIndex, matcher7Index);
+    EXPECT_EQ(durationProducer1->mCurrentSlicedDurationTrackerMap.size(), 2);
+    for (const auto& durationTrackerIt : durationProducer1->mCurrentSlicedDurationTrackerMap) {
+        EXPECT_EQ(durationTrackerIt.second->mConditionTrackerIndex, predicate4Index);
+    }
+    DurationMetricProducer* durationProducer2 =
+            static_cast<DurationMetricProducer*>(newMetricProducers[duration2Index].get());
+    EXPECT_EQ(durationProducer2->getMetricId(), duration2Id);
+    EXPECT_EQ(durationProducer2->mConditionTrackerIndex, -1);
+    EXPECT_EQ(durationProducer2->mCondition, ConditionState::kTrue);
+    EXPECT_EQ(durationProducer2->mStartIndex, matcher1Index);
+    EXPECT_EQ(durationProducer2->mStopIndex, matcher2Index);
+    EXPECT_EQ(durationProducer2->mStopAllIndex, -1);
+    DurationMetricProducer* durationProducer3 =
+            static_cast<DurationMetricProducer*>(newMetricProducers[duration3Index].get());
+    EXPECT_EQ(durationProducer3->getMetricId(), duration3Id);
+    EXPECT_EQ(durationProducer3->mConditionTrackerIndex, -1);
+    EXPECT_EQ(durationProducer3->mCondition, ConditionState::kTrue);
+    EXPECT_EQ(durationProducer3->mStartIndex, matcher7Index);
+    EXPECT_EQ(durationProducer3->mStopIndex, matcher8Index);
+    EXPECT_EQ(durationProducer3->mStopAllIndex, -1);
+    DurationMetricProducer* durationProducer4 =
+            static_cast<DurationMetricProducer*>(newMetricProducers[duration4Index].get());
+    EXPECT_EQ(durationProducer4->getMetricId(), duration4Id);
+    EXPECT_EQ(durationProducer4->mConditionTrackerIndex, predicate2Index);
+    EXPECT_EQ(durationProducer4->mCondition, ConditionState::kUnknown);
+    EXPECT_EQ(durationProducer4->mStartIndex, matcher7Index);
+    EXPECT_EQ(durationProducer4->mStopIndex, matcher8Index);
+    EXPECT_EQ(durationProducer4->mStopAllIndex, -1);
+    DurationMetricProducer* durationProducer6 =
+            static_cast<DurationMetricProducer*>(newMetricProducers[duration6Index].get());
+    EXPECT_EQ(durationProducer6->getMetricId(), duration6Id);
+    EXPECT_EQ(durationProducer6->mConditionTrackerIndex, predicate5Index);
+    // TODO(b/167491517): should this be unknown since the condition is sliced?
+    EXPECT_EQ(durationProducer6->mCondition, ConditionState::kTrue);
+    EXPECT_EQ(durationProducer6->mStartIndex, matcher6Index);
+    EXPECT_EQ(durationProducer6->mStopIndex, matcher5Index);
+    EXPECT_EQ(durationProducer6->mStopAllIndex, -1);
+
+    sp<ConditionWizard> newConditionWizard = newMetricProducers[0]->mWizard;
+    EXPECT_NE(newConditionWizard, oldConditionWizard);
+    EXPECT_EQ(newConditionWizard->getStrongCount(), 8);
+    oldMetricProducers.clear();
+    // Only reference to the old wizard should be the one in the test.
+    EXPECT_EQ(oldConditionWizard->getStrongCount(), 1);
+}
+
 TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) {
     StatsdConfig config;
     // Add atom matchers
@@ -2376,11 +2723,16 @@
     int64_t gaugeMetricId = gaugeMetric.id();
     *config.add_gauge_metric() = gaugeMetric;
 
+    // Preserved.
+    DurationMetric durationMetric = createDurationMetric("DURATION1", predicate1Id, nullopt, {});
+    int64_t durationMetricId = durationMetric.id();
+    *config.add_duration_metric() = durationMetric;
+
     EXPECT_TRUE(initConfig(config));
 
     // Used later to ensure the condition wizard is replaced. Get it before doing the update.
     sp<ConditionWizard> oldConditionWizard = oldMetricProducers[0]->mWizard;
-    EXPECT_EQ(oldConditionWizard->getStrongCount(), 4);
+    EXPECT_EQ(oldConditionWizard->getStrongCount(), 5);
 
     // Mark matcher 2 as replaced. Causes eventMetric to be replaced.
     set<int64_t> replacedMatchers;
@@ -2414,10 +2766,15 @@
     StatsdConfig newConfig;
     *newConfig.add_count_metric() = countMetric;
     const int countMetricIndex = 0;
+    *newConfig.add_duration_metric() = durationMetric;
+    const int durationMetricIndex = 1;
     *newConfig.add_event_metric() = eventMetric;
-    const int eventMetricIndex = 1;
+    const int eventMetricIndex = 2;
     *newConfig.add_gauge_metric() = gaugeMetric;
-    const int gaugeMetricIndex = 2;
+    const int gaugeMetricIndex = 3;
+
+    // Add the predicate since duration metric needs it.
+    *newConfig.add_predicate() = predicate1;
 
     // Output data structures to validate.
     unordered_map<int64_t, int> newMetricProducerMap;
@@ -2440,15 +2797,18 @@
 
     unordered_map<int64_t, int> expectedMetricProducerMap = {
             {countMetricId, countMetricIndex},
+            {durationMetricId, durationMetricIndex},
             {eventMetricId, eventMetricIndex},
             {gaugeMetricId, gaugeMetricIndex},
     };
     EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
 
     // Make sure preserved metrics are the same.
-    ASSERT_EQ(newMetricProducers.size(), 3);
+    ASSERT_EQ(newMetricProducers.size(), 4);
     EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(countMetricId)],
               newMetricProducers[newMetricProducerMap.at(countMetricId)]);
+    EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(durationMetricId)],
+              newMetricProducers[newMetricProducerMap.at(durationMetricId)]);
 
     // Make sure replaced metrics are different.
     EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(eventMetricId)],
@@ -2464,9 +2824,9 @@
     // Verify the trackerToMetricMap.
     ASSERT_EQ(trackerToMetricMap.size(), 3);
     const vector<int>& matcher1Metrics = trackerToMetricMap[matcher1Index];
-    EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(countMetricIndex));
+    EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(countMetricIndex, durationMetricIndex));
     const vector<int>& matcher2Metrics = trackerToMetricMap[matcher2Index];
-    EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(eventMetricIndex));
+    EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(eventMetricIndex, durationMetricIndex));
     const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index];
     EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gaugeMetricIndex));
 
@@ -2479,6 +2839,9 @@
     EXPECT_EQ(newMetricProducers[countMetricIndex]->getMetricId(), countMetricId);
     EXPECT_EQ(newMetricProducers[countMetricIndex]->mConditionTrackerIndex, predicate1Index);
     EXPECT_EQ(newMetricProducers[countMetricIndex]->mCondition, ConditionState::kUnknown);
+    EXPECT_EQ(newMetricProducers[durationMetricIndex]->getMetricId(), durationMetricId);
+    EXPECT_EQ(newMetricProducers[durationMetricIndex]->mConditionTrackerIndex, -1);
+    EXPECT_EQ(newMetricProducers[durationMetricIndex]->mCondition, ConditionState::kTrue);
     EXPECT_EQ(newMetricProducers[eventMetricIndex]->getMetricId(), eventMetricId);
     EXPECT_EQ(newMetricProducers[eventMetricIndex]->mConditionTrackerIndex, -1);
     EXPECT_EQ(newMetricProducers[eventMetricIndex]->mCondition, ConditionState::kTrue);
@@ -2488,7 +2851,7 @@
 
     sp<ConditionWizard> newConditionWizard = newMetricProducers[0]->mWizard;
     EXPECT_NE(newConditionWizard, oldConditionWizard);
-    EXPECT_EQ(newConditionWizard->getStrongCount(), 4);
+    EXPECT_EQ(newConditionWizard->getStrongCount(), 5);
     oldMetricProducers.clear();
     // Only reference to the old wizard should be the one in the test.
     EXPECT_EQ(oldConditionWizard->getStrongCount(), 1);
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index a6e8c13..0f3cdfc 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -616,7 +616,7 @@
     public static final String STRING_TYPE_MOTION_DETECT = "android.sensor.motion_detect";
 
     /**
-     * A constant describing a motion detect sensor.
+     * A constant describing a heart beat sensor.
      *
      * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details.
      *
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 9906331..236fab0 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -550,7 +550,7 @@
      *   <h4>{@link android.hardware.Sensor#TYPE_HEART_BEAT
      * Sensor.TYPE_HEART_BEAT}:</h4>
      *
-     * A sensor of this type returns an event everytime a hear beat peak is
+     * A sensor of this type returns an event everytime a heart beat peak is
      * detected.
      *
      * Peak here ideally corresponds to the positive peak in the QRS complex of
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 6ba1627..be21fea 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -109,11 +109,11 @@
     private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2;
     private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF = 3;
 
-    // Values for GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE
+    // Values for ANGLE_GL_DRIVER_ALL_ANGLE
     private static final int ANGLE_GL_DRIVER_ALL_ANGLE_ON = 1;
     private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0;
 
-    // Values for GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES
+    // Values for ANGLE_GL_DRIVER_SELECTION_VALUES
     private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
     private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
     private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
@@ -382,11 +382,11 @@
         final int allUseAngle;
         if (bundle != null) {
             allUseAngle =
-                    bundle.getInt(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
+                    bundle.getInt(Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE);
         } else {
             ContentResolver contentResolver = context.getContentResolver();
             allUseAngle = Settings.Global.getInt(contentResolver,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE,
+                    Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE,
                     ANGLE_GL_DRIVER_ALL_ANGLE_OFF);
         }
         if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
@@ -402,10 +402,10 @@
         final ContentResolver contentResolver = context.getContentResolver();
         final List<String> optInPackages =
                 getGlobalSettingsString(contentResolver, bundle,
-                        Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS);
+                        Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS);
         final List<String> optInValues =
                 getGlobalSettingsString(contentResolver, bundle,
-                        Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES);
+                        Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES);
 
         // Make sure we have good settings to use
         if (optInPackages.size() != optInValues.size()) {
@@ -462,11 +462,11 @@
 
         if (coreSettings != null) {
             debugPackage =
-                    coreSettings.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
+                    coreSettings.getString(Settings.Global.ANGLE_DEBUG_PACKAGE);
         } else {
             ContentResolver contentResolver = context.getContentResolver();
             debugPackage = Settings.Global.getString(contentResolver,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
+                    Settings.Global.ANGLE_DEBUG_PACKAGE);
         }
         if (TextUtils.isEmpty(debugPackage)) {
             return "";
@@ -578,7 +578,7 @@
         final ContentResolver contentResolver = context.getContentResolver();
         final List<String> angleAllowlist =
                 getGlobalSettingsString(contentResolver, bundle,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST);
+                    Settings.Global.ANGLE_ALLOWLIST);
 
         if (DEBUG) Log.v(TAG, "ANGLE allowlist: " + angleAllowlist);
 
@@ -678,7 +678,7 @@
         try {
             ContentResolver contentResolver = context.getContentResolver();
             final int showDialogBox = Settings.Global.getInt(contentResolver,
-                    Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX);
+                    Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX);
 
             return (showDialogBox == 1);
         } catch (Settings.SettingNotFoundException | SecurityException e) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a4212b5..2eee643 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12367,37 +12367,34 @@
          * to dumpable apps that opt-in.
          * @hide
          */
-        public static final String GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE =
-                "angle_debug_package";
+        public static final String ANGLE_DEBUG_PACKAGE = "angle_debug_package";
 
         /**
          * Force all PKGs to use ANGLE, regardless of any other settings
          * The value is a boolean (1 or 0).
          * @hide
          */
-        public static final String GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE =
-                "angle_gl_driver_all_angle";
+        public static final String ANGLE_GL_DRIVER_ALL_ANGLE = "angle_gl_driver_all_angle";
 
         /**
          * List of PKGs that have an OpenGL driver selected
          * @hide
          */
-        public static final String GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS =
+        public static final String ANGLE_GL_DRIVER_SELECTION_PKGS =
                 "angle_gl_driver_selection_pkgs";
 
         /**
          * List of selected OpenGL drivers, corresponding to the PKGs in GLOBAL_SETTINGS_DRIVER_PKGS
          * @hide
          */
-        public static final String GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES =
+        public static final String ANGLE_GL_DRIVER_SELECTION_VALUES =
                 "angle_gl_driver_selection_values";
 
         /**
          * List of package names that should check ANGLE rules
          * @hide
          */
-        public static final String GLOBAL_SETTINGS_ANGLE_ALLOWLIST =
-                "angle_allowlist";
+        public static final String ANGLE_ALLOWLIST = "angle_allowlist";
 
         /**
          * Lists of ANGLE EGL features for debugging.
@@ -12413,8 +12410,7 @@
          * The value is a boolean (1 or 0).
          * @hide
          */
-        public static final String GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX =
-                "show_angle_in_use_dialog_box";
+        public static final String SHOW_ANGLE_IN_USE_DIALOG_BOX = "show_angle_in_use_dialog_box";
 
         /**
          * Updatable driver global preference for all Apps.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsHandler.java
index de3261b..2ab087c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsHandler.java
@@ -22,9 +22,9 @@
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 
+import android.annotation.NonNull;
 import android.app.ActivityTaskManager;
 import android.app.ActivityTaskManager.RootTaskInfo;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
@@ -52,14 +52,11 @@
     private static final String TAG = PipBoundsHandler.class.getSimpleName();
     private static final float INVALID_SNAP_FRACTION = -1f;
 
+    private final @NonNull PipBoundsState mPipBoundsState;
     private final PipSnapAlgorithm mSnapAlgorithm;
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
     private DisplayLayout mDisplayLayout;
 
-    private ComponentName mLastPipComponentName;
-    private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
-    private Size mReentrySize;
-
     private float mDefaultAspectRatio;
     private float mMinAspectRatio;
     private float mMaxAspectRatio;
@@ -75,7 +72,8 @@
     private boolean mIsShelfShowing;
     private int mShelfHeight;
 
-    public PipBoundsHandler(Context context) {
+    public PipBoundsHandler(Context context, @NonNull PipBoundsState pipBoundsState) {
+        mPipBoundsState = pipBoundsState;
         mSnapAlgorithm = new PipSnapAlgorithm(context);
         mDisplayLayout = new DisplayLayout();
         reloadResources(context);
@@ -175,40 +173,6 @@
     }
 
     /**
-     * Responds to IPinnedStackListener on saving reentry snap fraction and size
-     * for a given {@link ComponentName}.
-     */
-    public void onSaveReentryBounds(ComponentName componentName, Rect bounds) {
-        mReentrySnapFraction = getSnapFraction(bounds);
-        mReentrySize = new Size(bounds.width(), bounds.height());
-        mLastPipComponentName = componentName;
-    }
-
-    /**
-     * Responds to IPinnedStackListener on resetting reentry snap fraction and size
-     * for a given {@link ComponentName}.
-     */
-    public void onResetReentryBounds(ComponentName componentName) {
-        if (componentName.equals(mLastPipComponentName)) {
-            onResetReentryBoundsUnchecked();
-        }
-    }
-
-    private void onResetReentryBoundsUnchecked() {
-        mReentrySnapFraction = INVALID_SNAP_FRACTION;
-        mReentrySize = null;
-        mLastPipComponentName = null;
-    }
-
-    /**
-     * Returns ture if there's a valid snap fraction. This is used with {@link EXTRA_IS_FIRST_ENTRY}
-     * to see if this is the first time user has entered PIP for the component.
-     */
-    public boolean hasSaveReentryBounds() {
-        return mReentrySnapFraction != INVALID_SNAP_FRACTION;
-    }
-
-    /**
      * The {@link PipSnapAlgorithm} is couple on display bounds
      * @return {@link PipSnapAlgorithm}.
      */
@@ -250,37 +214,43 @@
     }
 
     /**
-     * See {@link #getDestinationBounds(ComponentName, float, Rect, Size, boolean)}
+     * See {@link #getDestinationBounds(float, Rect, Size, boolean)}
      */
-    public Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds,
-            Size minimalSize) {
-        return getDestinationBounds(componentName, aspectRatio, bounds, minimalSize,
+    public Rect getDestinationBounds(float aspectRatio, Rect bounds, Size minimalSize) {
+        return getDestinationBounds(aspectRatio, bounds, minimalSize,
                 false /* useCurrentMinEdgeSize */);
     }
 
     /**
      * @return {@link Rect} of the destination PiP window bounds.
      */
-    public Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds,
+    public Rect getDestinationBounds(float aspectRatio, Rect bounds,
             Size minimalSize, boolean useCurrentMinEdgeSize) {
-        if (!componentName.equals(mLastPipComponentName)) {
-            onResetReentryBoundsUnchecked();
-            mLastPipComponentName = componentName;
-        }
+        boolean isReentryBounds = false;
         final Rect destinationBounds;
         if (bounds == null) {
-            final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize);
-            destinationBounds = new Rect(defaultBounds);
-            if (mReentrySnapFraction == INVALID_SNAP_FRACTION && mReentrySize == null) {
+            // Calculating initial entry bounds
+            final PipBoundsState.PipReentryState state = mPipBoundsState.getReentryState();
+
+            final Rect defaultBounds;
+            if (state != null) {
+                // Restore to reentry bounds.
+                defaultBounds = getDefaultBounds(state.getSnapFraction(), state.getSize());
+                isReentryBounds = true;
+            } else {
+                // Get actual default bounds.
+                defaultBounds = getDefaultBounds(INVALID_SNAP_FRACTION, null /* size */);
                 mOverrideMinimalSize = minimalSize;
             }
+
+            destinationBounds = new Rect(defaultBounds);
         } else {
+            // Just adjusting bounds (e.g. on aspect ratio changed).
             destinationBounds = new Rect(bounds);
         }
         if (isValidPictureInPictureAspectRatio(aspectRatio)) {
-            boolean useCurrentSize = bounds == null && mReentrySize != null;
             transformBoundsToAspectRatio(destinationBounds, aspectRatio, useCurrentMinEdgeSize,
-                    useCurrentSize);
+                    isReentryBounds);
         }
         mAspectRatio = aspectRatio;
         return destinationBounds;
@@ -533,9 +503,6 @@
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
-        pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
-        pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction);
-        pw.println(innerPrefix + "mReentrySize=" + mReentrySize);
         pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
         pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio);
         pw.println(innerPrefix + "mMinAspectRatio=" + mMinAspectRatio);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 10e5c3d..2625f16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -17,9 +17,15 @@
 package com.android.wm.shell.pip;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.graphics.Rect;
+import android.util.Size;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 
 /**
  * Singleton source of truth for the current state of PIP bounds.
@@ -28,6 +34,8 @@
     private static final String TAG = PipBoundsState.class.getSimpleName();
 
     private final @NonNull Rect mBounds = new Rect();
+    private PipReentryState mPipReentryState;
+    private ComponentName mLastPipComponentName;
 
     void setBounds(@NonNull Rect bounds) {
         mBounds.set(bounds);
@@ -39,11 +47,83 @@
     }
 
     /**
+     * Save the reentry state to restore to when re-entering PIP mode.
+     *
+     * TODO(b/169373982): consider refactoring this so that this class alone can use mBounds and
+     * calculate the snap fraction to save for re-entry.
+     */
+    public void saveReentryState(@NonNull Rect bounds, float fraction) {
+        mPipReentryState = new PipReentryState(new Size(bounds.width(), bounds.height()), fraction);
+    }
+
+    /**
+     * Returns the saved reentry state.
+     */
+    @Nullable
+    public PipReentryState getReentryState() {
+        return mPipReentryState;
+    }
+
+    /**
+     * Set the last {@link ComponentName} to enter PIP mode.
+     */
+    public void setLastPipComponentName(ComponentName lastPipComponentName) {
+        final boolean changed = !Objects.equals(mLastPipComponentName, lastPipComponentName);
+        mLastPipComponentName = lastPipComponentName;
+        if (changed) {
+            clearReentryState();
+        }
+    }
+
+    public ComponentName getLastPipComponentName() {
+        return mLastPipComponentName;
+    }
+
+    @VisibleForTesting
+    void clearReentryState() {
+        mPipReentryState = null;
+    }
+
+    static final class PipReentryState {
+        private static final String TAG = PipReentryState.class.getSimpleName();
+
+        private final @NonNull Size mSize;
+        private final float mSnapFraction;
+
+        PipReentryState(@NonNull Size size, float snapFraction) {
+            mSize = size;
+            mSnapFraction = snapFraction;
+        }
+
+        @NonNull
+        Size getSize() {
+            return mSize;
+        }
+
+        float getSnapFraction() {
+            return mSnapFraction;
+        }
+
+        void dump(PrintWriter pw, String prefix) {
+            final String innerPrefix = prefix + "  ";
+            pw.println(prefix + TAG);
+            pw.println(innerPrefix + "mSize=" + mSize);
+            pw.println(innerPrefix + "mSnapFraction=" + mSnapFraction);
+        }
+    }
+
+    /**
      * Dumps internal state.
      */
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
         pw.println(innerPrefix + "mBounds=" + mBounds);
+        pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
+        if (mPipReentryState == null) {
+            pw.println(innerPrefix + "mPipReentryState=null");
+        } else {
+            mPipReentryState.dump(pw, innerPrefix);
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 15fd424..22dc084 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -329,7 +329,8 @@
             PictureInPictureParams pictureInPictureParams) {
         mShouldIgnoreEnteringPipTransition = true;
         mState = State.ENTERING_PIP;
-        return mPipBoundsHandler.getDestinationBounds(componentName,
+        mPipBoundsState.setLastPipComponentName(componentName);
+        return mPipBoundsHandler.getDestinationBounds(
                 getAspectRatioOrDefault(pictureInPictureParams),
                 null /* bounds */, getMinimalSize(activityInfo));
     }
@@ -465,6 +466,7 @@
         mLeash = leash;
         mInitialState.put(mToken.asBinder(), new Configuration(mTaskInfo.configuration));
         mPictureInPictureParams = mTaskInfo.pictureInPictureParams;
+        mPipBoundsState.setLastPipComponentName(mTaskInfo.topActivity);
 
         mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
         mPipUiEventLoggerLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
@@ -491,7 +493,7 @@
         }
 
         final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams),
+                getAspectRatioOrDefault(mPictureInPictureParams),
                 null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
@@ -686,13 +688,14 @@
     @Override
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
         Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
+        mPipBoundsState.setLastPipComponentName(info.topActivity);
         final PictureInPictureParams newParams = info.pictureInPictureParams;
         if (newParams == null || !applyPictureInPictureParams(newParams)) {
             Log.d(TAG, "Ignored onTaskInfoChanged with PiP param: " + newParams);
             return;
         }
         final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                info.topActivity, getAspectRatioOrDefault(newParams),
+                getAspectRatioOrDefault(newParams),
                 mPipBoundsState.getBounds(), getMinimalSize(info.topActivityInfo),
                 true /* userCurrentMinEdgeSize */);
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
@@ -709,7 +712,7 @@
     public void onFixedRotationFinished(int displayId) {
         if (mShouldDeferEnteringPip && mState.isInPip()) {
             final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                    mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams),
+                    getAspectRatioOrDefault(mPictureInPictureParams),
                     null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
             // schedule a regular animation to ensure all the callbacks are still being sent
             enterPipWithAlphaAnimation(destinationBounds, 0 /* durationMs */);
@@ -783,7 +786,7 @@
         }
 
         final Rect newDestinationBounds = mPipBoundsHandler.getDestinationBounds(
-                mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams),
+                getAspectRatioOrDefault(mPictureInPictureParams),
                 null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
         if (newDestinationBounds.equals(currentDestinationBounds)) return;
         if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 13f5ac3..a3d21f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -173,7 +173,13 @@
 
         @Override
         public void onActivityHidden(ComponentName componentName) {
-            mHandler.post(() -> mPipBoundsHandler.onResetReentryBounds(componentName));
+            mHandler.post(() -> {
+                if (componentName.equals(mPipBoundsState.getLastPipComponentName())) {
+                    // The activity was removed, we don't want to restore to the reentry state
+                    // saved for this component anymore.
+                    mPipBoundsState.setLastPipComponentName(null);
+                }
+            });
         }
 
         @Override
@@ -384,7 +390,8 @@
         if (isOutPipDirection(direction)) {
             // Exiting PIP, save the reentry bounds to restore to when re-entering.
             updateReentryBounds(pipBounds);
-            mPipBoundsHandler.onSaveReentryBounds(activity, mReentryBounds);
+            final float snapFraction = mPipBoundsHandler.getSnapFraction(mReentryBounds);
+            mPipBoundsState.saveReentryState(mReentryBounds, snapFraction);
         }
         // Disable touches while the animation is running
         mTouchHandler.setTouchEnabled(false);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java
index d9e3148..e0ac8e2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java
@@ -20,7 +20,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import android.content.ComponentName;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -58,15 +57,13 @@
 
     private PipBoundsHandler mPipBoundsHandler;
     private DisplayInfo mDefaultDisplayInfo;
-    private ComponentName mTestComponentName1;
-    private ComponentName mTestComponentName2;
+    private PipBoundsState mPipBoundsState;
 
     @Before
     public void setUp() throws Exception {
         initializeMockResources();
-        mPipBoundsHandler = new PipBoundsHandler(mContext);
-        mTestComponentName1 = new ComponentName(mContext, "component1");
-        mTestComponentName2 = new ComponentName(mContext, "component2");
+        mPipBoundsState = new PipBoundsState();
+        mPipBoundsHandler = new PipBoundsHandler(mContext, mPipBoundsState);
 
         mPipBoundsHandler.onDisplayInfoChanged(mDefaultDisplayInfo);
     }
@@ -126,8 +123,8 @@
                 (MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2
         };
         for (float aspectRatio : aspectRatios) {
-            final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                    mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+            final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(aspectRatio,
+                    EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
             final float actualAspectRatio =
                     destinationBounds.width() / (destinationBounds.height() * 1f);
             assertEquals("Destination bounds matches the given aspect ratio",
@@ -142,8 +139,8 @@
                 MAX_ASPECT_RATIO * 2
         };
         for (float aspectRatio : invalidAspectRatios) {
-            final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                    mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+            final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(aspectRatio,
+                    EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
             final float actualAspectRatio =
                     destinationBounds.width() / (destinationBounds.height() * 1f);
             assertEquals("Destination bounds fallbacks to default aspect ratio",
@@ -158,8 +155,8 @@
         final Rect currentBounds = new Rect(0, 0, 0, 100);
         currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left;
 
-        final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                mTestComponentName1, aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE);
+        final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(aspectRatio,
+                currentBounds, EMPTY_MINIMAL_SIZE);
 
         final float actualAspectRatio =
                 destinationBounds.width() / (destinationBounds.height() * 1f);
@@ -182,8 +179,8 @@
         for (int i = 0; i < aspectRatios.length; i++) {
             final float aspectRatio = aspectRatios[i];
             final Size minimalSize = minimalSizes[i];
-            final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                    mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize);
+            final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(aspectRatio,
+                    EMPTY_CURRENT_BOUNDS, minimalSize);
             assertTrue("Destination bounds is no smaller than minimal requirement",
                     (destinationBounds.width() == minimalSize.getWidth()
                             && destinationBounds.height() >= minimalSize.getHeight())
@@ -203,8 +200,8 @@
         currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left;
         final Size minSize = new Size(currentBounds.width() / 2, currentBounds.height() / 2);
 
-        final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                mTestComponentName1, aspectRatio, currentBounds, minSize);
+        final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(aspectRatio,
+                currentBounds, minSize);
 
         assertTrue("Destination bounds ignores minimal size",
                 destinationBounds.width() > minSize.getWidth()
@@ -212,28 +209,44 @@
     }
 
     @Test
-    public void getDestinationBounds_withDifferentComponentName_ignoreLastPosition() {
-        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+    public void getDestinationBounds_reentryStateExists_restoreLastSize() {
+        final Rect reentryBounds = mPipBoundsHandler.getDestinationBounds(DEFAULT_ASPECT_RATIO,
+                EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+        reentryBounds.scale(1.25f);
+        final float reentrySnapFraction = mPipBoundsHandler.getSnapFraction(reentryBounds);
 
-        oldPosition.offset(0, -100);
-        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition);
+        mPipBoundsState.saveReentryState(reentryBounds, reentrySnapFraction);
+        final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(DEFAULT_ASPECT_RATIO,
+                EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
-        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName2,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+        assertEquals(reentryBounds.width(), destinationBounds.width());
+        assertEquals(reentryBounds.height(), destinationBounds.height());
+    }
 
-        assertNonBoundsInclusionWithMargin("ignore saved bounds", oldPosition, newPosition);
+    @Test
+    public void getDestinationBounds_reentryStateExists_restoreLastPosition() {
+        final Rect reentryBounds = mPipBoundsHandler.getDestinationBounds(DEFAULT_ASPECT_RATIO,
+                EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+        reentryBounds.offset(0, -100);
+        final float reentrySnapFraction = mPipBoundsHandler.getSnapFraction(reentryBounds);
+
+        mPipBoundsState.saveReentryState(reentryBounds, reentrySnapFraction);
+
+        final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(DEFAULT_ASPECT_RATIO,
+                EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+
+        assertBoundsInclusionWithMargin("restoreLastPosition", reentryBounds, destinationBounds);
     }
 
     @Test
     public void setShelfHeight_offsetBounds() {
         final int shelfHeight = 100;
-        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(DEFAULT_ASPECT_RATIO,
+                EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         mPipBoundsHandler.setShelfHeight(true, shelfHeight);
-        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(DEFAULT_ASPECT_RATIO,
+                EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         oldPosition.offset(0, -shelfHeight);
         assertBoundsInclusionWithMargin("offsetBounds by shelf", oldPosition, newPosition);
@@ -242,92 +255,30 @@
     @Test
     public void onImeVisibilityChanged_offsetBounds() {
         final int imeHeight = 100;
-        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(DEFAULT_ASPECT_RATIO,
+                EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         mPipBoundsHandler.onImeVisibilityChanged(true, imeHeight);
-        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(DEFAULT_ASPECT_RATIO,
+                EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         oldPosition.offset(0, -imeHeight);
         assertBoundsInclusionWithMargin("offsetBounds by IME", oldPosition, newPosition);
     }
 
     @Test
-    public void onSaveReentryBounds_restoreLastPosition() {
-        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+    public void getDestinationBounds_noReentryState_useDefaultBounds() {
+        final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(DEFAULT_ASPECT_RATIO,
+                EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
-        oldPosition.offset(0, -100);
-        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition);
+        mPipBoundsState.clearReentryState();
 
-        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
-
-        assertBoundsInclusionWithMargin("restoreLastPosition", oldPosition, newPosition);
-    }
-
-    @Test
-    public void onSaveReentryBounds_restoreLastSize() {
-        final Rect oldSize = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
-
-        oldSize.scale(1.25f);
-        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldSize);
-
-        final Rect newSize = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
-
-        assertEquals(oldSize.width(), newSize.width());
-        assertEquals(oldSize.height(), newSize.height());
-    }
-
-    @Test
-    public void onResetReentryBounds_useDefaultBounds() {
-        final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
-        final Rect newBounds = new Rect(defaultBounds);
-        newBounds.offset(0, -100);
-        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds);
-
-        mPipBoundsHandler.onResetReentryBounds(mTestComponentName1);
-        final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+        final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(DEFAULT_ASPECT_RATIO,
+                EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         assertBoundsInclusionWithMargin("useDefaultBounds", defaultBounds, actualBounds);
     }
 
-    @Test
-    public void onResetReentryBounds_componentMismatch_restoreLastPosition() {
-        final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
-        final Rect newBounds = new Rect(defaultBounds);
-        newBounds.offset(0, -100);
-        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds);
-
-        mPipBoundsHandler.onResetReentryBounds(mTestComponentName2);
-        final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
-
-        assertBoundsInclusionWithMargin("restoreLastPosition", newBounds, actualBounds);
-    }
-
-    @Test
-    public void onSaveReentryBounds_componentMismatch_restoreLastSize() {
-        final Rect oldSize = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
-
-        oldSize.scale(1.25f);
-        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldSize);
-
-        mPipBoundsHandler.onResetReentryBounds(mTestComponentName2);
-        final Rect newSize = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
-
-        assertEquals(oldSize.width(), newSize.width());
-        assertEquals(oldSize.height(), newSize.height());
-    }
-
     private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
         final Rect expectedWithMargin = new Rect(expected);
         expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
new file mode 100644
index 0000000..dc9399e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.wm.shell.pip;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Size;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link PipBoundsState}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class PipBoundsStateTest extends PipTestCase {
+
+    private static final Rect DEFAULT_BOUNDS = new Rect(0, 0, 10, 10);
+    private static final float DEFAULT_SNAP_FRACTION = 1.0f;
+
+    private PipBoundsState mPipBoundsState;
+    private ComponentName mTestComponentName1;
+    private ComponentName mTestComponentName2;
+
+    @Before
+    public void setUp() {
+        mPipBoundsState = new PipBoundsState();
+        mTestComponentName1 = new ComponentName(mContext, "component1");
+        mTestComponentName2 = new ComponentName(mContext, "component2");
+    }
+
+    @Test
+    public void testSetBounds() {
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        mPipBoundsState.setBounds(bounds);
+
+        assertEquals(bounds, mPipBoundsState.getBounds());
+    }
+
+    @Test
+    public void testSetReentryState() {
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        final float snapFraction = 0.5f;
+
+        mPipBoundsState.saveReentryState(bounds, snapFraction);
+
+        final PipBoundsState.PipReentryState state = mPipBoundsState.getReentryState();
+        assertEquals(new Size(100, 100), state.getSize());
+        assertEquals(snapFraction, state.getSnapFraction(), 0.01);
+    }
+
+    @Test
+    public void testClearReentryState() {
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        final float snapFraction = 0.5f;
+
+        mPipBoundsState.saveReentryState(bounds, snapFraction);
+        mPipBoundsState.clearReentryState();
+
+        assertNull(mPipBoundsState.getReentryState());
+    }
+
+    @Test
+    public void testSetLastPipComponentName_notChanged_doesNotClearReentryState() {
+        mPipBoundsState.setLastPipComponentName(mTestComponentName1);
+        mPipBoundsState.saveReentryState(DEFAULT_BOUNDS, DEFAULT_SNAP_FRACTION);
+
+        mPipBoundsState.setLastPipComponentName(mTestComponentName1);
+
+        final PipBoundsState.PipReentryState state = mPipBoundsState.getReentryState();
+        assertNotNull(state);
+        assertEquals(new Size(DEFAULT_BOUNDS.width(), DEFAULT_BOUNDS.height()), state.getSize());
+        assertEquals(DEFAULT_SNAP_FRACTION, state.getSnapFraction(), 0.01);
+    }
+
+    @Test
+    public void testSetLastPipComponentName_changed_clearReentryState() {
+        mPipBoundsState.setLastPipComponentName(mTestComponentName1);
+        mPipBoundsState.saveReentryState(DEFAULT_BOUNDS, DEFAULT_SNAP_FRACTION);
+
+        mPipBoundsState.setLastPipComponentName(mTestComponentName2);
+
+        assertNull(mPipBoundsState.getReentryState());
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
similarity index 88%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 46ebbf3..2f7faaf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.pip;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -27,12 +27,6 @@
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.pip.PipBoundsHandler;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTestCase;
-import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.splitscreen.SplitScreen;
 
 import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 4713142..3f60cc0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -93,7 +93,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mPipBoundsState = new PipBoundsState();
-        mPipBoundsHandler = new PipBoundsHandler(mContext);
+        mPipBoundsHandler = new PipBoundsHandler(mContext, mPipBoundsState);
         mPipSnapAlgorithm = mPipBoundsHandler.getSnapAlgorithm();
         mPipSnapAlgorithm = new PipSnapAlgorithm(mContext);
         mPipTouchHandler = new PipTouchHandler(mContext, mPipMenuActivityController,
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 8ab7da5..903ca2a 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -155,11 +155,12 @@
         android: {
             srcs: [
                 "tests/BackupData_test.cpp",
-		"tests/BackupHelpers_test.cpp",
+                "tests/BackupHelpers_test.cpp",
+                "tests/CursorWindow_test.cpp",
                 "tests/ObbFile_test.cpp",
                 "tests/PosixUtils_test.cpp",
             ],
-            shared_libs: common_test_libs + ["libui"],
+            shared_libs: common_test_libs + ["libbinder", "liblog", "libui"],
         },
         host: {
             static_libs: common_test_libs + ["liblog", "libz"],
@@ -185,9 +186,28 @@
         // Actual benchmarks.
         "tests/AssetManager2_bench.cpp",
         "tests/AttributeResolution_bench.cpp",
+        "tests/CursorWindow_bench.cpp",
         "tests/SparseEntry_bench.cpp",
         "tests/Theme_bench.cpp",
     ],
     shared_libs: common_test_libs,
     data: ["tests/data/**/*.apk"],
 }
+
+cc_library {
+    name: "libandroidfw_fuzzer_lib",
+    defaults: ["libandroidfw_defaults"],
+    host_supported: true,
+    srcs: [
+        "CursorWindow.cpp",
+    ],
+    export_include_dirs: ["include"],
+    target: {
+        android: {
+            shared_libs: common_test_libs + ["libbinder", "liblog"],
+        },
+        host: {
+            static_libs: common_test_libs + ["libbinder", "liblog"],
+        },
+    },
+}
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 71c8e1f..915c0d7 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -14,19 +14,14 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
 #define LOG_TAG "CursorWindow"
 
 #include <androidfw/CursorWindow.h>
-#include <binder/Parcel.h>
-#include <utils/Log.h>
 
-#include <cutils/ashmem.h>
 #include <sys/mman.h>
 
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
+#include "android-base/logging.h"
+#include "cutils/ashmem.h"
 
 namespace android {
 
@@ -36,11 +31,10 @@
  */
 static constexpr const size_t kInlineSize = 16384;
 
-CursorWindow::CursorWindow(const String8& name, int ashmemFd, void* data, size_t size,
-                           size_t inflatedSize, bool readOnly) :
-        mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size),
-        mInflatedSize(inflatedSize), mReadOnly(readOnly) {
-    mHeader = static_cast<Header*>(mData);
+static constexpr const size_t kSlotShift = 4;
+static constexpr const size_t kSlotSizeBytes = 1 << kSlotShift;
+
+CursorWindow::CursorWindow() {
 }
 
 CursorWindow::~CursorWindow() {
@@ -52,234 +46,243 @@
     }
 }
 
-status_t CursorWindow::create(const String8& name, size_t inflatedSize,
-                              CursorWindow** outCursorWindow) {
-    *outCursorWindow = nullptr;
+status_t CursorWindow::create(const String8 &name, size_t inflatedSize, CursorWindow **outWindow) {
+    *outWindow = nullptr;
 
-    size_t size = std::min(kInlineSize, inflatedSize);
-    void* data = calloc(size, 1);
-    if (!data) return NO_MEMORY;
+    CursorWindow* window = new CursorWindow();
+    if (!window) goto fail;
 
-    CursorWindow* window = new CursorWindow(name, -1, data, size,
-                                            inflatedSize, false /*readOnly*/);
-    status_t result = window->clear();
-    if (!result) {
-        LOG_WINDOW("Created new CursorWindow: freeOffset=%d, "
-                "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
-                window->mHeader->freeOffset,
-                window->mHeader->numRows,
-                window->mHeader->numColumns,
-                window->mSize, window->mData);
-        *outCursorWindow = window;
-        return OK;
-    }
+    window->mName = name;
+    window->mSize = std::min(kInlineSize, inflatedSize);
+    window->mInflatedSize = inflatedSize;
+    window->mData = malloc(window->mSize);
+    if (!window->mData) goto fail;
+    window->mReadOnly = false;
+
+    window->clear();
+    window->updateSlotsData();
+
+    LOG(DEBUG) << "Created: " << window->toString();
+    *outWindow = window;
+    return OK;
+
+fail:
+    LOG(ERROR) << "Failed create";
+fail_silent:
     delete window;
-    return result;
+    return UNKNOWN_ERROR;
 }
 
-status_t CursorWindow::inflate() {
-    // Shortcut when we can't expand any further
-    if (mSize == mInflatedSize) return INVALID_OPERATION;
+status_t CursorWindow::maybeInflate() {
+    int ashmemFd = 0;
+    void* newData = nullptr;
+
+    // Bail early when we can't expand any further
+    if (mReadOnly || mSize == mInflatedSize) {
+        return INVALID_OPERATION;
+    }
 
     String8 ashmemName("CursorWindow: ");
     ashmemName.append(mName);
 
-    status_t result;
-    int ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize);
+    ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize);
     if (ashmemFd < 0) {
-        result = -errno;
-        ALOGE("CursorWindow: ashmem_create_region() failed: errno=%d.", errno);
-    } else {
-        result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE);
-        if (result < 0) {
-            ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d",errno);
-        } else {
-            void* data = ::mmap(NULL, mInflatedSize, PROT_READ | PROT_WRITE,
-                                MAP_SHARED, ashmemFd, 0);
-            if (data == MAP_FAILED) {
-                result = -errno;
-                ALOGE("CursorWindow: mmap() failed: errno=%d.", errno);
-            } else {
-                result = ashmem_set_prot_region(ashmemFd, PROT_READ);
-                if (result < 0) {
-                    ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d.", errno);
-                } else {
-                    // Move inline contents into new ashmem region
-                    memcpy(data, mData, mSize);
-                    free(mData);
-                    mAshmemFd = ashmemFd;
-                    mData = data;
-                    mHeader = static_cast<Header*>(mData);
-                    mSize = mInflatedSize;
-                    LOG_WINDOW("Inflated CursorWindow: freeOffset=%d, "
-                            "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
-                            mHeader->freeOffset,
-                            mHeader->numRows,
-                            mHeader->numColumns,
-                            mSize, mData);
-                    return OK;
-                }
-            }
-            ::munmap(data, mInflatedSize);
-        }
-        ::close(ashmemFd);
+        PLOG(ERROR) << "Failed ashmem_create_region";
+        goto fail_silent;
     }
-    return result;
+
+    if (ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE) < 0) {
+        PLOG(ERROR) << "Failed ashmem_set_prot_region";
+        goto fail_silent;
+    }
+
+    newData = ::mmap(nullptr, mInflatedSize, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
+    if (newData == MAP_FAILED) {
+        PLOG(ERROR) << "Failed mmap";
+        goto fail_silent;
+    }
+
+    if (ashmem_set_prot_region(ashmemFd, PROT_READ) < 0) {
+        PLOG(ERROR) << "Failed ashmem_set_prot_region";
+        goto fail_silent;
+    }
+
+    {
+        // Migrate existing contents into new ashmem region
+        uint32_t slotsSize = mSize - mSlotsOffset;
+        uint32_t newSlotsOffset = mInflatedSize - slotsSize;
+        memcpy(static_cast<uint8_t*>(newData),
+                static_cast<uint8_t*>(mData), mAllocOffset);
+        memcpy(static_cast<uint8_t*>(newData) + newSlotsOffset,
+                static_cast<uint8_t*>(mData) + mSlotsOffset, slotsSize);
+
+        free(mData);
+        mAshmemFd = ashmemFd;
+        mData = newData;
+        mSize = mInflatedSize;
+        mSlotsOffset = newSlotsOffset;
+
+        updateSlotsData();
+    }
+
+    LOG(DEBUG) << "Inflated: " << this->toString();
+    return OK;
+
+fail:
+    LOG(ERROR) << "Failed maybeInflate";
+fail_silent:
+    ::munmap(newData, mInflatedSize);
+    ::close(ashmemFd);
+    return UNKNOWN_ERROR;
 }
 
-status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow) {
-    *outCursorWindow = nullptr;
+status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outWindow) {
+    *outWindow = nullptr;
 
-    String8 name;
-    status_t result = parcel->readString8(&name);
-    if (result) return result;
+    CursorWindow* window = new CursorWindow();
+    if (!window) goto fail;
+
+    if (parcel->readString8(&window->mName)) goto fail;
+    if (parcel->readUint32(&window->mNumRows)) goto fail;
+    if (parcel->readUint32(&window->mNumColumns)) goto fail;
+    if (parcel->readUint32(&window->mSize)) goto fail;
+
+    if ((window->mNumRows * window->mNumColumns * kSlotSizeBytes) > window->mSize) {
+        LOG(ERROR) << "Unexpected size " << window->mSize << " for " << window->mNumRows
+                << " rows and " << window->mNumColumns << " columns";
+        goto fail_silent;
+    }
 
     bool isAshmem;
-    result = parcel->readBool(&isAshmem);
-    if (result) return result;
-
+    if (parcel->readBool(&isAshmem)) goto fail;
     if (isAshmem) {
-        return createFromParcelAshmem(parcel, name, outCursorWindow);
-    } else {
-        return createFromParcelInline(parcel, name, outCursorWindow);
-    }
-}
-
-status_t CursorWindow::createFromParcelAshmem(Parcel* parcel, String8& name,
-                                              CursorWindow** outCursorWindow) {
-    status_t result;
-    int actualSize;
-    int ashmemFd = parcel->readFileDescriptor();
-    if (ashmemFd == int(BAD_TYPE)) {
-        result = BAD_TYPE;
-        ALOGE("CursorWindow: readFileDescriptor() failed");
-    } else {
-        ssize_t size = ashmem_get_size_region(ashmemFd);
-        if (size < 0) {
-            result = UNKNOWN_ERROR;
-            ALOGE("CursorWindow: ashmem_get_size_region() failed: errno=%d.", errno);
-        } else {
-            int dupAshmemFd = ::fcntl(ashmemFd, F_DUPFD_CLOEXEC, 0);
-            if (dupAshmemFd < 0) {
-                result = -errno;
-                ALOGE("CursorWindow: fcntl() failed: errno=%d.", errno);
-            } else {
-                // the size of the ashmem descriptor can be modified between ashmem_get_size_region
-                // call and mmap, so we'll check again immediately after memory is mapped
-                void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0);
-                if (data == MAP_FAILED) {
-                    result = -errno;
-                    ALOGE("CursorWindow: mmap() failed: errno=%d.", errno);
-                } else if ((actualSize = ashmem_get_size_region(dupAshmemFd)) != size) {
-                    ::munmap(data, size);
-                    result = BAD_VALUE;
-                    ALOGE("CursorWindow: ashmem_get_size_region() returned %d, expected %d"
-                            " errno=%d",
-                            actualSize, (int) size, errno);
-                } else {
-                    CursorWindow* window = new CursorWindow(name, dupAshmemFd,
-                            data, size, size, true /*readOnly*/);
-                    LOG_WINDOW("Created CursorWindow from ashmem parcel: freeOffset=%d, "
-                            "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
-                            window->mHeader->freeOffset,
-                            window->mHeader->numRows,
-                            window->mHeader->numColumns,
-                            window->mSize, window->mData);
-                    *outCursorWindow = window;
-                    return OK;
-                }
-                ::close(dupAshmemFd);
-            }
+        window->mAshmemFd = parcel->readFileDescriptor();
+        if (window->mAshmemFd < 0) {
+            LOG(ERROR) << "Failed readFileDescriptor";
+            goto fail_silent;
         }
+
+        window->mAshmemFd = ::fcntl(window->mAshmemFd, F_DUPFD_CLOEXEC, 0);
+        if (window->mAshmemFd < 0) {
+            PLOG(ERROR) << "Failed F_DUPFD_CLOEXEC";
+            goto fail_silent;
+        }
+
+        window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, window->mAshmemFd, 0);
+        if (window->mData == MAP_FAILED) {
+            PLOG(ERROR) << "Failed mmap";
+            goto fail_silent;
+        }
+    } else {
+        window->mAshmemFd = -1;
+
+        if (window->mSize > kInlineSize) {
+            LOG(ERROR) << "Unexpected size " << window->mSize << " for inline window";
+            goto fail_silent;
+        }
+
+        window->mData = malloc(window->mSize);
+        if (!window->mData) goto fail;
+
+        if (parcel->read(window->mData, window->mSize)) goto fail;
     }
-    *outCursorWindow = NULL;
-    return result;
-}
 
-status_t CursorWindow::createFromParcelInline(Parcel* parcel, String8& name,
-                                              CursorWindow** outCursorWindow) {
-    uint32_t sentSize;
-    status_t result = parcel->readUint32(&sentSize);
-    if (result) return result;
-    if (sentSize > kInlineSize) return NO_MEMORY;
+    // We just came from a remote source, so we're read-only
+    // and we can't inflate ourselves
+    window->mInflatedSize = window->mSize;
+    window->mReadOnly = true;
 
-    void* data = calloc(sentSize, 1);
-    if (!data) return NO_MEMORY;
+    window->updateSlotsData();
 
-    result = parcel->read(data, sentSize);
-    if (result) return result;
-
-    CursorWindow* window = new CursorWindow(name, -1, data, sentSize,
-                                            sentSize, true /*readOnly*/);
-    LOG_WINDOW("Created CursorWindow from inline parcel: freeOffset=%d, "
-            "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
-            window->mHeader->freeOffset,
-            window->mHeader->numRows,
-            window->mHeader->numColumns,
-            window->mSize, window->mData);
-    *outCursorWindow = window;
+    LOG(DEBUG) << "Created from parcel: " << window->toString();
+    *outWindow = window;
     return OK;
+
+fail:
+    LOG(ERROR) << "Failed createFromParcel";
+fail_silent:
+    delete window;
+    return UNKNOWN_ERROR;
 }
 
 status_t CursorWindow::writeToParcel(Parcel* parcel) {
-        LOG_WINDOW("Writing CursorWindow: freeOffset=%d, "
-                "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
-                mHeader->freeOffset,
-                mHeader->numRows,
-                mHeader->numColumns,
-                mSize, mData);
+    LOG(DEBUG) << "Writing to parcel: " << this->toString();
 
-    status_t result = parcel->writeString8(mName);
-    if (result) return result;
-
+    if (parcel->writeString8(mName)) goto fail;
+    if (parcel->writeUint32(mNumRows)) goto fail;
+    if (parcel->writeUint32(mNumColumns)) goto fail;
     if (mAshmemFd != -1) {
-        result = parcel->writeBool(true);
-        if (result) return result;
-        return writeToParcelAshmem(parcel);
+        if (parcel->writeUint32(mSize)) goto fail;
+        if (parcel->writeBool(true)) goto fail;
+        if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail;
     } else {
-        result = parcel->writeBool(false);
-        if (result) return result;
-        return writeToParcelInline(parcel);
+        // Since we know we're going to be read-only on the remote side,
+        // we can compact ourselves on the wire, with just enough padding
+        // to ensure our slots stay aligned
+        size_t slotsSize = mSize - mSlotsOffset;
+        size_t compactedSize = mAllocOffset + slotsSize;
+        compactedSize = (compactedSize + 3) & ~3;
+        if (parcel->writeUint32(compactedSize)) goto fail;
+        if (parcel->writeBool(false)) goto fail;
+        void* dest = parcel->writeInplace(compactedSize);
+        if (!dest) goto fail;
+        memcpy(static_cast<uint8_t*>(dest),
+                static_cast<uint8_t*>(mData), mAllocOffset);
+        memcpy(static_cast<uint8_t*>(dest) + compactedSize - slotsSize,
+                static_cast<uint8_t*>(mData) + mSlotsOffset, slotsSize);
     }
-}
+    return OK;
 
-status_t CursorWindow::writeToParcelAshmem(Parcel* parcel) {
-    return parcel->writeDupFileDescriptor(mAshmemFd);
-}
-
-status_t CursorWindow::writeToParcelInline(Parcel* parcel) {
-    status_t result = parcel->writeUint32(mHeader->freeOffset);
-    if (result) return result;
-
-    return parcel->write(mData, mHeader->freeOffset);
+fail:
+    LOG(ERROR) << "Failed writeToParcel";
+fail_silent:
+    return UNKNOWN_ERROR;
 }
 
 status_t CursorWindow::clear() {
     if (mReadOnly) {
         return INVALID_OPERATION;
     }
-
-    mHeader->freeOffset = sizeof(Header) + sizeof(RowSlotChunk);
-    mHeader->firstChunkOffset = sizeof(Header);
-    mHeader->numRows = 0;
-    mHeader->numColumns = 0;
-
-    RowSlotChunk* firstChunk = static_cast<RowSlotChunk*>(offsetToPtr(mHeader->firstChunkOffset));
-    firstChunk->nextChunkOffset = 0;
+    mAllocOffset = 0;
+    mSlotsOffset = mSize;
+    mNumRows = 0;
+    mNumColumns = 0;
     return OK;
 }
 
+void CursorWindow::updateSlotsData() {
+    mSlotsStart = static_cast<uint8_t*>(mData) + mSize - kSlotSizeBytes;
+    mSlotsEnd = static_cast<uint8_t*>(mData) + mSlotsOffset;
+}
+
+void* CursorWindow::offsetToPtr(uint32_t offset, uint32_t bufferSize = 0) {
+    if (offset > mSize) {
+        LOG(ERROR) << "Offset " << offset
+                << " out of bounds, max value " << mSize;
+        return nullptr;
+    }
+    if (offset + bufferSize > mSize) {
+        LOG(ERROR) << "End offset " << (offset + bufferSize)
+                << " out of bounds, max value " << mSize;
+        return nullptr;
+    }
+    return static_cast<uint8_t*>(mData) + offset;
+}
+
+uint32_t CursorWindow::offsetFromPtr(void* ptr) {
+    return static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(mData);
+}
+
 status_t CursorWindow::setNumColumns(uint32_t numColumns) {
     if (mReadOnly) {
         return INVALID_OPERATION;
     }
-
-    uint32_t cur = mHeader->numColumns;
-    if ((cur > 0 || mHeader->numRows > 0) && cur != numColumns) {
-        ALOGE("Trying to go from %d columns to %d", cur, numColumns);
+    uint32_t cur = mNumColumns;
+    if ((cur > 0 || mNumRows > 0) && cur != numColumns) {
+        LOG(ERROR) << "Trying to go from " << cur << " columns to " << numColumns;
         return INVALID_OPERATION;
     }
-    mHeader->numColumns = numColumns;
+    mNumColumns = numColumns;
     return OK;
 }
 
@@ -287,30 +290,19 @@
     if (mReadOnly) {
         return INVALID_OPERATION;
     }
-
-    // Fill in the row slot
-    RowSlot* rowSlot = allocRowSlot();
-    if (rowSlot == NULL) {
-        return NO_MEMORY;
+    size_t size = mNumColumns * kSlotSizeBytes;
+    off_t newOffset = mSlotsOffset - size;
+    if (newOffset < mAllocOffset) {
+        maybeInflate();
+        newOffset = mSlotsOffset - size;
+        if (newOffset < mAllocOffset) {
+            return NO_MEMORY;
+        }
     }
-    uint32_t rowSlotOffset = offsetFromPtr(rowSlot);
-
-    // Allocate the slots for the field directory
-    size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot);
-    uint32_t fieldDirOffset = alloc(fieldDirSize, true /*aligned*/);
-    if (!fieldDirOffset) {
-        mHeader->numRows--;
-        LOG_WINDOW("The row failed, so back out the new row accounting "
-                "from allocRowSlot %d", mHeader->numRows);
-        return NO_MEMORY;
-    }
-    FieldSlot* fieldDir = static_cast<FieldSlot*>(offsetToPtr(fieldDirOffset));
-    memset(fieldDir, 0, fieldDirSize);
-
-    LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %zu bytes at offset %u\n",
-            mHeader->numRows - 1, rowSlotOffset, fieldDirSize, fieldDirOffset);
-    rowSlot = static_cast<RowSlot*>(offsetToPtr(rowSlotOffset));
-    rowSlot->offset = fieldDirOffset;
+    memset(offsetToPtr(newOffset), 0, size);
+    mSlotsOffset = newOffset;
+    updateSlotsData();
+    mNumRows++;
     return OK;
 }
 
@@ -318,90 +310,48 @@
     if (mReadOnly) {
         return INVALID_OPERATION;
     }
-
-    if (mHeader->numRows > 0) {
-        mHeader->numRows--;
+    size_t size = mNumColumns * kSlotSizeBytes;
+    off_t newOffset = mSlotsOffset + size;
+    if (newOffset > mSize) {
+        return NO_MEMORY;
     }
+    mSlotsOffset = newOffset;
+    updateSlotsData();
+    mNumRows--;
     return OK;
 }
 
-uint32_t CursorWindow::alloc(size_t size, bool aligned) {
-    uint32_t padding;
-    if (aligned) {
-        // 4 byte alignment
-        padding = (~mHeader->freeOffset + 1) & 3;
-    } else {
-        padding = 0;
+status_t CursorWindow::alloc(size_t size, uint32_t* outOffset) {
+    if (mReadOnly) {
+        return INVALID_OPERATION;
     }
-
-    uint32_t offset = mHeader->freeOffset + padding;
-    uint32_t nextFreeOffset = offset + size;
-    if (nextFreeOffset > mSize) {
-        // Try inflating to ashmem before finally giving up
-        inflate();
-        if (nextFreeOffset > mSize) {
-            ALOGW("Window is full: requested allocation %zu bytes, "
-                    "free space %zu bytes, window size %zu bytes",
-                    size, freeSpace(), mSize);
-            return 0;
+    size_t alignedSize = (size + 3) & ~3;
+    off_t newOffset = mAllocOffset + alignedSize;
+    if (newOffset > mSlotsOffset) {
+        maybeInflate();
+        newOffset = mAllocOffset + alignedSize;
+        if (newOffset > mSlotsOffset) {
+            return NO_MEMORY;
         }
     }
-
-    mHeader->freeOffset = nextFreeOffset;
-    return offset;
-}
-
-CursorWindow::RowSlot* CursorWindow::getRowSlot(uint32_t row) {
-    uint32_t chunkPos = row;
-    RowSlotChunk* chunk = static_cast<RowSlotChunk*>(
-            offsetToPtr(mHeader->firstChunkOffset));
-    while (chunkPos >= ROW_SLOT_CHUNK_NUM_ROWS) {
-        chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
-        chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS;
-    }
-    return &chunk->slots[chunkPos];
-}
-
-CursorWindow::RowSlot* CursorWindow::allocRowSlot() {
-    uint32_t chunkPos = mHeader->numRows;
-    RowSlotChunk* chunk = static_cast<RowSlotChunk*>(
-            offsetToPtr(mHeader->firstChunkOffset));
-    while (chunkPos > ROW_SLOT_CHUNK_NUM_ROWS) {
-        chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
-        chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS;
-    }
-    if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) {
-        if (!chunk->nextChunkOffset) {
-            uint32_t chunkOffset = offsetFromPtr(chunk);
-            uint32_t newChunk = alloc(sizeof(RowSlotChunk), true /*aligned*/);
-            chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunkOffset));
-            chunk->nextChunkOffset = newChunk;
-            if (!chunk->nextChunkOffset) {
-                return NULL;
-            }
-        }
-        chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
-        chunk->nextChunkOffset = 0;
-        chunkPos = 0;
-    }
-    mHeader->numRows += 1;
-    return &chunk->slots[chunkPos];
+    *outOffset = mAllocOffset;
+    mAllocOffset = newOffset;
+    return OK;
 }
 
 CursorWindow::FieldSlot* CursorWindow::getFieldSlot(uint32_t row, uint32_t column) {
-    if (row >= mHeader->numRows || column >= mHeader->numColumns) {
-        ALOGE("Failed to read row %d, column %d from a CursorWindow which "
-                "has %d rows, %d columns.",
-                row, column, mHeader->numRows, mHeader->numColumns);
-        return NULL;
+    // This is carefully tuned to use as few cycles as
+    // possible, since this is an extremely hot code path;
+    // see CursorWindow_bench.cpp for more details
+    void *result = static_cast<uint8_t*>(mSlotsStart)
+            - (((row * mNumColumns) + column) << kSlotShift);
+    if (result < mSlotsEnd || column >= mNumColumns) {
+        LOG(ERROR) << "Failed to read row " << row << ", column " << column
+                << " from a window with " << mNumRows << " rows, " << mNumColumns << " columns";
+        return nullptr;
+    } else {
+        return static_cast<FieldSlot*>(result);
     }
-    RowSlot* rowSlot = getRowSlot(row);
-    if (!rowSlot) {
-        ALOGE("Failed to find rowSlot for row %d.", row);
-        return NULL;
-    }
-    FieldSlot* fieldDir = static_cast<FieldSlot*>(offsetToPtr(rowSlot->offset));
-    return &fieldDir[column];
 }
 
 status_t CursorWindow::putBlob(uint32_t row, uint32_t column, const void* value, size_t size) {
@@ -423,16 +373,15 @@
     if (!fieldSlot) {
         return BAD_VALUE;
     }
-    uint32_t fieldSlotOffset = offsetFromPtr(fieldSlot);
 
-    uint32_t offset = alloc(size);
-    if (!offset) {
+    uint32_t offset;
+    if (alloc(size, &offset)) {
         return NO_MEMORY;
     }
 
     memcpy(offsetToPtr(offset), value, size);
 
-    fieldSlot = static_cast<FieldSlot*>(offsetToPtr(fieldSlotOffset));
+    fieldSlot = getFieldSlot(row, column);
     fieldSlot->type = type;
     fieldSlot->data.buffer.offset = offset;
     fieldSlot->data.buffer.size = size;
diff --git a/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp b/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp
new file mode 100644
index 0000000..2dac47b
--- /dev/null
+++ b/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp
@@ -0,0 +1,31 @@
+cc_fuzz {
+    name: "cursorwindow_fuzzer",
+    srcs: [
+        "cursorwindow_fuzzer.cpp",
+    ],
+    host_supported: true,
+    corpus: ["corpus/*"],
+    static_libs: ["libgmock"],
+    target: {
+        android: {
+            shared_libs: [
+                "libandroidfw_fuzzer_lib",
+                "libbase",
+                "libbinder",
+                "libcutils",
+                "liblog",
+                "libutils",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libandroidfw_fuzzer_lib",
+                "libbase",
+                "libbinder",
+                "libcutils",
+                "liblog",
+                "libutils",
+            ],
+        },
+    },
+}
diff --git a/libs/androidfw/fuzz/cursorwindow_fuzzer/corpus/typical.bin b/libs/androidfw/fuzz/cursorwindow_fuzzer/corpus/typical.bin
new file mode 100644
index 0000000..c7e22dd
--- /dev/null
+++ b/libs/androidfw/fuzz/cursorwindow_fuzzer/corpus/typical.bin
Binary files differ
diff --git a/libs/androidfw/fuzz/cursorwindow_fuzzer/cursorwindow_fuzzer.cpp b/libs/androidfw/fuzz/cursorwindow_fuzzer/cursorwindow_fuzzer.cpp
new file mode 100644
index 0000000..8dce212
--- /dev/null
+++ b/libs/androidfw/fuzz/cursorwindow_fuzzer/cursorwindow_fuzzer.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <string>
+#include <memory>
+
+#include "android-base/logging.h"
+#include "androidfw/CursorWindow.h"
+#include "binder/Parcel.h"
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+using android::CursorWindow;
+using android::Parcel;
+
+extern "C" int LLVMFuzzerInitialize(int *, char ***) {
+    setenv("ANDROID_LOG_TAGS", "*:s", 1);
+    android::base::InitLogging(nullptr, &android::base::StderrLogger);
+    return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    Parcel p;
+    p.setData(data, size);
+
+    CursorWindow* w = nullptr;
+    if (!CursorWindow::createFromParcel(&p, &w)) {
+        LOG(WARNING) << "Valid cursor with " << w->getNumRows() << " rows, "
+                << w->getNumColumns() << " cols";
+
+        // Try obtaining heap allocations for most items; we trim the
+        // search space to speed things up
+        auto rows = std::min(w->getNumRows(), static_cast<uint32_t>(128));
+        auto cols = std::min(w->getNumColumns(), static_cast<uint32_t>(128));
+        for (auto row = 0; row < rows; row++) {
+            for (auto col = 0; col < cols; col++) {
+                auto field = w->getFieldSlot(row, col);
+                if (!field) continue;
+                switch (w->getFieldSlotType(field)) {
+                case CursorWindow::FIELD_TYPE_STRING: {
+                    size_t size;
+                    w->getFieldSlotValueString(field, &size);
+                    break;
+                }
+                case CursorWindow::FIELD_TYPE_BLOB: {
+                    size_t size;
+                    w->getFieldSlotValueBlob(field, &size);
+                    break;
+                }
+                }
+            }
+        }
+
+        // Finally, try obtaining the furthest valid field
+        if (rows > 0 && cols > 0) {
+            w->getFieldSlot(w->getNumRows() - 1, w->getNumColumns() - 1);
+        }
+    }
+    delete w;
+
+    return 0;
+}
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index 0bee609..6e55a9a 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -20,38 +20,36 @@
 #include <inttypes.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <string>
 
-#include <binder/Parcel.h>
-#include <log/log.h>
-#include <utils/String8.h>
+#include "android-base/stringprintf.h"
+#include "binder/Parcel.h"
+#include "utils/String8.h"
 
-#if LOG_NDEBUG
-
-#define IF_LOG_WINDOW() if (false)
 #define LOG_WINDOW(...)
 
-#else
-
-#define IF_LOG_WINDOW() IF_ALOG(LOG_DEBUG, "CursorWindow")
-#define LOG_WINDOW(...) ALOG(LOG_DEBUG, "CursorWindow", __VA_ARGS__)
-
-#endif
-
 namespace android {
 
 /**
- * This class stores a set of rows from a database in a buffer. The begining of the
- * window has first chunk of RowSlots, which are offsets to the row directory, followed by
- * an offset to the next chunk in a linked-list of additional chunk of RowSlots in case
- * the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a
- * FieldSlot per column, which has the size, offset, and type of the data for that field.
- * Note that the data types come from sqlite3.h.
+ * This class stores a set of rows from a database in a buffer. Internally
+ * data is structured as a "heap" of string/blob allocations at the bottom
+ * of the memory region, and a "stack" of FieldSlot allocations at the top
+ * of the memory region. Here's an example visual representation:
+ *
+ *   +----------------------------------------------------------------+
+ *   |heap\0of\0strings\0                                 222211110000| ...
+ *   +-------------------+--------------------------------+-------+---+
+ *    ^                  ^                                ^       ^   ^     ^
+ *    |                  |                                |       |   |     |
+ *    |                  +- mAllocOffset    mSlotsOffset -+       |   |     |
+ *    +- mData                                       mSlotsStart -+   |     |
+ *                                                             mSize -+     |
+ *                                                           mInflatedSize -+
  *
  * Strings are stored in UTF-8.
  */
 class CursorWindow {
-    CursorWindow(const String8& name, int ashmemFd, void* data, size_t size,
-                 size_t inflatedSize, bool readOnly);
+    CursorWindow();
 
 public:
     /* Field types. */
@@ -88,9 +86,9 @@
 
     inline String8 name() { return mName; }
     inline size_t size() { return mSize; }
-    inline size_t freeSpace() { return mSize - mHeader->freeOffset; }
-    inline uint32_t getNumRows() { return mHeader->numRows; }
-    inline uint32_t getNumColumns() { return mHeader->numColumns; }
+    inline size_t freeSpace() { return mSlotsOffset - mAllocOffset; }
+    inline uint32_t getNumRows() { return mNumRows; }
+    inline uint32_t getNumColumns() { return mNumColumns; }
 
     status_t clear();
     status_t setNumColumns(uint32_t numColumns);
@@ -138,75 +136,57 @@
         return offsetToPtr(fieldSlot->data.buffer.offset, fieldSlot->data.buffer.size);
     }
 
+    inline std::string toString() const {
+        return android::base::StringPrintf("CursorWindow{name=%s, fd=%d, size=%d, inflatedSize=%d, "
+                "allocOffset=%d, slotsOffset=%d, numRows=%d, numColumns=%d}", mName.c_str(),
+                mAshmemFd, mSize, mInflatedSize, mAllocOffset, mSlotsOffset, mNumRows, mNumColumns);
+    }
+
 private:
-    static const size_t ROW_SLOT_CHUNK_NUM_ROWS = 100;
-
-    struct Header {
-        // Offset of the lowest unused byte in the window.
-        uint32_t freeOffset;
-
-        // Offset of the first row slot chunk.
-        uint32_t firstChunkOffset;
-
-        uint32_t numRows;
-        uint32_t numColumns;
-    };
-
-    struct RowSlot {
-        uint32_t offset;
-    };
-
-    struct RowSlotChunk {
-        RowSlot slots[ROW_SLOT_CHUNK_NUM_ROWS];
-        uint32_t nextChunkOffset;
-    };
-
     String8 mName;
-    int mAshmemFd;
-    void* mData;
-    size_t mSize;
-    size_t mInflatedSize;
-    bool mReadOnly;
-    Header* mHeader;
+    int mAshmemFd = -1;
+    void* mData = nullptr;
+    /**
+     * Pointer to the first FieldSlot, used to optimize the extremely
+     * hot code path of getFieldSlot().
+     */
+    void* mSlotsStart = nullptr;
+    void* mSlotsEnd = nullptr;
+    uint32_t mSize = 0;
+    /**
+     * When a window starts as lightweight inline allocation, this value
+     * holds the "full" size to be created after ashmem inflation.
+     */
+    uint32_t mInflatedSize = 0;
+    /**
+     * Offset to the top of the "heap" of string/blob allocations. By
+     * storing these allocations at the bottom of our memory region we
+     * avoid having to rewrite offsets when inflating.
+     */
+    uint32_t mAllocOffset = 0;
+    /**
+     * Offset to the bottom of the "stack" of FieldSlot allocations.
+     */
+    uint32_t mSlotsOffset = 0;
+    uint32_t mNumRows = 0;
+    uint32_t mNumColumns = 0;
+    bool mReadOnly = false;
 
-    inline void* offsetToPtr(uint32_t offset, uint32_t bufferSize = 0) {
-        if (offset > mSize) {
-            ALOGE("Offset %" PRIu32 " out of bounds, max value %zu", offset, mSize);
-            return NULL;
-        }
-        if (offset + bufferSize > mSize) {
-            ALOGE("End offset %" PRIu32 " out of bounds, max value %zu",
-                    offset + bufferSize, mSize);
-            return NULL;
-        }
-        return static_cast<uint8_t*>(mData) + offset;
-    }
+    void updateSlotsData();
 
-    inline uint32_t offsetFromPtr(void* ptr) {
-        return static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(mData);
-    }
-
-    static status_t createFromParcelAshmem(Parcel*, String8&, CursorWindow**);
-    static status_t createFromParcelInline(Parcel*, String8&, CursorWindow**);
-
-    status_t writeToParcelAshmem(Parcel*);
-    status_t writeToParcelInline(Parcel*);
+    void* offsetToPtr(uint32_t offset, uint32_t bufferSize);
+    uint32_t offsetFromPtr(void* ptr);
 
     /**
      * By default windows are lightweight inline allocations; this method
      * inflates the window into a larger ashmem region.
      */
-    status_t inflate();
+    status_t maybeInflate();
 
     /**
-     * Allocate a portion of the window. Returns the offset
-     * of the allocation, or 0 if there isn't enough space.
-     * If aligned is true, the allocation gets 4 byte alignment.
+     * Allocate a portion of the window.
      */
-    uint32_t alloc(size_t size, bool aligned = false);
-
-    RowSlot* getRowSlot(uint32_t row);
-    RowSlot* allocRowSlot();
+    status_t alloc(size_t size, uint32_t* outOffset);
 
     status_t putBlobOrString(uint32_t row, uint32_t column,
             const void* value, size_t size, int32_t type);
diff --git a/libs/androidfw/tests/CursorWindow_bench.cpp b/libs/androidfw/tests/CursorWindow_bench.cpp
new file mode 100644
index 0000000..f1191c3
--- /dev/null
+++ b/libs/androidfw/tests/CursorWindow_bench.cpp
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#include "benchmark/benchmark.h"
+
+#include "androidfw/CursorWindow.h"
+
+namespace android {
+
+static void BM_CursorWindowWrite(benchmark::State& state, size_t rows, size_t cols) {
+    CursorWindow* w;
+    CursorWindow::create(String8("test"), 1 << 21, &w);
+
+    while (state.KeepRunning()) {
+        w->clear();
+        w->setNumColumns(cols);
+        for (int row = 0; row < rows; row++) {
+            w->allocRow();
+            for (int col = 0; col < cols; col++) {
+                w->putLong(row, col, 0xcafe);
+            }
+        }
+    }
+}
+
+static void BM_CursorWindowWrite4x4(benchmark::State& state) {
+    BM_CursorWindowWrite(state, 4, 4);
+}
+BENCHMARK(BM_CursorWindowWrite4x4);
+
+static void BM_CursorWindowWrite1Kx4(benchmark::State& state) {
+    BM_CursorWindowWrite(state, 1024, 4);
+}
+BENCHMARK(BM_CursorWindowWrite1Kx4);
+
+static void BM_CursorWindowWrite16Kx4(benchmark::State& state) {
+    BM_CursorWindowWrite(state, 16384, 4);
+}
+BENCHMARK(BM_CursorWindowWrite16Kx4);
+
+static void BM_CursorWindowRead(benchmark::State& state, size_t rows, size_t cols) {
+    CursorWindow* w;
+    CursorWindow::create(String8("test"), 1 << 21, &w);
+    w->setNumColumns(cols);
+    for (int row = 0; row < rows; row++) {
+        w->allocRow();
+    }
+
+    while (state.KeepRunning()) {
+        for (int row = 0; row < rows; row++) {
+            for (int col = 0; col < cols; col++) {
+                w->getFieldSlot(row, col);
+            }
+        }
+    }
+}
+
+static void BM_CursorWindowRead4x4(benchmark::State& state) {
+    BM_CursorWindowRead(state, 4, 4);
+}
+BENCHMARK(BM_CursorWindowRead4x4);
+
+static void BM_CursorWindowRead1Kx4(benchmark::State& state) {
+    BM_CursorWindowRead(state, 1024, 4);
+}
+BENCHMARK(BM_CursorWindowRead1Kx4);
+
+static void BM_CursorWindowRead16Kx4(benchmark::State& state) {
+    BM_CursorWindowRead(state, 16384, 4);
+}
+BENCHMARK(BM_CursorWindowRead16Kx4);
+
+}  // namespace android
diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp
new file mode 100644
index 0000000..dfcf76e
--- /dev/null
+++ b/libs/androidfw/tests/CursorWindow_test.cpp
@@ -0,0 +1,359 @@
+/*
+ * 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.
+ */
+
+#include <utility>
+
+#include "androidfw/CursorWindow.h"
+
+#include "TestHelpers.h"
+
+#define CREATE_WINDOW_1K \
+    CursorWindow* w; \
+    CursorWindow::create(String8("test"), 1 << 10, &w);
+
+#define CREATE_WINDOW_1K_3X3 \
+    CursorWindow* w; \
+    CursorWindow::create(String8("test"), 1 << 10, &w); \
+    ASSERT_EQ(w->setNumColumns(3), OK); \
+    ASSERT_EQ(w->allocRow(), OK); \
+    ASSERT_EQ(w->allocRow(), OK); \
+    ASSERT_EQ(w->allocRow(), OK);
+
+#define CREATE_WINDOW_2M \
+    CursorWindow* w; \
+    CursorWindow::create(String8("test"), 1 << 21, &w);
+
+static constexpr const size_t kHalfInlineSize = 8192;
+static constexpr const size_t kGiantSize = 1048576;
+
+namespace android {
+
+TEST(CursorWindowTest, Empty) {
+    CREATE_WINDOW_1K;
+
+    ASSERT_EQ(w->getNumRows(), 0);
+    ASSERT_EQ(w->getNumColumns(), 0);
+    ASSERT_EQ(w->size(), 1 << 10);
+    ASSERT_EQ(w->freeSpace(), 1 << 10);
+}
+
+TEST(CursorWindowTest, SetNumColumns) {
+    CREATE_WINDOW_1K;
+
+    // Once we've locked in columns, we can't adjust
+    ASSERT_EQ(w->getNumColumns(), 0);
+    ASSERT_EQ(w->setNumColumns(4), OK);
+    ASSERT_NE(w->setNumColumns(5), OK);
+    ASSERT_NE(w->setNumColumns(3), OK);
+    ASSERT_EQ(w->getNumColumns(), 4);
+}
+
+TEST(CursorWindowTest, SetNumColumnsAfterRow) {
+    CREATE_WINDOW_1K;
+
+    // Once we've locked in a row, we can't adjust columns
+    ASSERT_EQ(w->getNumColumns(), 0);
+    ASSERT_EQ(w->allocRow(), OK);
+    ASSERT_NE(w->setNumColumns(4), OK);
+    ASSERT_EQ(w->getNumColumns(), 0);
+}
+
+TEST(CursorWindowTest, AllocRow) {
+    CREATE_WINDOW_1K;
+
+    ASSERT_EQ(w->setNumColumns(4), OK);
+
+    // Rolling forward means we have less free space
+    ASSERT_EQ(w->getNumRows(), 0);
+    auto before = w->freeSpace();
+    ASSERT_EQ(w->allocRow(), OK);
+    ASSERT_LT(w->freeSpace(), before);
+    ASSERT_EQ(w->getNumRows(), 1);
+
+    // Verify we can unwind
+    ASSERT_EQ(w->freeLastRow(), OK);
+    ASSERT_EQ(w->freeSpace(), before);
+    ASSERT_EQ(w->getNumRows(), 0);
+
+    // Can't unwind when no rows left
+    ASSERT_NE(w->freeLastRow(), OK);
+}
+
+TEST(CursorWindowTest, AllocRowBounds) {
+    CREATE_WINDOW_1K;
+
+    // 60 columns is 960 bytes, which means only a single row can fit
+    ASSERT_EQ(w->setNumColumns(60), OK);
+    ASSERT_EQ(w->allocRow(), OK);
+    ASSERT_NE(w->allocRow(), OK);
+}
+
+TEST(CursorWindowTest, StoreNull) {
+    CREATE_WINDOW_1K_3X3;
+
+    ASSERT_EQ(w->putNull(1, 1), OK);
+    ASSERT_EQ(w->putNull(0, 0), OK);
+
+    {
+        auto field = w->getFieldSlot(1, 1);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL);
+    }
+    {
+        auto field = w->getFieldSlot(0, 0);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL);
+    }
+}
+
+TEST(CursorWindowTest, StoreLong) {
+    CREATE_WINDOW_1K_3X3;
+
+    ASSERT_EQ(w->putLong(1, 1, 0xf00d), OK);
+    ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
+
+    {
+        auto field = w->getFieldSlot(1, 1);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
+        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xf00d);
+    }
+    {
+        auto field = w->getFieldSlot(0, 0);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
+        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
+    }
+}
+
+TEST(CursorWindowTest, StoreString) {
+    CREATE_WINDOW_1K_3X3;
+
+    ASSERT_EQ(w->putString(1, 1, "food", 5), OK);
+    ASSERT_EQ(w->putString(0, 0, "cafe", 5), OK);
+
+    size_t size;
+    {
+        auto field = w->getFieldSlot(1, 1);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_STRING);
+        auto actual = w->getFieldSlotValueString(field, &size);
+        ASSERT_EQ(std::string(actual), "food");
+    }
+    {
+        auto field = w->getFieldSlot(0, 0);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_STRING);
+        auto actual = w->getFieldSlotValueString(field, &size);
+        ASSERT_EQ(std::string(actual), "cafe");
+    }
+}
+
+TEST(CursorWindowTest, StoreBounds) {
+    CREATE_WINDOW_1K_3X3;
+
+    // Can't work with values beyond bounds
+    ASSERT_NE(w->putLong(0, 3, 0xcafe), OK);
+    ASSERT_NE(w->putLong(3, 0, 0xcafe), OK);
+    ASSERT_NE(w->putLong(3, 3, 0xcafe), OK);
+    ASSERT_EQ(w->getFieldSlot(0, 3), nullptr);
+    ASSERT_EQ(w->getFieldSlot(3, 0), nullptr);
+    ASSERT_EQ(w->getFieldSlot(3, 3), nullptr);
+}
+
+TEST(CursorWindowTest, Inflate) {
+    CREATE_WINDOW_2M;
+
+    auto before = w->size();
+    ASSERT_EQ(w->setNumColumns(4), OK);
+    ASSERT_EQ(w->allocRow(), OK);
+
+    // Scratch buffer that will fit before inflation
+    void* buf = malloc(kHalfInlineSize);
+
+    // Store simple value
+    ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
+
+    // Store first object that fits inside
+    memset(buf, 42, kHalfInlineSize);
+    ASSERT_EQ(w->putBlob(0, 1, buf, kHalfInlineSize), OK);
+    ASSERT_EQ(w->size(), before);
+
+    // Store second simple value
+    ASSERT_EQ(w->putLong(0, 2, 0xface), OK);
+
+    // Store second object that requires inflation
+    memset(buf, 84, kHalfInlineSize);
+    ASSERT_EQ(w->putBlob(0, 3, buf, kHalfInlineSize), OK);
+    ASSERT_GT(w->size(), before);
+
+    // Verify data is intact
+    {
+        auto field = w->getFieldSlot(0, 0);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
+        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
+    }
+    {
+        auto field = w->getFieldSlot(0, 1);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
+        size_t actualSize;
+        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
+        ASSERT_EQ(actualSize, kHalfInlineSize);
+        memset(buf, 42, kHalfInlineSize);
+        ASSERT_NE(actual, buf);
+        ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0);
+    }
+    {
+        auto field = w->getFieldSlot(0, 2);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
+        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xface);
+    }
+    {
+        auto field = w->getFieldSlot(0, 3);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
+        size_t actualSize;
+        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
+        ASSERT_EQ(actualSize, kHalfInlineSize);
+        memset(buf, 84, kHalfInlineSize);
+        ASSERT_NE(actual, buf);
+        ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0);
+    }
+}
+
+TEST(CursorWindowTest, ParcelEmpty) {
+    CREATE_WINDOW_2M;
+
+    Parcel p;
+    w->writeToParcel(&p);
+    p.setDataPosition(0);
+    w = nullptr;
+
+    ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK);
+    ASSERT_EQ(w->getNumRows(), 0);
+    ASSERT_EQ(w->getNumColumns(), 0);
+    ASSERT_EQ(w->size(), 0);
+    ASSERT_EQ(w->freeSpace(), 0);
+
+    // We can't mutate the window after parceling
+    ASSERT_NE(w->setNumColumns(4), OK);
+    ASSERT_NE(w->allocRow(), OK);
+}
+
+TEST(CursorWindowTest, ParcelSmall) {
+    CREATE_WINDOW_2M;
+
+    auto before = w->size();
+    ASSERT_EQ(w->setNumColumns(4), OK);
+    ASSERT_EQ(w->allocRow(), OK);
+
+    // Scratch buffer that will fit before inflation
+    void* buf = malloc(kHalfInlineSize);
+
+    // Store simple value
+    ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
+
+    // Store first object that fits inside
+    memset(buf, 42, kHalfInlineSize);
+    ASSERT_EQ(w->putBlob(0, 1, buf, kHalfInlineSize), OK);
+    ASSERT_EQ(w->size(), before);
+
+    // Store second object with zero length
+    ASSERT_EQ(w->putBlob(0, 2, buf, 0), OK);
+    ASSERT_EQ(w->size(), before);
+
+    // Force through a parcel
+    Parcel p;
+    w->writeToParcel(&p);
+    p.setDataPosition(0);
+    w = nullptr;
+
+    ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK);
+    ASSERT_EQ(w->getNumRows(), 1);
+    ASSERT_EQ(w->getNumColumns(), 4);
+
+    // Verify data is intact
+    {
+        auto field = w->getFieldSlot(0, 0);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
+        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
+    }
+    {
+        auto field = w->getFieldSlot(0, 1);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
+        size_t actualSize;
+        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
+        ASSERT_EQ(actualSize, kHalfInlineSize);
+        memset(buf, 42, kHalfInlineSize);
+        ASSERT_NE(actual, buf);
+        ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0);
+    }
+    {
+        auto field = w->getFieldSlot(0, 2);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
+        size_t actualSize;
+        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
+        ASSERT_EQ(actualSize, 0);
+        ASSERT_NE(actual, nullptr);
+    }
+}
+
+TEST(CursorWindowTest, ParcelLarge) {
+    CREATE_WINDOW_2M;
+
+    ASSERT_EQ(w->setNumColumns(4), OK);
+    ASSERT_EQ(w->allocRow(), OK);
+
+    // Store simple value
+    ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
+
+    // Store object that forces inflation
+    void* buf = malloc(kGiantSize);
+    memset(buf, 42, kGiantSize);
+    ASSERT_EQ(w->putBlob(0, 1, buf, kGiantSize), OK);
+
+    // Store second object with zero length
+    ASSERT_EQ(w->putBlob(0, 2, buf, 0), OK);
+
+    // Force through a parcel
+    Parcel p;
+    w->writeToParcel(&p);
+    p.setDataPosition(0);
+    w = nullptr;
+
+    ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK);
+    ASSERT_EQ(w->getNumRows(), 1);
+    ASSERT_EQ(w->getNumColumns(), 4);
+
+    // Verify data is intact
+    {
+        auto field = w->getFieldSlot(0, 0);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
+        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
+    }
+    {
+        auto field = w->getFieldSlot(0, 1);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
+        size_t actualSize;
+        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
+        ASSERT_EQ(actualSize, kGiantSize);
+        memset(buf, 42, kGiantSize);
+        ASSERT_EQ(memcmp(buf, actual, kGiantSize), 0);
+    }
+    {
+        auto field = w->getFieldSlot(0, 2);
+        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
+        size_t actualSize;
+        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
+        ASSERT_EQ(actualSize, 0);
+        ASSERT_NE(actual, nullptr);
+    }
+}
+
+} // android
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index aedf5d5..1881e38 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -1048,6 +1048,20 @@
         }
     }
 
+    private void onModulationReported(int modulation) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(
+                    () -> mScanCallback.onModulationReported(modulation));
+        }
+    }
+
+    private void onPriorityReported(boolean isHighPriority) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(
+                    () -> mScanCallback.onPriorityReported(isHighPriority));
+        }
+    }
+
     /**
      * Opens a filter object based on the given types and buffer size.
      *
diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
index 2649fcf..62d55f5 100644
--- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -16,6 +16,7 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
@@ -101,6 +102,7 @@
      * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would return
      * default value. Use {@link TunerVersionChecker.getTunerVersion()} to check the version.
      */
+    @IntRange(from = 0, to = 0xefff)
     public int getIpFilterContextId() {
         return mIpFilterContextId;
     }
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index 57a04fd..91be5c3 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -187,7 +187,6 @@
 
     /**
      * Releases the MediaEvent object.
-     * @hide
      */
     public void release() {
         synchronized (mLock) {
diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
index b0491fd..c1400c8 100644
--- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java
+++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
@@ -19,6 +19,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.util.Log;
 
 /**
  * Scan callback.
@@ -27,6 +28,8 @@
  */
 @SystemApi
 public interface ScanCallback {
+    /** @hide **/
+    String TAG = "ScanCallback";
 
     /** Scan locked the signal. */
     void onLocked();
@@ -70,4 +73,13 @@
     /** Frontend signal type. */
     void onSignalTypeReported(@AnalogFrontendSettings.SignalType int signalType);
 
+    /** Frontend modulation reported. */
+    default void onModulationReported(@FrontendStatus.FrontendModulation int modulation) {
+        Log.d(TAG, "Received modulation scan message");
+    }
+
+    /** Frontend scan message priority reported. */
+    default void onPriorityReported(boolean isHighPriority) {
+        Log.d(TAG, "Received priority scan message: isHighPriority=" + isHighPriority);
+    }
 }
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index 7ce8512..93fdfba 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -5233,7 +5233,7 @@
     method @NonNull public static android.media.tv.tuner.filter.IpFilterConfiguration.Builder builder();
     method @NonNull @Size(min=4, max=16) public byte[] getDstIpAddress();
     method public int getDstPort();
-    method public int getIpFilterContextId();
+    method @IntRange(from=0, to=61439) public int getIpFilterContextId();
     method @NonNull @Size(min=4, max=16) public byte[] getSrcIpAddress();
     method public int getSrcPort();
     method public int getType();
@@ -5269,6 +5269,7 @@
     method public boolean isPrivateData();
     method public boolean isPtsPresent();
     method public boolean isSecureMemory();
+    method public void release();
   }
 
   public final class MmtpFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration {
@@ -6248,7 +6249,9 @@
     method public void onHierarchyReported(int);
     method public void onInputStreamIdsReported(@NonNull int[]);
     method public void onLocked();
+    method public default void onModulationReported(int);
     method public void onPlpIdsReported(@NonNull int[]);
+    method public default void onPriorityReported(boolean);
     method public void onProgress(@IntRange(from=0, to=100) int);
     method public void onScanStopped();
     method public void onSignalTypeReported(int);
diff --git a/packages/CarSystemUI/samples/sample3/rro/Android.bp b/packages/CarSystemUI/samples/sample3/rro/Android.bp
new file mode 100644
index 0000000..0eae7c2
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/Android.bp
@@ -0,0 +1,27 @@
+//
+// 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.
+//
+
+android_app {
+    name: "CarSystemUISampleThreeRRO",
+    resource_dirs: ["res"],
+    certificate: "platform",
+    platform_apis: true,
+    manifest: "AndroidManifest.xml",
+    aaptflags: [
+        "--no-resource-deduping",
+        "--no-resource-removal",
+     ]
+}
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/AndroidManifest.xml b/packages/CarSystemUI/samples/sample3/rro/AndroidManifest.xml
new file mode 100644
index 0000000..5c25056
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.systemui.rro">
+    <overlay
+        android:targetPackage="com.android.systemui"
+        android:isStatic="false"
+        android:resourcesMap="@xml/car_sysui_overlays"
+    />
+</manifest>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_apps.xml b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_apps.xml
new file mode 100644
index 0000000..a8d8a2f
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_apps.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+<path
+    android:pathData="M7.33333333 14.6666667L14.6666667 14.6666667L14.6666667 7.33333333L7.33333333 7.33333333L7.33333333 14.6666667ZM18.3333333 36.6666667L25.6666667 36.6666667L25.6666667 29.3333333L18.3333333 29.3333333L18.3333333 36.6666667ZM7.33333333 36.6666667L14.6666667 36.6666667L14.6666667 29.3333333L7.33333333 29.3333333L7.33333333 36.6666667ZM7.33333333 25.6666667L14.6666667 25.6666667L14.6666667 18.3333333L7.33333333 18.3333333L7.33333333 25.6666667ZM18.3333333 25.6666667L25.6666667 25.6666667L25.6666667 18.3333333L18.3333333 18.3333333L18.3333333 25.6666667ZM29.3333333 7.33333333L29.3333333 14.6666667L36.6666667 14.6666667L36.6666667 7.33333333L29.3333333 7.33333333ZM18.3333333 14.6666667L25.6666667 14.6666667L25.6666667 7.33333333L18.3333333 7.33333333L18.3333333 14.6666667ZM29.3333333 25.6666667L36.6666667 25.6666667L36.6666667 18.3333333L29.3333333 18.3333333L29.3333333 25.6666667ZM29.3333333 36.6666667L36.6666667 36.6666667L36.6666667 29.3333333L29.3333333 29.3333333L29.3333333 36.6666667Z"
+    android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_home.xml b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_home.xml
new file mode 100644
index 0000000..c78f0ed
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_home.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/system_bar_icon_drawing_size"
+        android:height="@dimen/system_bar_icon_drawing_size"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_hvac.xml b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_hvac.xml
new file mode 100644
index 0000000..55c968e
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_hvac.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/system_bar_icon_drawing_size"
+        android:height="@dimen/system_bar_icon_drawing_size"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:pathData="M16.34,8.36l-2.29,0.82c-0.18,-0.13 -0.38,-0.25 -0.58,-0.34c0.17,-0.83 0.63,-1.58 1.36,-2.06C16.85,5.44 16.18,2 13.39,2C9,2 7.16,5.01 8.36,7.66l0.82,2.29c-0.13,0.18 -0.25,0.38 -0.34,0.58c-0.83,-0.17 -1.58,-0.63 -2.06,-1.36C5.44,7.15 2,7.82 2,10.61c0,4.4 3.01,6.24 5.66,5.03l2.29,-0.82c0.18,0.13 0.38,0.25 0.58,0.34c-0.17,0.83 -0.63,1.58 -1.36,2.06C7.15,18.56 7.82,22 10.61,22c4.4,0 6.24,-3.01 5.03,-5.66l-0.82,-2.29c0.13,-0.18 0.25,-0.38 0.34,-0.58c0.83,0.17 1.58,0.63 2.06,1.36c1.34,2.01 4.77,1.34 4.77,-1.45C22,9 18.99,7.16 16.34,8.36zM12,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5c0,-0.83 0.67,-1.5 1.5,-1.5c0.83,0 1.5,0.67 1.5,1.5C13.5,12.83 12.83,13.5 12,13.5zM10.24,5.22C10.74,4.44 11.89,4 13.39,4c0.79,0 0.71,0.86 0.34,1.11c-1.22,0.81 -2,2.06 -2.25,3.44c-0.21,0.03 -0.42,0.08 -0.62,0.15l-0.68,-1.88C10,6.42 9.86,5.81 10.24,5.22zM6.83,13.82c-0.4,0.18 -1.01,0.32 -1.61,-0.06C4.44,13.26 4,12.11 4,10.61c0,-0.79 0.86,-0.71 1.11,-0.34c0.81,1.22 2.06,2 3.44,2.25c0.03,0.21 0.08,0.42 0.15,0.62L6.83,13.82zM13.76,18.78c-0.5,0.77 -1.65,1.22 -3.15,1.22c-0.79,0 -0.71,-0.86 -0.34,-1.11c1.22,-0.81 2,-2.06 2.25,-3.44c0.21,-0.03 0.42,-0.08 0.62,-0.15l0.68,1.88C14,17.58 14.14,18.18 13.76,18.78zM18.89,13.73c-0.81,-1.22 -2.06,-2 -3.44,-2.25c-0.03,-0.21 -0.08,-0.42 -0.15,-0.62l1.88,-0.68c0.4,-0.18 1.01,-0.32 1.61,0.06c0.77,0.5 1.22,1.65 1.22,3.15C20,14.19 19.14,14.11 18.89,13.73z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_music.xml b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_music.xml
new file mode 100644
index 0000000..6339ebb
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_music.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M22 5.5L22 24.8416667C20.9183333 24.2183333 19.6716667 23.8333333 18.3333333 23.8333333C14.2816667 23.8333333 11 27.115 11 31.1666667C11 35.2183333 14.2816667 38.5 18.3333333 38.5C22.385 38.5 25.6666667 35.2183333 25.6666667 31.1666667L25.6666667 12.8333333L33 12.8333333L33 5.5L22 5.5Z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_navigation.xml b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_navigation.xml
new file mode 100644
index 0000000..e1fabe0
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_navigation.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M39.8016667 20.6983333L23.3016667 4.19833333C22.5866667 3.48333333 21.4316667 3.48333333 20.7166667 4.19833333L4.21666667 20.6983333C3.50166667 21.4133333 3.50166667 22.5683333 4.21666667 23.2833333L20.7166667 39.7833333C21.4316667 40.4983333 22.5866667 40.4983333 23.3016667 39.7833333L39.8016667 23.2833333C40.5166667 22.5866667 40.5166667 21.4316667 39.8016667 20.6983333ZM25.6666667 26.5833333L25.6666667 22L18.3333333 22L18.3333333 27.5L14.6666667 27.5L14.6666667 20.1666667C14.6666667 19.1583333 15.4916667 18.3333333 16.5 18.3333333L25.6666667 18.3333333L25.6666667 13.75L32.0833333 20.1666667L25.6666667 26.5833333Z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_notification.xml b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_notification.xml
new file mode 100644
index 0000000..aabf916
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_notification.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/system_bar_icon_drawing_size"
+        android:height="@dimen/system_bar_icon_drawing_size"
+        android:viewportWidth="44"
+        android:viewportHeight="44">
+    <path
+        android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_overview.xml b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_overview.xml
new file mode 100644
index 0000000..f185eb9
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_overview.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M36.92857 22.39286A14.53571 14.53571 0 0 1 7.857143 22.39286A14.53571 14.53571 0 0 1 36.92857 22.39286Z"
+        android:strokeColor="@color/car_nav_icon_fill_color"
+        android:strokeWidth="4" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_phone.xml b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_phone.xml
new file mode 100644
index 0000000..50e36b5
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/drawable/car_ic_phone.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M12.1366667 19.7816667C14.7766667 24.97 19.03 29.205 24.2183333 31.8633333L28.2516667 27.83C28.7466667 27.335 29.48 27.17 30.1216667 27.39C32.175 28.0683333 34.3933333 28.435 36.6666667 28.435C37.675 28.435 38.5 29.26 38.5 30.2683333L38.5 36.6666667C38.5 37.675 37.675 38.5 36.6666667 38.5C19.4516667 38.5 5.5 24.5483333 5.5 7.33333333C5.5 6.325 6.325 5.5 7.33333333 5.5L13.75 5.5C14.7583333 5.5 15.5833333 6.325 15.5833333 7.33333333C15.5833333 9.625 15.95 11.825 16.6283333 13.8783333C16.83 14.52 16.6833333 15.235 16.17 15.7483333L12.1366667 19.7816667Z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/drawable/system_bar_background.xml b/packages/CarSystemUI/samples/sample3/rro/res/drawable/system_bar_background.xml
new file mode 100644
index 0000000..66da21c
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/drawable/system_bar_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
+    <solid
+        android:color="#404040"
+    />
+</shape>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/layout/car_left_navigation_bar.xml b/packages/CarSystemUI/samples/sample3/rro/res/layout/car_left_navigation_bar.xml
new file mode 100644
index 0000000..3d1cd08
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/layout/car_left_navigation_bar.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.systemui.car.navigationbar.CarNavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
+    android:background="@android:color/transparent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:gravity="bottom"
+        android:orientation="vertical">
+
+        <com.android.systemui.statusbar.policy.Clock
+            android:id="@+id/clock"
+            android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:singleLine="true"
+            android:gravity="center_horizontal"
+            android:paddingBottom="20dp"
+        />
+
+        <Space
+            android:layout_height="50dp"
+            android:layout_width="match_parent"/>
+
+    </LinearLayout>
+
+</com.android.systemui.car.navigationbar.CarNavigationBarView>
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/samples/sample3/rro/res/layout/car_navigation_bar.xml
new file mode 100644
index 0000000..8314ba5
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/layout/car_navigation_bar.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<com.android.systemui.car.navigationbar.CarNavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/system_bar_background"
+    android:gravity="center"
+    android:orientation="horizontal">
+
+    <RelativeLayout
+        android:id="@+id/nav_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layoutDirection="ltr">
+
+        <com.android.systemui.car.hvac.AdjustableTemperatureView
+            android:id="@+id/driver_hvac"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:gravity="center_vertical"
+            systemui:hvacAreaId="49"
+            systemui:hvacTempFormat="%.0f\u00B0" />
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_centerInParent="true"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:layoutDirection="ltr"
+            android:paddingEnd="@dimen/system_bar_button_group_padding"
+            android:paddingStart="@dimen/system_bar_button_group_padding">
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"/>
+
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/home"
+                style="@style/NavigationBarButton"
+                systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+                systemui:highlightWhenSelected="true"
+                systemui:icon="@drawable/car_ic_home"
+                systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"/>
+
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/phone_nav"
+                style="@style/NavigationBarButton"
+                systemui:highlightWhenSelected="true"
+                systemui:icon="@drawable/car_ic_phone"
+                systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
+                systemui:packages="com.android.car.dialer"/>
+
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/grid_nav"
+                style="@style/NavigationBarButton"
+                systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+                systemui:highlightWhenSelected="true"
+                systemui:icon="@drawable/car_ic_apps"
+                systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"/>
+
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/hvac"
+                style="@style/NavigationBarButton"
+                systemui:highlightWhenSelected="true"
+                systemui:icon="@drawable/car_ic_hvac"
+                systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+                systemui:broadcast="true"/>
+
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/notifications"
+                style="@style/NavigationBarButton"
+                systemui:highlightWhenSelected="true"
+                systemui:icon="@drawable/car_ic_notification"
+                systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"/>
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"/>
+        </LinearLayout>
+
+        <com.android.systemui.car.hvac.AdjustableTemperatureView
+            android:id="@+id/passenger_hvac"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentEnd="true"
+            android:gravity="center_vertical"
+            systemui:hvacAreaId="68"
+            systemui:hvacTempFormat="%.0f\u00B0" />
+    </RelativeLayout>
+
+    <LinearLayout
+        android:id="@+id/lock_screen_nav_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:layoutDirection="ltr"
+        android:paddingEnd="@dimen/car_keyline_1"
+        android:paddingStart="@dimen/car_keyline_1"
+        android:visibility="gone"
+    />
+</com.android.systemui.car.navigationbar.CarNavigationBarView>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/values/attrs.xml b/packages/CarSystemUI/samples/sample3/rro/res/values/attrs.xml
new file mode 100644
index 0000000..bc7ded2
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/values/attrs.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <attr name="broadcast" format="boolean"/>
+    <attr name="icon" format="reference"/>
+    <attr name="intent" format="string"/>
+    <attr name="longIntent" format="string"/>
+    <attr name="componentNames" format="string" />
+    <attr name="highlightWhenSelected" format="boolean" />
+    <attr name="categories" format="string"/>
+    <attr name="packages" format="string" />
+
+    <!-- Custom attributes to configure hvac values -->
+    <declare-styleable name="AnimatedTemperatureView">
+        <attr name="hvacAreaId" format="integer"/>
+        <attr name="hvacPropertyId" format="integer"/>
+        <attr name="hvacTempFormat" format="string"/>
+        <!-- how far away the animations should center around -->
+        <attr name="hvacPivotOffset" format="dimension"/>
+        <attr name="hvacMinValue" format="float"/>
+        <attr name="hvacMaxValue" format="float"/>
+        <attr name="hvacMinText" format="string|reference"/>
+        <attr name="hvacMaxText" format="string|reference"/>
+        <attr name="android:gravity"/>
+        <attr name="android:minEms"/>
+        <attr name="android:textAppearance"/>
+    </declare-styleable>
+</resources>
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/values/colors.xml b/packages/CarSystemUI/samples/sample3/rro/res/values/colors.xml
new file mode 100644
index 0000000..f98cb96
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="car_nav_icon_fill_color">#8F8F8F</color>
+</resources>
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/values/config.xml b/packages/CarSystemUI/samples/sample3/rro/res/values/config.xml
new file mode 100644
index 0000000..2148e7c
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/values/config.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <!-- Configure which system bars should be displayed. -->
+    <bool name="config_enableTopNavigationBar">false</bool>
+    <bool name="config_enableLeftNavigationBar">true</bool>
+    <bool name="config_enableRightNavigationBar">false</bool>
+    <bool name="config_enableBottomNavigationBar">true</bool>
+
+    <!-- Configure the type of each system bar. Each system bar must have a unique type. -->
+    <!--    STATUS_BAR = 0-->
+    <!--    NAVIGATION_BAR = 1-->
+    <!--    STATUS_BAR_EXTRA = 2-->
+    <!--    NAVIGATION_BAR_EXTRA = 3-->
+    <integer name="config_topSystemBarType">2</integer>
+    <integer name="config_leftSystemBarType">0</integer>
+    <integer name="config_rightSystemBarType">3</integer>
+    <integer name="config_bottomSystemBarType">1</integer>
+
+    <!-- Configure the relative z-order among the system bars. When two system bars overlap (e.g.
+         if both top bar and left bar are enabled, it creates an overlapping space in the upper left
+         corner), the system bar with the higher z-order takes the overlapping space and padding is
+         applied to the other bar.-->
+    <!-- NOTE: If two overlapping system bars have the same z-order, SystemBarConfigs will throw a
+         RuntimeException, since their placing order cannot be determined. Bars that do not overlap
+         are allowed to have the same z-order. -->
+    <!-- NOTE: If the z-order of a bar is 10 or above, it will also appear on top of HUN's.    -->
+    <integer name="config_topSystemBarZOrder">0</integer>
+    <integer name="config_leftSystemBarZOrder">10</integer>
+    <integer name="config_rightSystemBarZOrder">0</integer>
+    <integer name="config_bottomSystemBarZOrder">15</integer>
+
+    <!-- Whether heads-up notifications should be shown on the bottom. If false, heads-up
+         notifications will be shown pushed to the top of their parent container. If true, they will
+         be shown pushed to the bottom of their parent container. If true, then should override
+         config_headsUpNotificationAnimationHelper to use a different AnimationHelper, such as
+         com.android.car.notification.headsup.animationhelper.
+         CarHeadsUpNotificationBottomAnimationHelper. -->
+    <bool name="config_showHeadsUpNotificationOnBottom">false</bool>
+
+    <string name="config_notificationPanelViewMediator" translatable="false">com.android.systemui.car.notification.BottomNotificationPanelViewMediator</string>
+</resources>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/values/dimens.xml b/packages/CarSystemUI/samples/sample3/rro/res/values/dimens.xml
new file mode 100644
index 0000000..c89f949
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<resources>
+    <dimen name="car_left_navigation_bar_width">280dp</dimen>
+    <dimen name="car_keyline_1">24dp</dimen>
+    <dimen name="system_bar_button_group_padding">64dp</dimen>
+    <dimen name="system_bar_icon_drawing_size">44dp</dimen>
+</resources>
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/values/styles.xml b/packages/CarSystemUI/samples/sample3/rro/res/values/styles.xml
new file mode 100644
index 0000000..bad3691
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/values/styles.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="TextAppearance.StatusBar.Clock"
+           parent="@*android:style/TextAppearance.StatusBar.Icon">
+        <item name="android:textSize">40sp</item>
+        <item name="android:fontFamily">sans-serif-regular</item>
+        <item name="android:textColor">#FFFFFF</item>
+    </style>
+
+    <style name="NavigationBarButton">
+        <item name="android:layout_height">96dp</item>
+        <item name="android:layout_width">96dp</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample3/rro/res/xml/car_sysui_overlays.xml b/packages/CarSystemUI/samples/sample3/rro/res/xml/car_sysui_overlays.xml
new file mode 100644
index 0000000..f08d968
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample3/rro/res/xml/car_sysui_overlays.xml
@@ -0,0 +1,75 @@
+
+<!--
+  ~ 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.
+  -->
+
+<overlay>
+    <item target="layout/car_navigation_bar" value="@layout/car_navigation_bar"/>
+    <item target="layout/car_left_navigation_bar" value="@layout/car_left_navigation_bar"/>
+
+    <item target="bool/config_enableTopNavigationBar" value="@bool/config_enableTopNavigationBar"/>
+    <item target="bool/config_enableLeftNavigationBar" value="@bool/config_enableLeftNavigationBar"/>
+    <item target="bool/config_enableRightNavigationBar" value="@bool/config_enableRightNavigationBar"/>
+    <item target="bool/config_enableBottomNavigationBar" value="@bool/config_enableBottomNavigationBar"/>
+    <item target="bool/config_showHeadsUpNotificationOnBottom" value="@bool/config_showHeadsUpNotificationOnBottom"/>
+
+    <item target="attr/icon" value="@attr/icon"/>
+    <item target="attr/intent" value="@attr/intent"/>
+    <item target="attr/longIntent" value="@attr/longIntent"/>
+    <item target="attr/componentNames" value="@attr/componentNames"/>
+    <item target="attr/highlightWhenSelected" value="@attr/highlightWhenSelected"/>
+    <item target="attr/categories" value="@attr/categories"/>
+    <item target="attr/packages" value="@attr/packages"/>
+    <item target="attr/hvacAreaId" value="@attr/hvacAreaId"/>
+    <item target="attr/hvacPropertyId" value="@attr/hvacPropertyId"/>
+    <item target="attr/hvacTempFormat" value="@attr/hvacTempFormat"/>
+    <item target="attr/hvacPivotOffset" value="@attr/hvacPivotOffset"/>
+    <item target="attr/hvacMinValue" value="@attr/hvacMinValue"/>
+    <item target="attr/hvacMaxValue" value="@attr/hvacMaxValue"/>
+    <item target="attr/hvacMinText" value="@attr/hvacMinText"/>
+    <item target="attr/hvacMaxText" value="@attr/hvacMaxText"/>
+    <!-- start the intent as a broad cast instead of an activity if true-->
+    <item target="attr/broadcast" value="@attr/broadcast"/>
+
+    <item target="color/car_nav_icon_fill_color" value="@color/car_nav_icon_fill_color" />
+
+    <item target="drawable/car_ic_overview" value="@drawable/car_ic_overview" />
+    <item target="drawable/car_ic_home" value="@drawable/car_ic_home" />
+    <item target="drawable/car_ic_hvac" value="@drawable/car_ic_hvac" />
+    <item target="drawable/car_ic_apps" value="@drawable/car_ic_apps" />
+    <item target="drawable/car_ic_music" value="@drawable/car_ic_music" />
+    <item target="drawable/car_ic_notification" value="@drawable/car_ic_notification" />
+    <item target="drawable/car_ic_phone" value="@drawable/car_ic_phone" />
+    <item target="drawable/car_ic_navigation" value="@drawable/car_ic_navigation" />
+
+    <item target="dimen/car_left_navigation_bar_width" value="@dimen/car_left_navigation_bar_width" />
+    <item target="dimen/car_keyline_1" value="@dimen/car_keyline_1" />
+    <item target="dimen/system_bar_button_group_padding" value="@dimen/system_bar_button_group_padding" />
+    <item target="dimen/system_bar_icon_drawing_size" value="@dimen/system_bar_icon_drawing_size" />
+
+    <item target="integer/config_topSystemBarType" value="@integer/config_topSystemBarType"/>
+    <item target="integer/config_leftSystemBarType" value="@integer/config_leftSystemBarType"/>
+    <item target="integer/config_rightSystemBarType" value="@integer/config_rightSystemBarType"/>
+    <item target="integer/config_bottomSystemBarType" value="@integer/config_bottomSystemBarType"/>
+
+    <item target="integer/config_topSystemBarZOrder" value="@integer/config_topSystemBarZOrder"/>
+    <item target="integer/config_leftSystemBarZOrder" value="@integer/config_leftSystemBarZOrder"/>
+    <item target="integer/config_rightSystemBarZOrder" value="@integer/config_rightSystemBarZOrder"/>
+    <item target="integer/config_bottomSystemBarZOrder" value="@integer/config_bottomSystemBarZOrder"/>
+
+    <item target="string/config_notificationPanelViewMediator" value="@string/config_notificationPanelViewMediator"/>
+
+    <item target="style/NavigationBarButton" value="@style/NavigationBarButton"/>
+</overlay>
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index f1fb527..90bed12 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -749,25 +749,25 @@
                 Settings.Global.GPU_DEBUG_LAYERS,
                 GlobalSettingsProto.Gpu.DEBUG_LAYERS);
         dumpSetting(s, p,
-                Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE,
+                Settings.Global.ANGLE_DEBUG_PACKAGE,
                 GlobalSettingsProto.Gpu.ANGLE_DEBUG_PACKAGE);
         dumpSetting(s, p,
-                Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE,
+                Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE,
                 GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_ALL_ANGLE);
         dumpSetting(s, p,
-                Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS,
+                Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS,
                 GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_PKGS);
         dumpSetting(s, p,
-                Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
+                Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES,
                 GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_VALUES);
         dumpSetting(s, p,
-                Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST,
+                Settings.Global.ANGLE_ALLOWLIST,
                 GlobalSettingsProto.Gpu.ANGLE_ALLOWLIST);
         dumpSetting(s, p,
                 Settings.Global.ANGLE_EGL_FEATURES,
                 GlobalSettingsProto.Gpu.ANGLE_EGL_FEATURES);
         dumpSetting(s, p,
-                Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX,
+                Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX,
                 GlobalSettingsProto.Gpu.SHOW_ANGLE_IN_USE_DIALOG);
         dumpSetting(s, p,
                 Settings.Global.GPU_DEBUG_LAYER_APP,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 8aad467..4713243 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -503,11 +503,11 @@
                     Settings.Global.GPU_DEBUG_APP,
                     Settings.Global.GPU_DEBUG_LAYERS,
                     Settings.Global.GPU_DEBUG_LAYERS_GLES,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST,
+                    Settings.Global.ANGLE_DEBUG_PACKAGE,
+                    Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE,
+                    Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS,
+                    Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES,
+                    Settings.Global.ANGLE_ALLOWLIST,
                     Settings.Global.ANGLE_EGL_FEATURES,
                     Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
                     Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
@@ -517,7 +517,7 @@
                     Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST,
                     Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST,
                     Settings.Global.UPDATABLE_DRIVER_SPHAL_LIBRARIES,
-                    Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX,
+                    Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX,
                     Settings.Global.GPU_DEBUG_LAYER_APP,
                     Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
                     Settings.Global.INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT,
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 997805a..e1af328b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -136,6 +136,7 @@
                 }
                 try {
                     if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
+                        options.setApplyActivityFlagsForBubbles(true);
                         mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
                                 options, null /* sourceBounds */);
                     } else {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index ae1145a..0714c5e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -2246,9 +2246,10 @@
             }
 
             // Stop suppressing the dot now that the flyout has morphed into the dot.
-            bubble.getIconView().removeDotSuppressionFlag(
-                    BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
-
+            if (bubble.getIconView() != null) {
+                bubble.getIconView().removeDotSuppressionFlag(
+                        BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
+            }
             // Hide the stack after a delay, if needed.
             updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
         };
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 5f726cd..37bcb16 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -316,7 +316,8 @@
             }
             mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
             mPendingIntent = PendingIntent.getActivity(getContext(), 0,
-                    new Intent(getContext(), KeyguardSliceProvider.class), 0);
+                    new Intent(getContext(), KeyguardSliceProvider.class),
+                    PendingIntent.FLAG_IMMUTABLE);
             try {
                 //TODO(b/168778439): Remove this whole try catch. This is for debugging in dogfood.
                 mMediaManager.addCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 9e326aa..c824458 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -91,9 +91,9 @@
         }
     private var playbackState: PlaybackState? = null
     private var callback = object : MediaController.Callback() {
-        override fun onPlaybackStateChanged(state: PlaybackState) {
+        override fun onPlaybackStateChanged(state: PlaybackState?) {
             playbackState = state
-            if (PlaybackState.STATE_NONE.equals(playbackState)) {
+            if (playbackState == null || PlaybackState.STATE_NONE.equals(playbackState)) {
                 clearController()
             } else {
                 checkIfPollingNeeded()
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index e14af23..5310b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -85,8 +85,9 @@
 
     @WMSingleton
     @Provides
-    static PipBoundsHandler providePipBoundsHandler(Context context) {
-        return new PipBoundsHandler(context);
+    static PipBoundsHandler providePipBoundsHandler(Context context,
+            PipBoundsState pipBoundsState) {
+        return new PipBoundsHandler(context, pipBoundsState);
     }
 
     @WMSingleton
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 70632b2..a6fe728 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -97,8 +97,9 @@
 
     @WMSingleton
     @Provides
-    static PipBoundsHandler providePipBoundsHandler(Context context) {
-        return new PipBoundsHandler(context);
+    static PipBoundsHandler providesPipBoundsHandler(Context context,
+            PipBoundsState pipBoundsState) {
+        return new PipBoundsHandler(context, pipBoundsState);
     }
 
     @WMSingleton
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index b81ab74..1f9862c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -654,4 +654,21 @@
         fakeExecutor.runAllReady()
         verify(mockController).unregisterCallback(any())
     }
+
+    @Test
+    fun nullPlaybackStateUnregistersCallback() {
+        viewModel.updateController(mockController)
+        val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
+        verify(mockController).registerCallback(captor.capture())
+        val callback = captor.value
+        // WHEN the callback receives a null state
+        callback.onPlaybackStateChanged(null)
+        with(fakeExecutor) {
+            advanceClockToNext()
+            runAllReady()
+        }
+        // THEN we unregister callback (as a result of clearing the controller)
+        fakeExecutor.runAllReady()
+        verify(mockController).unregisterCallback(any())
+    }
 }
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 529c651..f32423f 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -85,19 +85,19 @@
         sGlobalSettingToTypeMap.put(
                 Settings.Global.DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE, String.class);
         sGlobalSettingToTypeMap.put(
-                Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE, String.class);
+                Settings.Global.ANGLE_DEBUG_PACKAGE, String.class);
         sGlobalSettingToTypeMap.put(
-                Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE, int.class);
+                Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE, int.class);
         sGlobalSettingToTypeMap.put(
-                Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, String.class);
+                Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS, String.class);
         sGlobalSettingToTypeMap.put(
-                Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, String.class);
+                Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES, String.class);
         sGlobalSettingToTypeMap.put(
-                Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST, String.class);
+                Settings.Global.ANGLE_ALLOWLIST, String.class);
         sGlobalSettingToTypeMap.put(
                 Settings.Global.ANGLE_EGL_FEATURES, String.class);
         sGlobalSettingToTypeMap.put(
-                Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class);
+                Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class);
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS, String.class);
diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java
index 747e8a8..be63dd4 100644
--- a/services/core/java/com/android/server/am/PreBootBroadcaster.java
+++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java
@@ -144,7 +144,8 @@
 
                     final PendingIntent contentIntent;
                     if (context.getPackageManager().resolveActivity(intent, 0) != null) {
-                        contentIntent = PendingIntent.getActivity(context, 0, intent, 0);
+                        contentIntent = PendingIntent.getActivity(context, 0, intent,
+                                PendingIntent.FLAG_IMMUTABLE);
                     } else {
                         contentIntent = null;
                     }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 30f6fa6..d6a56ba 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -21,6 +21,7 @@
         "BroadcastRadio/TunerCallback.cpp",
         "BroadcastRadio/convert.cpp",
         "BroadcastRadio/regions.cpp",
+        "gnss/GnssConfiguration.cpp",
         "stats/PowerStatsPuller.cpp",
         "stats/SubsystemSleepStatePuller.cpp",
         "com_android_server_adb_AdbDebuggingManager.cpp",
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 31b9ad6..e9d048a 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "GnssLocationProvider"
-
+// Define LOG_TAG and LOG_NDEBUG before <log/log.h> to overwrite the default values.
+#define LOG_TAG "GnssLocationProviderJni"
 #define LOG_NDEBUG 0
 
 #include <android/hardware/gnss/1.0/IGnss.h>
@@ -39,6 +39,7 @@
 #include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
+#include "gnss/GnssConfiguration.h"
 #include "hardware_legacy/power.h"
 #include "jni.h"
 #include "utils/Log.h"
@@ -59,7 +60,6 @@
 static jclass class_location;
 static jclass class_gnssNavigationMessage;
 static jclass class_gnssClock;
-static jclass class_gnssConfiguration_halInterfaceVersion;
 static jclass class_gnssAntennaInfoBuilder;
 static jclass class_phaseCenterOffset;
 static jclass class_sphericalCorrections;
@@ -125,7 +125,6 @@
 static jmethodID method_gnssNavigationMessageCtor;
 static jmethodID method_gnssClockCtor;
 static jmethodID method_gnssMeasurementCtor;
-static jmethodID method_halInterfaceVersionCtor;
 static jmethodID method_gnssAntennaInfoBuilderCtor;
 static jmethodID method_phaseCenterOffsetCtor;
 static jmethodID method_sphericalCorrectionsCtor;
@@ -149,6 +148,7 @@
 using android::String16;
 using android::wp;
 using android::binder::Status;
+using android::gnss::GnssConfigurationInterface;
 
 using android::hardware::Return;
 using android::hardware::Void;
@@ -192,10 +192,6 @@
 using IGnssCallback_V1_0 = android::hardware::gnss::V1_0::IGnssCallback;
 using IGnssCallback_V2_0 = android::hardware::gnss::V2_0::IGnssCallback;
 using IGnssCallback_V2_1 = android::hardware::gnss::V2_1::IGnssCallback;
-using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration;
-using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration;
-using IGnssConfiguration_V2_0 = android::hardware::gnss::V2_0::IGnssConfiguration;
-using IGnssConfiguration_V2_1 = android::hardware::gnss::V2_1::IGnssConfiguration;
 using IGnssDebug_V1_0 = android::hardware::gnss::V1_0::IGnssDebug;
 using IGnssDebug_V2_0 = android::hardware::gnss::V2_0::IGnssDebug;
 using IGnssAntennaInfo = android::hardware::gnss::V2_1::IGnssAntennaInfo;
@@ -269,11 +265,6 @@
 sp<IGnssBatching_V2_0> gnssBatchingIface_V2_0 = nullptr;
 sp<IGnssDebug_V1_0> gnssDebugIface = nullptr;
 sp<IGnssDebug_V2_0> gnssDebugIface_V2_0 = nullptr;
-sp<IGnssConfigurationAidl> gnssConfigurationAidlIface = nullptr;
-sp<IGnssConfiguration_V1_0> gnssConfigurationIface = nullptr;
-sp<IGnssConfiguration_V1_1> gnssConfigurationIface_V1_1 = nullptr;
-sp<IGnssConfiguration_V2_0> gnssConfigurationIface_V2_0 = nullptr;
-sp<IGnssConfiguration_V2_1> gnssConfigurationIface_V2_1 = nullptr;
 sp<IGnssNi> gnssNiIface = nullptr;
 sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr;
 sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr;
@@ -285,6 +276,8 @@
 sp<IGnssVisibilityControl> gnssVisibilityControlIface = nullptr;
 sp<IGnssAntennaInfo> gnssAntennaInfoIface = nullptr;
 
+std::unique_ptr<GnssConfigurationInterface> gnssConfigurationIface = nullptr;
+
 #define WAKE_LOCK_NAME  "GPS"
 
 namespace android {
@@ -467,12 +460,6 @@
     return JNI_TRUE;
 }
 
-static jobject createHalInterfaceVersionJavaObject(JNIEnv* env, jint major, jint minor) {
-    jobject version = env->NewObject(class_gnssConfiguration_halInterfaceVersion,
-            method_halInterfaceVersionCtor, major, minor);
-    return version;
-}
-
 struct ScopedJniString {
     ScopedJniString(JNIEnv* env, jstring javaString) : mEnv(env), mJavaString(javaString) {
         mNativeString = mEnv->GetStringUTFChars(mJavaString, nullptr);
@@ -2159,13 +2146,6 @@
     class_gnssClock = (jclass) env->NewGlobalRef(gnssClockClass);
     method_gnssClockCtor = env->GetMethodID(class_gnssClock, "<init>", "()V");
 
-    jclass gnssConfiguration_halInterfaceVersionClass = env->FindClass(
-            "com/android/server/location/gnss/GnssConfiguration$HalInterfaceVersion");
-    class_gnssConfiguration_halInterfaceVersion =
-            (jclass) env->NewGlobalRef(gnssConfiguration_halInterfaceVersionClass);
-    method_halInterfaceVersionCtor =
-            env->GetMethodID(class_gnssConfiguration_halInterfaceVersion, "<init>", "(II)V");
-
     jclass arrayListClass = env->FindClass("java/util/ArrayList");
     class_arrayList = (jclass)env->NewGlobalRef(arrayListClass);
     method_arrayListCtor = env->GetMethodID(class_arrayList, "<init>", "()V");
@@ -2173,6 +2153,8 @@
 
     jclass doubleArrayClass = env->FindClass("[D");
     class_doubleArray = (jclass)env->NewGlobalRef(doubleArrayClass);
+
+    gnss::GnssConfiguration_class_init_once(env);
 }
 
 /* Initialization needed at system boot and whenever GNSS service dies. */
@@ -2373,44 +2355,38 @@
     if (gnssHalAidl != nullptr) {
         sp<IGnssConfigurationAidl> gnssConfigurationAidl;
         auto status = gnssHalAidl->getExtensionGnssConfiguration(&gnssConfigurationAidl);
-        if (status.isOk()) {
-            gnssConfigurationAidlIface = gnssConfigurationAidl;
-        } else {
-            ALOGD("Unable to get a handle to GnssConfiguration AIDL interface.");
+        if (checkAidlStatus(status,
+                            "Unable to get a handle to GnssConfiguration AIDL interface.")) {
+            gnssConfigurationIface =
+                    std::make_unique<android::gnss::GnssConfiguration>(gnssConfigurationAidl);
         }
     } else if (gnssHal_V2_1 != nullptr) {
         auto gnssConfiguration = gnssHal_V2_1->getExtensionGnssConfiguration_2_1();
-        if (!gnssConfiguration.isOk()) {
-            ALOGD("Unable to get a handle to GnssConfiguration_V2_1");
-        } else {
-            gnssConfigurationIface_V2_1 = gnssConfiguration;
-            gnssConfigurationIface_V2_0 = gnssConfigurationIface_V2_1;
-            gnssConfigurationIface_V1_1 = gnssConfigurationIface_V2_1;
-            gnssConfigurationIface = gnssConfigurationIface_V2_1;
+        if (checkHidlReturn(gnssConfiguration,
+                            "Unable to get a handle to GnssConfiguration_V2_1")) {
+            gnssConfigurationIface =
+                    std::make_unique<android::gnss::GnssConfiguration_V2_1>(gnssConfiguration);
         }
     } else if (gnssHal_V2_0 != nullptr) {
         auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0();
-        if (!gnssConfiguration.isOk()) {
-            ALOGD("Unable to get a handle to GnssConfiguration_V2_0");
-        } else {
-            gnssConfigurationIface_V2_0 = gnssConfiguration;
-            gnssConfigurationIface_V1_1 = gnssConfigurationIface_V2_0;
-            gnssConfigurationIface = gnssConfigurationIface_V2_0;
+        if (checkHidlReturn(gnssConfiguration,
+                            "Unable to get a handle to GnssConfiguration_V2_0")) {
+            gnssConfigurationIface =
+                    std::make_unique<android::gnss::GnssConfiguration_V2_0>(gnssConfiguration);
         }
     } else if (gnssHal_V1_1 != nullptr) {
         auto gnssConfiguration = gnssHal_V1_1->getExtensionGnssConfiguration_1_1();
-        if (!gnssConfiguration.isOk()) {
-            ALOGD("Unable to get a handle to GnssConfiguration_V1_1");
-        } else {
-            gnssConfigurationIface_V1_1 = gnssConfiguration;
-            gnssConfigurationIface = gnssConfigurationIface_V1_1;
+        if (checkHidlReturn(gnssConfiguration,
+                            "Unable to get a handle to GnssConfiguration_V1_1")) {
+            gnssConfigurationIface =
+                    std::make_unique<android::gnss::GnssConfiguration_V1_1>(gnssConfiguration);
         }
     } else {
-        auto gnssConfiguration_V1_0 = gnssHal->getExtensionGnssConfiguration();
-        if (!gnssConfiguration_V1_0.isOk()) {
-            ALOGD("Unable to get a handle to GnssConfiguration");
-        } else {
-            gnssConfigurationIface = gnssConfiguration_V1_0;
+        auto gnssConfiguration = gnssHal->getExtensionGnssConfiguration();
+        if (checkHidlReturn(gnssConfiguration,
+                            "Unable to get a handle to GnssConfiguration_V1_0")) {
+            gnssConfigurationIface =
+                    std::make_unique<android::gnss::GnssConfiguration_V1_0>(gnssConfiguration);
         }
     }
 
@@ -2466,25 +2442,10 @@
 
 static jobject android_location_GnssConfiguration_get_gnss_configuration_version(
         JNIEnv* env, jclass /* jclazz */) {
-    jint major, minor;
-    if (gnssConfigurationIface_V2_1 != nullptr) {
-        major = 2;
-        minor = 1;
-    }
-    else if (gnssConfigurationIface_V2_0 != nullptr) {
-        major = 2;
-        minor = 0;
-    } else if (gnssConfigurationIface_V1_1 != nullptr) {
-        major = 1;
-        minor = 1;
-    } else if (gnssConfigurationIface != nullptr) {
-        major = 1;
-        minor = 0;
-    } else {
+    if (gnssConfigurationIface == nullptr) {
         return nullptr;
     }
-
-    return createHalInterfaceVersionJavaObject(env, major, minor);
+    return gnssConfigurationIface->getVersion(env);
 }
 
 /* Initialization needed each time the GPS service is shutdown. */
@@ -3486,203 +3447,89 @@
 static jboolean android_location_GnssConfiguration_set_emergency_supl_pdn(JNIEnv*,
                                                                           jobject,
                                                                           jint emergencySuplPdn) {
-    if (gnssConfigurationIface == nullptr && gnssConfigurationAidlIface == nullptr) {
+    if (gnssConfigurationIface == nullptr) {
         ALOGE("%s: IGnssConfiguration interface not available.", __func__);
         return JNI_FALSE;
     }
-
-    if (gnssConfigurationAidlIface != nullptr) {
-        auto status = gnssConfigurationAidlIface->setEmergencySuplPdn(emergencySuplPdn);
-        return checkAidlStatus(status, "gnssConfigurationAidlIface setEmergencySuplPdn() failed.");
-    }
-
-    auto result = gnssConfigurationIface->setEmergencySuplPdn(emergencySuplPdn);
-    return checkHidlReturn(result, "IGnssConfiguration setEmergencySuplPdn() failed.");
+    return gnssConfigurationIface->setEmergencySuplPdn(emergencySuplPdn);
 }
 
 static jboolean android_location_GnssConfiguration_set_supl_version(JNIEnv*,
                                                                     jobject,
                                                                     jint version) {
-    if (gnssConfigurationIface == nullptr && gnssConfigurationAidlIface == nullptr) {
+    if (gnssConfigurationIface == nullptr) {
         ALOGE("%s: IGnssConfiguration interface not available.", __func__);
         return JNI_FALSE;
     }
-
-    if (gnssConfigurationAidlIface != nullptr) {
-        auto status = gnssConfigurationAidlIface->setSuplVersion(version);
-        return checkAidlStatus(status, "gnssConfigurationAidlIface setSuplVersion() failed.");
-    }
-
-    auto result = gnssConfigurationIface->setSuplVersion(version);
-    return checkHidlReturn(result, "IGnssConfiguration setSuplVersion() failed.");
+    return gnssConfigurationIface->setSuplVersion(version);
 }
 
 static jboolean android_location_GnssConfiguration_set_supl_es(JNIEnv*,
                                                                jobject,
                                                                jint suplEs) {
-    if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr ||
-        gnssConfigurationAidlIface != nullptr) {
-        ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0 and higher.");
-        return JNI_FALSE;
-    }
-
     if (gnssConfigurationIface == nullptr) {
         ALOGE("%s: IGnssConfiguration interface not available.", __func__);
         return JNI_FALSE;
     }
-
-    auto result = gnssConfigurationIface->setSuplEs(suplEs);
-    return checkHidlReturn(result, "IGnssConfiguration setSuplEs() failed.");
+    return gnssConfigurationIface->setSuplEs(suplEs);
 }
 
 static jboolean android_location_GnssConfiguration_set_supl_mode(JNIEnv*,
                                                                  jobject,
                                                                  jint mode) {
-    if (gnssConfigurationIface == nullptr && gnssConfigurationAidlIface == nullptr) {
+    if (gnssConfigurationIface == nullptr) {
         ALOGE("%s: IGnssConfiguration interface not available.", __func__);
         return JNI_FALSE;
     }
-
-    if (gnssConfigurationAidlIface != nullptr) {
-        auto status = gnssConfigurationAidlIface->setSuplMode(mode);
-        return checkAidlStatus(status, "gnssConfigurationAidlIface setSuplMode() failed.");
-    }
-
-    auto result = gnssConfigurationIface->setSuplMode(mode);
-    return checkHidlReturn(result, "IGnssConfiguration setSuplMode() failed.");
+    return gnssConfigurationIface->setSuplMode(mode);
 }
 
 static jboolean android_location_GnssConfiguration_set_gps_lock(JNIEnv*,
                                                                 jobject,
                                                                 jint gpsLock) {
-    if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr ||
-        gnssConfigurationAidlIface != nullptr) {
-        ALOGI("Config parameter GPS_LOCK is deprecated in IGnssConfiguration.hal version 2.0.");
-        return JNI_FALSE;
-    }
-
     if (gnssConfigurationIface == nullptr) {
         ALOGE("%s: IGnssConfiguration interface not available.", __func__);
         return JNI_FALSE;
     }
-
-    auto result = gnssConfigurationIface->setGpsLock(gpsLock);
-    return checkHidlReturn(result, "IGnssConfiguration setGpsLock() failed.");
+    return gnssConfigurationIface->setGpsLock(gpsLock);
 }
 
 static jboolean android_location_GnssConfiguration_set_lpp_profile(JNIEnv*,
                                                                    jobject,
                                                                    jint lppProfile) {
-    if (gnssConfigurationIface == nullptr && gnssConfigurationAidlIface == nullptr) {
+    if (gnssConfigurationIface == nullptr) {
         ALOGE("%s: IGnssConfiguration interface not available.", __func__);
         return JNI_FALSE;
     }
-
-    if (gnssConfigurationAidlIface != nullptr) {
-        auto status = gnssConfigurationAidlIface->setLppProfile(lppProfile);
-        return checkAidlStatus(status, "gnssConfigurationAidlIface setLppProfile() failed.");
-    }
-
-    auto result = gnssConfigurationIface->setLppProfile(lppProfile);
-    return checkHidlReturn(result, "IGnssConfiguration setLppProfile() failed.");
+    return gnssConfigurationIface->setLppProfile(lppProfile);
 }
 
 static jboolean android_location_GnssConfiguration_set_gnss_pos_protocol_select(JNIEnv*,
                                                                             jobject,
                                                                             jint gnssPosProtocol) {
-    if (gnssConfigurationIface == nullptr && gnssConfigurationAidlIface == nullptr) {
+    if (gnssConfigurationIface == nullptr) {
         ALOGE("%s: IGnssConfiguration interface not available.", __func__);
         return JNI_FALSE;
     }
-
-    if (gnssConfigurationAidlIface != nullptr) {
-        auto status = gnssConfigurationAidlIface->setGlonassPositioningProtocol(gnssPosProtocol);
-        return checkAidlStatus(status,
-                               "gnssConfigurationAidlIface setGlonassPositioningProtocol() "
-                               "failed.");
-    }
-
-    auto result = gnssConfigurationIface->setGlonassPositioningProtocol(gnssPosProtocol);
-    return checkHidlReturn(result, "IGnssConfiguration setGlonassPositioningProtocol() failed.");
+    return gnssConfigurationIface->setGlonassPositioningProtocol(gnssPosProtocol);
 }
 
 static jboolean android_location_GnssConfiguration_set_satellite_blacklist(
         JNIEnv* env, jobject, jintArray constellations, jintArray sv_ids) {
-    if (gnssConfigurationIface_V1_1 == nullptr && gnssConfigurationIface_V2_1 == nullptr &&
-        gnssConfigurationAidlIface == nullptr) {
+    if (gnssConfigurationIface == nullptr) {
         ALOGI("IGnssConfiguration interface does not support satellite blacklist.");
         return JNI_FALSE;
     }
-
-    jint *constellation_array = env->GetIntArrayElements(constellations, 0);
-    if (nullptr == constellation_array) {
-        ALOGI("GetIntArrayElements returns nullptr.");
-        return JNI_FALSE;
-    }
-    jsize length = env->GetArrayLength(constellations);
-
-    jint *sv_id_array = env->GetIntArrayElements(sv_ids, 0);
-    if (nullptr == sv_id_array) {
-        ALOGI("GetIntArrayElements returns nullptr.");
-        return JNI_FALSE;
-    }
-
-    if (length != env->GetArrayLength(sv_ids)) {
-        ALOGI("Lengths of constellations and sv_ids are inconsistent.");
-        return JNI_FALSE;
-    }
-
-    if (gnssConfigurationAidlIface != nullptr) {
-        std::vector<BlocklistedSource> sources;
-        sources.resize(length);
-
-        for (int i = 0; i < length; i++) {
-            sources[i].constellation = static_cast<GnssConstellationType>(constellation_array[i]);
-            sources[i].svid = sv_id_array[i];
-        }
-
-        auto status = gnssConfigurationAidlIface->setBlocklist(sources);
-        return checkAidlStatus(status, "gnssConfigurationAidlIface setBlocklist() failed.");
-    } else if (gnssConfigurationIface_V2_1 != nullptr) {
-        hidl_vec<IGnssConfiguration_V2_1::BlacklistedSource> sources;
-        sources.resize(length);
-
-        for (int i = 0; i < length; i++) {
-            sources[i].constellation = static_cast<GnssConstellationType_V2_0>(constellation_array[i]);
-            sources[i].svid = sv_id_array[i];
-        }
-
-        auto result = gnssConfigurationIface_V2_1->setBlacklist_2_1(sources);
-        return checkHidlReturn(result, "IGnssConfiguration_V2_1 setBlacklist_2_1() failed.");
-    }
-
-    hidl_vec<IGnssConfiguration_V1_1::BlacklistedSource> sources;
-    sources.resize(length);
-
-    for (int i = 0; i < length; i++) {
-        sources[i].constellation = static_cast<GnssConstellationType_V1_0>(constellation_array[i]);
-        sources[i].svid = sv_id_array[i];
-    }
-
-    auto result = gnssConfigurationIface_V1_1->setBlacklist(sources);
-    return checkHidlReturn(result, "IGnssConfiguration setBlacklist() failed.");
+    return gnssConfigurationIface->setBlocklist(env, constellations, sv_ids);
 }
 
 static jboolean android_location_GnssConfiguration_set_es_extension_sec(
         JNIEnv*, jobject, jint emergencyExtensionSeconds) {
-    if (gnssConfigurationIface_V2_0 == nullptr && gnssConfigurationAidlIface == nullptr) {
-        ALOGI("Config parameter ES_EXTENSION_SEC is not supported in IGnssConfiguration.hal"
-                " versions earlier than 2.0.");
+    if (gnssConfigurationIface == nullptr) {
+        ALOGE("%s: IGnssConfiguration interface not available.", __func__);
         return JNI_FALSE;
     }
-
-    if (gnssConfigurationAidlIface != nullptr) {
-        auto status = gnssConfigurationAidlIface->setEsExtensionSec(emergencyExtensionSeconds);
-        return checkAidlStatus(status, "gnssConfigurationAidlIface setEsExtensionSec() failed.");
-    }
-
-    auto result = gnssConfigurationIface_V2_0->setEsExtensionSec(emergencyExtensionSeconds);
-    return checkHidlReturn(result, "IGnssConfiguration setEsExtensionSec() failed.");
+    return gnssConfigurationIface->setEsExtensionSec(emergencyExtensionSeconds);
 }
 
 static jint android_location_GnssBatchingProvider_get_batch_size(JNIEnv*, jclass) {
diff --git a/services/core/jni/gnss/GnssConfiguration.cpp b/services/core/jni/gnss/GnssConfiguration.cpp
new file mode 100644
index 0000000..8610c75
--- /dev/null
+++ b/services/core/jni/gnss/GnssConfiguration.cpp
@@ -0,0 +1,264 @@
+/*
+ * 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.
+ */
+
+// Define LOG_TAG before <log/log.h> to overwrite the default value.
+#define LOG_TAG "GnssConfigurationJni"
+
+#include "GnssConfiguration.h"
+
+using android::hardware::gnss::GnssConstellationType;
+using GnssConstellationType_V1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
+using GnssConstellationType_V2_0 = android::hardware::gnss::V2_0::GnssConstellationType;
+
+using android::binder::Status;
+using android::hardware::hidl_vec;
+using android::hardware::Return;
+
+using android::hardware::gnss::IGnssConfiguration;
+using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration;
+using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration;
+using IGnssConfiguration_V2_0 = android::hardware::gnss::V2_0::IGnssConfiguration;
+using IGnssConfiguration_V2_1 = android::hardware::gnss::V2_1::IGnssConfiguration;
+
+using android::hardware::gnss::BlocklistedSource;
+using BlocklistedSource_V1_1 = IGnssConfiguration_V1_1::BlacklistedSource;
+using BlocklistedSource_V2_1 = IGnssConfiguration_V2_1::BlacklistedSource;
+
+namespace {
+
+jclass class_gnssConfiguration_halInterfaceVersion;
+jmethodID method_halInterfaceVersionCtor;
+
+jboolean checkAidlStatus(const Status& status, const char* errorMessage) {
+    if (!status.isOk()) {
+        ALOGE("%s AIDL transport error: %s", errorMessage, status.toString8().c_str());
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+template <class T>
+inline void logHidlError(Return<T>& result, const char* errorMessage) {
+    ALOGE("%s HIDL transport error: %s", errorMessage, result.description().c_str());
+}
+
+template <class T>
+jboolean checkHidlReturn(Return<T>& result, const char* errorMessage) {
+    if (!result.isOk()) {
+        logHidlError(result, errorMessage);
+        return JNI_FALSE;
+    } else {
+        return JNI_TRUE;
+    }
+}
+
+jobject createHalInterfaceVersionJavaObject(JNIEnv* env, jint major, jint minor) {
+    return env->NewObject(class_gnssConfiguration_halInterfaceVersion,
+                          method_halInterfaceVersionCtor, major, minor);
+}
+
+} // anonymous namespace
+
+namespace android::gnss {
+
+void GnssConfiguration_class_init_once(JNIEnv* env) {
+    jclass gnssConfiguration_halInterfaceVersionClass = env->FindClass(
+            "com/android/server/location/gnss/GnssConfiguration$HalInterfaceVersion");
+    class_gnssConfiguration_halInterfaceVersion =
+            (jclass)env->NewGlobalRef(gnssConfiguration_halInterfaceVersionClass);
+    method_halInterfaceVersionCtor =
+            env->GetMethodID(class_gnssConfiguration_halInterfaceVersion, "<init>", "(II)V");
+}
+
+// Implementation of GnssConfiguration (AIDL HAL)
+
+GnssConfiguration::GnssConfiguration(const sp<IGnssConfiguration>& iGnssConfiguration)
+      : mIGnssConfiguration(iGnssConfiguration) {}
+
+jobject GnssConfiguration::getVersion(JNIEnv* env) {
+    return createHalInterfaceVersionJavaObject(env, 3, 0);
+}
+
+jboolean GnssConfiguration::setEmergencySuplPdn(jint enable) {
+    auto status = mIGnssConfiguration->setEmergencySuplPdn(enable);
+    return checkAidlStatus(status, "IGnssConfiguration setEmergencySuplPdn() failed.");
+}
+
+jboolean GnssConfiguration::setSuplVersion(jint version) {
+    auto status = mIGnssConfiguration->setSuplVersion(version);
+    return checkAidlStatus(status, "IGnssConfiguration setSuplVersion() failed.");
+}
+
+jboolean GnssConfiguration::setSuplEs(jint enable) {
+    ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration AIDL HAL.");
+    return JNI_FALSE;
+}
+
+jboolean GnssConfiguration::setSuplMode(jint mode) {
+    auto status = mIGnssConfiguration->setSuplMode(mode);
+    return checkAidlStatus(status, "IGnssConfiguration setSuplMode() failed.");
+}
+
+jboolean GnssConfiguration::setGpsLock(jint gpsLock) {
+    ALOGI("Config parameter GPS_LOCK is not supported in IGnssConfiguration AIDL HAL.");
+    return JNI_FALSE;
+}
+
+jboolean GnssConfiguration::setLppProfile(jint lppProfile) {
+    auto status = mIGnssConfiguration->setLppProfile(lppProfile);
+    return checkAidlStatus(status, "IGnssConfiguration setLppProfile() failed.");
+}
+
+jboolean GnssConfiguration::setGlonassPositioningProtocol(jint gnssPosProtocol) {
+    auto status = mIGnssConfiguration->setGlonassPositioningProtocol(gnssPosProtocol);
+    return checkAidlStatus(status, "IGnssConfiguration setGlonassPositioningProtocol() failed.");
+}
+
+jboolean GnssConfiguration::setEsExtensionSec(jint emergencyExtensionSeconds) {
+    auto status = mIGnssConfiguration->setEsExtensionSec(emergencyExtensionSeconds);
+    return checkAidlStatus(status, "IGnssConfiguration setEsExtensionSec() failed.");
+}
+
+jboolean GnssConfiguration::setBlocklist(JNIEnv* env, jintArray& constellations,
+                                         jintArray& sv_ids) {
+    auto sources =
+            getBlocklistedSources<BlocklistedSource, GnssConstellationType>(env, constellations,
+                                                                            sv_ids);
+    auto status = mIGnssConfiguration->setBlocklist(sources);
+    return checkAidlStatus(status, "IGnssConfiguration setBlocklist() failed.");
+}
+
+// Implementation of GnssConfiguration_V1_0
+
+GnssConfiguration_V1_0::GnssConfiguration_V1_0(
+        const sp<IGnssConfiguration_V1_0>& iGnssConfiguration)
+      : mIGnssConfiguration_V1_0(iGnssConfiguration) {}
+
+jobject GnssConfiguration_V1_0::getVersion(JNIEnv* env) {
+    return createHalInterfaceVersionJavaObject(env, 1, 0);
+}
+
+jboolean GnssConfiguration_V1_0::setEmergencySuplPdn(jint enable) {
+    auto result = mIGnssConfiguration_V1_0->setEmergencySuplPdn(enable);
+    return checkHidlReturn(result, "IGnssConfiguration setEmergencySuplPdn() failed.");
+}
+
+jboolean GnssConfiguration_V1_0::setSuplVersion(jint version) {
+    auto result = mIGnssConfiguration_V1_0->setSuplVersion(version);
+    return checkHidlReturn(result, "IGnssConfiguration setSuplVersion() failed.");
+}
+
+jboolean GnssConfiguration_V1_0::setSuplEs(jint enable) {
+    auto result = mIGnssConfiguration_V1_0->setSuplEs(enable);
+    return checkHidlReturn(result, "IGnssConfiguration setSuplEs() failed.");
+}
+
+jboolean GnssConfiguration_V1_0::setSuplMode(jint mode) {
+    auto result = mIGnssConfiguration_V1_0->setSuplMode(mode);
+    return checkHidlReturn(result, "IGnssConfiguration setSuplMode() failed.");
+}
+
+jboolean GnssConfiguration_V1_0::setGpsLock(jint gpsLock) {
+    auto result = mIGnssConfiguration_V1_0->setGpsLock(gpsLock);
+    return checkHidlReturn(result, "IGnssConfiguration setGpsLock() failed.");
+}
+
+jboolean GnssConfiguration_V1_0::setLppProfile(jint lppProfile) {
+    auto result = mIGnssConfiguration_V1_0->setLppProfile(lppProfile);
+    return checkHidlReturn(result, "IGnssConfiguration setLppProfile() failed.");
+}
+
+jboolean GnssConfiguration_V1_0::setGlonassPositioningProtocol(jint gnssPosProtocol) {
+    auto result = mIGnssConfiguration_V1_0->setGlonassPositioningProtocol(gnssPosProtocol);
+    return checkHidlReturn(result, "IGnssConfiguration setGlonassPositioningProtocol() failed.");
+}
+
+jboolean GnssConfiguration_V1_0::setEsExtensionSec(jint emergencyExtensionSeconds) {
+    ALOGI("Config parameter ES_EXTENSION_SEC is not supported in IGnssConfiguration.hal"
+          " versions earlier than 2.0.");
+    return JNI_FALSE;
+}
+
+jboolean GnssConfiguration_V1_0::setBlocklist(JNIEnv* env, jintArray& constellations,
+                                              jintArray& sv_ids) {
+    ALOGI("IGnssConfiguration interface does not support satellite blocklist.");
+    return JNI_FALSE;
+}
+
+// Implementation of GnssConfiguration_V1_1
+
+GnssConfiguration_V1_1::GnssConfiguration_V1_1(
+        const sp<IGnssConfiguration_V1_1>& iGnssConfiguration)
+      : GnssConfiguration_V1_0{iGnssConfiguration}, mIGnssConfiguration_V1_1(iGnssConfiguration) {}
+
+jobject GnssConfiguration_V1_1::getVersion(JNIEnv* env) {
+    return createHalInterfaceVersionJavaObject(env, 1, 1);
+}
+
+jboolean GnssConfiguration_V1_1::setBlocklist(JNIEnv* env, jintArray& constellations,
+                                              jintArray& sv_ids) {
+    auto sources = getBlocklistedSources<BlocklistedSource_V1_1,
+                                         GnssConstellationType_V1_0>(env, constellations, sv_ids);
+    auto result = mIGnssConfiguration_V1_1->setBlacklist(sources);
+    return checkHidlReturn(result, "IGnssConfiguration setBlocklist() failed.");
+}
+
+// Implementation of GnssConfiguration_V2_0
+
+GnssConfiguration_V2_0::GnssConfiguration_V2_0(
+        const sp<IGnssConfiguration_V2_0>& iGnssConfiguration)
+      : GnssConfiguration_V1_1{iGnssConfiguration}, mIGnssConfiguration_V2_0(iGnssConfiguration) {}
+
+jobject GnssConfiguration_V2_0::getVersion(JNIEnv* env) {
+    return createHalInterfaceVersionJavaObject(env, 2, 0);
+}
+
+jboolean GnssConfiguration_V2_0::setSuplEs(jint enable) {
+    ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0 and "
+          "higher.");
+    return JNI_FALSE;
+}
+
+jboolean GnssConfiguration_V2_0::setGpsLock(jint enable) {
+    ALOGI("Config parameter GPS_LOCK is deprecated in IGnssConfiguration.hal version 2.0 and "
+          "higher.");
+    return JNI_FALSE;
+}
+
+jboolean GnssConfiguration_V2_0::setEsExtensionSec(jint emergencyExtensionSeconds) {
+    auto result = mIGnssConfiguration_V2_0->setEsExtensionSec(emergencyExtensionSeconds);
+    return checkHidlReturn(result, "IGnssConfiguration setEsExtensionSec() failed.");
+}
+
+// Implementation of GnssConfiguration_V2_1
+
+GnssConfiguration_V2_1::GnssConfiguration_V2_1(
+        const sp<IGnssConfiguration_V2_1>& iGnssConfiguration)
+      : GnssConfiguration_V2_0{iGnssConfiguration}, mIGnssConfiguration_V2_1(iGnssConfiguration) {}
+
+jobject GnssConfiguration_V2_1::getVersion(JNIEnv* env) {
+    return createHalInterfaceVersionJavaObject(env, 2, 1);
+}
+
+jboolean GnssConfiguration_V2_1::setBlocklist(JNIEnv* env, jintArray& constellations,
+                                              jintArray& sv_ids) {
+    auto sources = getBlocklistedSources<BlocklistedSource_V2_1,
+                                         GnssConstellationType_V2_0>(env, constellations, sv_ids);
+    auto result = mIGnssConfiguration_V2_1->setBlacklist_2_1(sources);
+    return checkHidlReturn(result, "IGnssConfiguration setBlocklist() failed.");
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssConfiguration.h b/services/core/jni/gnss/GnssConfiguration.h
new file mode 100644
index 0000000..ea0f178
--- /dev/null
+++ b/services/core/jni/gnss/GnssConfiguration.h
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSCONFIGURATION_H
+#define _ANDROID_SERVER_GNSS_GNSSCONFIGURATION_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IGnssConfiguration.h>
+#include <android/hardware/gnss/1.1/IGnssConfiguration.h>
+#include <android/hardware/gnss/2.0/IGnssConfiguration.h>
+#include <android/hardware/gnss/2.1/IGnssConfiguration.h>
+#include <android/hardware/gnss/BnGnssConfiguration.h>
+#include <log/log.h>
+
+#include "jni.h"
+
+namespace android::gnss {
+
+void GnssConfiguration_class_init_once(JNIEnv* env);
+
+class GnssConfigurationInterface {
+public:
+    virtual ~GnssConfigurationInterface() {}
+    virtual jobject getVersion(JNIEnv* env) = 0;
+    virtual jboolean setEmergencySuplPdn(jint enable) = 0;
+    virtual jboolean setSuplVersion(jint version) = 0;
+    virtual jboolean setSuplEs(jint enable) = 0;
+    virtual jboolean setSuplMode(jint mode) = 0;
+    virtual jboolean setGpsLock(jint gpsLock) = 0;
+    virtual jboolean setLppProfile(jint lppProfile) = 0;
+    virtual jboolean setGlonassPositioningProtocol(jint gnssPosProtocol) = 0;
+    virtual jboolean setEsExtensionSec(jint emergencyExtensionSeconds) = 0;
+    virtual jboolean setBlocklist(JNIEnv* env, jintArray& constellations, jintArray& sv_ids) = 0;
+
+protected:
+    template <class T_BlocklistSource, class T_ConstellationType>
+    hardware::hidl_vec<T_BlocklistSource> getBlocklistedSources(JNIEnv* env,
+                                                                jintArray& constellations,
+                                                                jintArray& sv_ids) {
+        jint* constellation_array = env->GetIntArrayElements(constellations, 0);
+        if (nullptr == constellation_array) {
+            ALOGI("GetIntArrayElements returns nullptr.");
+            return JNI_FALSE;
+        }
+
+        jsize length = env->GetArrayLength(constellations);
+
+        jint* sv_id_array = env->GetIntArrayElements(sv_ids, 0);
+        if (nullptr == sv_id_array) {
+            ALOGI("GetIntArrayElements returns nullptr.");
+            return JNI_FALSE;
+        }
+
+        if (length != env->GetArrayLength(sv_ids)) {
+            ALOGI("Lengths of constellations and sv_ids are inconsistent.");
+            return JNI_FALSE;
+        }
+
+        hardware::hidl_vec<T_BlocklistSource> sources;
+        sources.resize(length);
+
+        for (int i = 0; i < length; i++) {
+            sources[i].constellation = static_cast<T_ConstellationType>(constellation_array[i]);
+            sources[i].svid = sv_id_array[i];
+        }
+
+        env->ReleaseIntArrayElements(constellations, constellation_array, 0);
+        env->ReleaseIntArrayElements(sv_ids, sv_id_array, 0);
+
+        return sources;
+    }
+};
+
+class GnssConfiguration : public GnssConfigurationInterface {
+public:
+    GnssConfiguration(const sp<android::hardware::gnss::IGnssConfiguration>& iGnssConfiguration);
+    jobject getVersion(JNIEnv* env) override;
+    jboolean setEmergencySuplPdn(jint enable) override;
+    jboolean setSuplVersion(jint version) override;
+    jboolean setSuplEs(jint enable) override;
+    jboolean setSuplMode(jint mode) override;
+    jboolean setGpsLock(jint gpsLock) override;
+    jboolean setLppProfile(jint lppProfile) override;
+    jboolean setGlonassPositioningProtocol(jint gnssPosProtocol) override;
+    jboolean setEsExtensionSec(jint emergencyExtensionSeconds) override;
+    jboolean setBlocklist(JNIEnv* env, jintArray& constellations, jintArray& sv_ids) override;
+
+private:
+    const sp<android::hardware::gnss::IGnssConfiguration> mIGnssConfiguration;
+};
+
+class GnssConfiguration_V1_0 : public GnssConfigurationInterface {
+public:
+    GnssConfiguration_V1_0(
+            const sp<android::hardware::gnss::V1_0::IGnssConfiguration>& iGnssConfiguration);
+    jobject getVersion(JNIEnv* env) override;
+    jboolean setEmergencySuplPdn(jint enable);
+    jboolean setSuplVersion(jint version) override;
+    jboolean setSuplEs(jint enable) override;
+    jboolean setSuplMode(jint mode) override;
+    jboolean setGpsLock(jint gpsLock) override;
+    jboolean setLppProfile(jint lppProfile);
+    jboolean setGlonassPositioningProtocol(jint gnssPosProtocol) override;
+    jboolean setEsExtensionSec(jint emergencyExtensionSeconds) override;
+    jboolean setBlocklist(JNIEnv* env, jintArray& constellations, jintArray& sv_ids) override;
+
+private:
+    const sp<android::hardware::gnss::V1_0::IGnssConfiguration> mIGnssConfiguration_V1_0;
+};
+
+class GnssConfiguration_V1_1 : public GnssConfiguration_V1_0 {
+public:
+    GnssConfiguration_V1_1(
+            const sp<android::hardware::gnss::V1_1::IGnssConfiguration>& iGnssConfiguration);
+
+    jobject getVersion(JNIEnv* env) override;
+
+    jboolean setBlocklist(JNIEnv* env, jintArray& constellations, jintArray& sv_ids) override;
+
+private:
+    const sp<android::hardware::gnss::V1_1::IGnssConfiguration> mIGnssConfiguration_V1_1;
+};
+
+class GnssConfiguration_V2_0 : public GnssConfiguration_V1_1 {
+public:
+    GnssConfiguration_V2_0(
+            const sp<android::hardware::gnss::V2_0::IGnssConfiguration>& iGnssConfiguration);
+    jobject getVersion(JNIEnv* env) override;
+    jboolean setSuplEs(jint enable) override;
+    jboolean setGpsLock(jint enable) override;
+    jboolean setEsExtensionSec(jint emergencyExtensionSeconds) override;
+
+private:
+    const sp<android::hardware::gnss::V2_0::IGnssConfiguration> mIGnssConfiguration_V2_0;
+};
+
+class GnssConfiguration_V2_1 : public GnssConfiguration_V2_0 {
+public:
+    GnssConfiguration_V2_1(
+            const sp<android::hardware::gnss::V2_1::IGnssConfiguration>& iGnssConfiguration);
+    jobject getVersion(JNIEnv* env) override;
+    jboolean setBlocklist(JNIEnv* env, jintArray& constellations, jintArray& sv_ids) override;
+
+private:
+    const sp<android::hardware::gnss::V2_1::IGnssConfiguration> mIGnssConfiguration_V2_1;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSCONFIGURATION_H
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 7c2556d..fd4e1dd 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -1626,6 +1626,14 @@
         private boolean mHasEverConnected;
 
         /**
+         * Boolean indicating if captive portal has never been detected on this network.
+         *
+         * This should be true by default, for newly created WifiConfigurations until a captive
+         * portal is detected.
+         */
+        private boolean mHasNeverDetectedCaptivePortal = true;
+
+        /**
          * set whether this network is visible in latest Qualified Network Selection
          * @param seen value set to candidate
          * @hide
@@ -1714,6 +1722,19 @@
             return mHasEverConnected;
         }
 
+        /**
+         * Set whether a captive portal has never been detected on this network.
+         * @hide
+         */
+        public void setHasNeverDetectedCaptivePortal(boolean value) {
+            mHasNeverDetectedCaptivePortal = value;
+        }
+
+        /** @hide */
+        public boolean hasNeverDetectedCaptivePortal() {
+            return mHasNeverDetectedCaptivePortal;
+        }
+
         /** @hide */
         public NetworkSelectionStatus() {
             // previously stored configs will not have this parameter, so we default to false.
@@ -1989,6 +2010,7 @@
             setCandidateScore(source.getCandidateScore());
             setConnectChoice(source.getConnectChoice());
             setHasEverConnected(source.hasEverConnected());
+            setHasNeverDetectedCaptivePortal(source.hasNeverDetectedCaptivePortal());
         }
 
         /** @hide */
@@ -2008,6 +2030,7 @@
                 dest.writeInt(CONNECT_CHOICE_NOT_EXISTS);
             }
             dest.writeInt(hasEverConnected() ? 1 : 0);
+            dest.writeInt(hasNeverDetectedCaptivePortal() ? 1 : 0);
         }
 
         /** @hide */
@@ -2026,6 +2049,7 @@
                 setConnectChoice(null);
             }
             setHasEverConnected(in.readInt() != 0);
+            setHasNeverDetectedCaptivePortal(in.readInt() != 0);
         }
     }
 
@@ -2287,6 +2311,8 @@
         }
         sbuf.append(" hasEverConnected: ")
                 .append(mNetworkSelectionStatus.hasEverConnected()).append("\n");
+        sbuf.append(" hasNeverDetectedCaptivePortal: ")
+                .append(mNetworkSelectionStatus.hasNeverDetectedCaptivePortal()).append("\n");
 
         if (this.numAssociation > 0) {
             sbuf.append(" numAssociation ").append(this.numAssociation).append("\n");