Merge "Frameworks: Make MockUtils more robust" into pi-dev
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 133f33b..49de1ac 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -31,9 +31,8 @@
 namespace os {
 namespace statsd {
 
-// TODO: Get rid of bucketNumbers, and return to the original circular array method.
 AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
-    : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) {
+        : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) {
     VLOG("AnomalyTracker() called");
     if (mAlert.num_buckets() <= 0) {
         ALOGE("Cannot create AnomalyTracker with %lld buckets", (long long)mAlert.num_buckets());
@@ -60,85 +59,102 @@
 
 size_t AnomalyTracker::index(int64_t bucketNum) const {
     if (bucketNum < 0) {
-        // To support this use-case, we can easily modify index to wrap around. But currently
-        // AnomalyTracker should never need this, so if it happens, it's a bug we should log.
-        // TODO: Audit this.
         ALOGE("index() was passed a negative bucket number (%lld)!", (long long)bucketNum);
     }
     return bucketNum % mNumOfPastBuckets;
 }
 
-void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
-    VLOG("addPastBucket() called.");
-    if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
-        ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
+void AnomalyTracker::advanceMostRecentBucketTo(const int64_t& bucketNum) {
+    VLOG("advanceMostRecentBucketTo() called.");
+    if (bucketNum <= mMostRecentBucketNum) {
+        ALOGW("Cannot advance buckets backwards (bucketNum=%lld but mMostRecentBucketNum=%lld)",
+              (long long)bucketNum, (long long)mMostRecentBucketNum);
         return;
     }
-
-    // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
-    // mSumOverPastBuckets.
-    if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastBuckets) {
+    // If in the future (i.e. buckets are ancient), just empty out all past info.
+    if (bucketNum >= mMostRecentBucketNum + mNumOfPastBuckets) {
         resetStorage();
-    } else {
-        for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastBuckets + 1));
-             i <= latestPastBucketNum - mNumOfPastBuckets; i++) {
-            const int idx = index(i);
-            subtractBucketFromSum(mPastBuckets[idx]);
-            mPastBuckets[idx] = nullptr;  // release (but not clear) the old bucket.
+        mMostRecentBucketNum = bucketNum;
+        return;
+    }
+
+    // Clear out space by emptying out old mPastBuckets[i] values and update mSumOverPastBuckets.
+    for (int64_t i = mMostRecentBucketNum + 1; i <= bucketNum; i++) {
+        const int idx = index(i);
+        subtractBucketFromSum(mPastBuckets[idx]);
+        mPastBuckets[idx] = nullptr;  // release (but not clear) the old bucket.
+    }
+    mMostRecentBucketNum = bucketNum;
+}
+
+void AnomalyTracker::addPastBucket(const MetricDimensionKey& key,
+                                   const int64_t& bucketValue,
+                                   const int64_t& bucketNum) {
+    VLOG("addPastBucket(bucketValue) called.");
+    if (mNumOfPastBuckets == 0 ||
+        bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
+        return;
+    }
+
+    const int bucketIndex = index(bucketNum);
+    if (bucketNum <= mMostRecentBucketNum && (mPastBuckets[bucketIndex] != nullptr)) {
+        // We need to insert into an already existing past bucket.
+        std::shared_ptr<DimToValMap>& bucket = mPastBuckets[bucketIndex];
+        auto itr = bucket->find(key);
+        if (itr != bucket->end()) {
+            // Old entry already exists; update it.
+            subtractValueFromSum(key, itr->second);
+            itr->second = bucketValue;
+        } else {
+            bucket->insert({key, bucketValue});
         }
-    }
-
-    // It is an update operation.
-    if (latestPastBucketNum <= mMostRecentBucketNum &&
-        latestPastBucketNum > mMostRecentBucketNum - mNumOfPastBuckets) {
-        subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
+        mSumOverPastBuckets[key] += bucketValue;
+    } else {
+        // Bucket does not exist yet (in future or was never made), so we must make it.
+        std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
+        bucket->insert({key, bucketValue});
+        addPastBucket(bucket, bucketNum);
     }
 }
 
-void AnomalyTracker::addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue,
+void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucket,
                                    const int64_t& bucketNum) {
-    if (mNumOfPastBuckets == 0) {
+    VLOG("addPastBucket(bucket) called.");
+    if (mNumOfPastBuckets == 0 ||
+            bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
         return;
     }
-    flushPastBuckets(bucketNum);
 
-    auto& bucket = mPastBuckets[index(bucketNum)];
-    if (bucket == nullptr) {
-        bucket = std::make_shared<DimToValMap>();
+    if (bucketNum <= mMostRecentBucketNum) {
+        // We are updating an old bucket, not adding a new one.
+        subtractBucketFromSum(mPastBuckets[index(bucketNum)]);
+    } else {
+        // Clear space for the new bucket to be at bucketNum.
+        advanceMostRecentBucketTo(bucketNum);
     }
-    bucket->insert({key, bucketValue});
+    mPastBuckets[index(bucketNum)] = bucket;
     addBucketToSum(bucket);
-    mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
-}
-
-void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucketValues,
-                                   const int64_t& bucketNum) {
-    VLOG("addPastBucket() called.");
-    if (mNumOfPastBuckets == 0) {
-        return;
-    }
-    flushPastBuckets(bucketNum);
-    // Replace the oldest bucket with the new bucket we are adding.
-    mPastBuckets[index(bucketNum)] = bucketValues;
-    addBucketToSum(bucketValues);
-    mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
 }
 
 void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
     if (bucket == nullptr) {
         return;
     }
-    // For each dimension present in the bucket, subtract its value from its corresponding sum.
     for (const auto& keyValuePair : *bucket) {
-        auto itr = mSumOverPastBuckets.find(keyValuePair.first);
-        if (itr == mSumOverPastBuckets.end()) {
-            continue;
-        }
-        itr->second -= keyValuePair.second;
-        // TODO: No need to look up the object twice like this. Use a var.
-        if (itr->second == 0) {
-            mSumOverPastBuckets.erase(itr);
-        }
+        subtractValueFromSum(keyValuePair.first, keyValuePair.second);
+    }
+}
+
+
+void AnomalyTracker::subtractValueFromSum(const MetricDimensionKey& key,
+                                          const int64_t& bucketValue) {
+    auto itr = mSumOverPastBuckets.find(key);
+    if (itr == mSumOverPastBuckets.end()) {
+        return;
+    }
+    itr->second -= bucketValue;
+    if (itr->second == 0) {
+        mSumOverPastBuckets.erase(itr);
     }
 }
 
@@ -154,7 +170,8 @@
 
 int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key,
                                            const int64_t& bucketNum) const {
-    if (mNumOfPastBuckets == 0 || bucketNum < 0) {
+    if (bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets
+            || bucketNum > mMostRecentBucketNum) {
         return 0;
     }
 
@@ -174,11 +191,13 @@
     return 0;
 }
 
-bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const MetricDimensionKey& key,
+bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
+                                   const MetricDimensionKey& key,
                                    const int64_t& currentBucketValue) {
+
+    // currentBucketNum should be the next bucket after pastBuckets. If not, advance so that it is.
     if (currentBucketNum > mMostRecentBucketNum + 1) {
-        // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this.
-        addPastBucket(key, 0, currentBucketNum - 1);
+        advanceMostRecentBucketTo(currentBucketNum - 1);
     }
     return mAlert.has_trigger_if_sum_gt() &&
            getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
@@ -190,19 +209,17 @@
         VLOG("Skipping anomaly declaration since within refractory period");
         return;
     }
-    mRefractoryPeriodEndsSec[key] = (timestampNs / NS_PER_SEC) + mAlert.refractory_period_secs();
-
-    // TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
-    // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); }
+    if (mAlert.has_refractory_period_secs()) {
+        mRefractoryPeriodEndsSec[key] = ((timestampNs + NS_PER_SEC - 1) / NS_PER_SEC) // round up
+                                        + mAlert.refractory_period_secs();
+        // TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
+        // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) {resetStorage();}
+    }
 
     if (!mSubscriptions.empty()) {
-        if (mAlert.has_id()) {
-            ALOGI("An anomaly (%lld) %s has occurred! Informing subscribers.", mAlert.id(),
-                  key.toString().c_str());
-            informSubscribers(key);
-        } else {
-            ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers.");
-        }
+        ALOGI("An anomaly (%lld) %s has occurred! Informing subscribers.",
+                mAlert.id(), key.toString().c_str());
+        informSubscribers(key);
     } else {
         ALOGI("An anomaly has occurred! (But no subscriber for that alert.)");
     }
@@ -227,7 +244,7 @@
                                           const MetricDimensionKey& key) {
     const auto& it = mRefractoryPeriodEndsSec.find(key);
     if (it != mRefractoryPeriodEndsSec.end()) {
-        if ((timestampNs / NS_PER_SEC) <= it->second) {
+        if (timestampNs < it->second * NS_PER_SEC) {
             return true;
         } else {
             mRefractoryPeriodEndsSec.erase(key);
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index d27dee8..d3da7dc 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -47,20 +47,32 @@
         mSubscriptions.push_back(subscription);
     }
 
-    // Adds a bucket.
-    // Bucket index starts from 0.
-    void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum);
+    // Adds a bucket for the given bucketNum (index starting at 0).
+    // If a bucket for bucketNum already exists, it will be replaced.
+    // Also, advances to bucketNum (if not in the past), effectively filling any intervening
+    // buckets with 0s.
+    void addPastBucket(std::shared_ptr<DimToValMap> bucket, const int64_t& bucketNum);
+
+    // Inserts (or replaces) the bucket entry for the given bucketNum at the given key to be the
+    // given bucketValue. If the bucket does not exist, it will be created.
+    // Also, advances to bucketNum (if not in the past), effectively filling any intervening
+    // buckets with 0s.
     void addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue,
                        const int64_t& bucketNum);
 
-    // Returns true if detected anomaly for the existing buckets on one or more dimension keys.
+    // Returns true if, based on past buckets plus the new currentBucketValue (which generally
+    // represents the partially-filled current bucket), an anomaly has happened.
+    // Also advances to currBucketNum-1.
     bool detectAnomaly(const int64_t& currBucketNum, const MetricDimensionKey& key,
                        const int64_t& currentBucketValue);
 
     // Informs incidentd about the detected alert.
     void declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key);
 
-    // Detects the alert and informs the incidentd when applicable.
+    // Detects if, based on past buckets plus the new currentBucketValue (which generally
+    // represents the partially-filled current bucket), an anomaly has happened, and if so,
+    // declares an anomaly and informs relevant subscribers.
+    // Also advances to currBucketNum-1.
     void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
                                  const MetricDimensionKey& key, const int64_t& currentBucketValue);
 
@@ -69,24 +81,26 @@
         return; // Base AnomalyTracker class has no need for the AlarmMonitor.
     }
 
-    // Helper function to return the sum value of past buckets at given dimension.
+    // Returns the sum of all past bucket values for the given dimension key.
     int64_t getSumOverPastBuckets(const MetricDimensionKey& key) const;
 
-    // Helper function to return the value for a past bucket.
+    // Returns the value for a past bucket, or 0 if that bucket doesn't exist.
     int64_t getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const;
 
-    // Returns the anomaly threshold.
+    // Returns the anomaly threshold set in the configuration.
     inline int64_t getAnomalyThreshold() const {
         return mAlert.trigger_if_sum_gt();
     }
 
-    // Returns the refractory period timestamp (in seconds) for the given key.
+    // Returns the refractory period ending timestamp (in seconds) for the given key.
+    // Before this moment, any detected anomaly will be ignored.
     // If there is no stored refractory period ending timestamp, returns 0.
     uint32_t getRefractoryPeriodEndsSec(const MetricDimensionKey& key) const {
         const auto& it = mRefractoryPeriodEndsSec.find(key);
         return it != mRefractoryPeriodEndsSec.end() ? it->second : 0;
     }
 
+    // Returns the (constant) number of past buckets this anomaly tracker can store.
     inline int getNumOfPastBuckets() const {
         return mNumOfPastBuckets;
     }
@@ -112,22 +126,27 @@
     // for the anomaly detection (since the current bucket is not in the past).
     const int mNumOfPastBuckets;
 
-    // The existing bucket list.
+    // Values for each of the past mNumOfPastBuckets buckets. Always of size mNumOfPastBuckets.
+    // mPastBuckets[i] can be null, meaning that no data is present in that bucket.
     std::vector<shared_ptr<DimToValMap>> mPastBuckets;
 
-    // Sum over all existing buckets cached in mPastBuckets.
+    // Cached sum over all existing buckets in mPastBuckets.
+    // Its buckets never contain entries of 0.
     DimToValMap mSumOverPastBuckets;
 
     // The bucket number of the last added bucket.
     int64_t mMostRecentBucketNum = -1;
 
     // Map from each dimension to the timestamp that its refractory period (if this anomaly was
-    // declared for that dimension) ends, in seconds. Only anomalies that occur after this period
-    // ends will be declared.
+    // declared for that dimension) ends, in seconds. From this moment and onwards, anomalies
+    // can be declared again.
     // Entries may be, but are not guaranteed to be, removed after the period is finished.
     unordered_map<MetricDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
 
-    void flushPastBuckets(const int64_t& currBucketNum);
+    // Advances mMostRecentBucketNum to bucketNum, deleting any data that is now too old.
+    // Specifically, since it is now too old, removes the data for
+    //   [mMostRecentBucketNum - mNumOfPastBuckets + 1, bucketNum - mNumOfPastBuckets].
+    void advanceMostRecentBucketTo(const int64_t& bucketNum);
 
     // Add the information in the given bucket to mSumOverPastBuckets.
     void addBucketToSum(const shared_ptr<DimToValMap>& bucket);
@@ -136,15 +155,21 @@
     // and remove any items with value 0.
     void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
 
+    // From mSumOverPastBuckets[key], subtracts bucketValue, removing it if it is now 0.
+    void subtractValueFromSum(const MetricDimensionKey& key, const int64_t& bucketValue);
+
+    // Returns true if in the refractory period, else false.
+    // If there is a stored refractory period but it ended prior to timestampNs, it is removed.
     bool isInRefractoryPeriod(const uint64_t& timestampNs, const MetricDimensionKey& key);
 
     // Calculates the corresponding bucket index within the circular array.
+    // Requires bucketNum >= 0.
     size_t index(int64_t bucketNum) const;
 
     // Resets all bucket data. For use when all the data gets stale.
     virtual void resetStorage();
 
-    // Informs the subscribers that an anomaly has occurred.
+    // Informs the subscribers (incidentd, perfetto, broadcasts, etc) that an anomaly has occurred.
     void informSubscribers(const MetricDimensionKey& key);
 
     FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
index 31d50be..d85157c 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
@@ -26,30 +26,13 @@
 
 DurationAnomalyTracker::DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey,
                                                const sp<AlarmMonitor>& alarmMonitor)
-    : AnomalyTracker(alert, configKey), mAlarmMonitor(alarmMonitor) {
+        : AnomalyTracker(alert, configKey), mAlarmMonitor(alarmMonitor) {
+    VLOG("DurationAnomalyTracker() called");
 }
 
 DurationAnomalyTracker::~DurationAnomalyTracker() {
-    stopAllAlarms();
-}
-
-void DurationAnomalyTracker::resetStorage() {
-    AnomalyTracker::resetStorage();
-    if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!");
-}
-
-void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const MetricDimensionKey& dimensionKey,
-                                                          const uint64_t& timestampNs) {
-    auto itr = mAlarms.find(dimensionKey);
-    if (itr == mAlarms.end()) {
-        return;
-    }
-
-    if (itr->second != nullptr &&
-        static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) {
-        declareAnomaly(timestampNs, dimensionKey);
-        stopAlarm(dimensionKey);
-    }
+    VLOG("~DurationAnomalyTracker() called");
+    cancelAllAlarms();
 }
 
 void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey,
@@ -57,34 +40,47 @@
     // Alarms are stored in secs. Must round up, since if it fires early, it is ignored completely.
     uint32_t timestampSec = static_cast<uint32_t>((timestampNs -1)/ NS_PER_SEC) + 1; // round up
     if (isInRefractoryPeriod(timestampNs, dimensionKey)) {
+        // TODO: Bug! By the refractory's end, the data might be erased and the alarm inapplicable.
         VLOG("Setting a delayed anomaly alarm lest it fall in the refractory period");
         timestampSec = getRefractoryPeriodEndsSec(dimensionKey) + 1;
     }
+
+    auto itr = mAlarms.find(dimensionKey);
+    if (itr != mAlarms.end() && mAlarmMonitor != nullptr) {
+        mAlarmMonitor->remove(itr->second);
+    }
+
     sp<const InternalAlarm> alarm = new InternalAlarm{timestampSec};
-    mAlarms.insert({dimensionKey, alarm});
+    mAlarms[dimensionKey] = alarm;
     if (mAlarmMonitor != nullptr) {
         mAlarmMonitor->add(alarm);
     }
 }
 
-void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey) {
-    auto itr = mAlarms.find(dimensionKey);
-    if (itr != mAlarms.end()) {
-        mAlarms.erase(dimensionKey);
-        if (mAlarmMonitor != nullptr) {
-            mAlarmMonitor->remove(itr->second);
-        }
+void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey,
+                                       const uint64_t& timestampNs) {
+    const auto itr = mAlarms.find(dimensionKey);
+    if (itr == mAlarms.end()) {
+        return;
+    }
+
+    // If the alarm is set in the past but hasn't fired yet (due to lag), catch it now.
+    if (itr->second != nullptr && timestampNs >= NS_PER_SEC * itr->second->timestampSec) {
+        declareAnomaly(timestampNs, dimensionKey);
+    }
+    mAlarms.erase(dimensionKey);
+    if (mAlarmMonitor != nullptr) {
+        mAlarmMonitor->remove(itr->second);
     }
 }
 
-void DurationAnomalyTracker::stopAllAlarms() {
-    std::set<MetricDimensionKey> keys;
-    for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
-        keys.insert(itr->first);
+void DurationAnomalyTracker::cancelAllAlarms() {
+    if (mAlarmMonitor != nullptr) {
+        for (const auto& itr : mAlarms) {
+            mAlarmMonitor->remove(itr.second);
+        }
     }
-    for (auto key : keys) {
-        stopAlarm(key);
-    }
+    mAlarms.clear();
 }
 
 void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
index 51186df..92bb2bc 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -32,21 +32,20 @@
 
     virtual ~DurationAnomalyTracker();
 
-    // Starts the alarm at the given timestamp.
+    // Sets an alarm for the given timestamp.
+    // Replaces previous alarm if one already exists.
     void startAlarm(const MetricDimensionKey& dimensionKey, const uint64_t& eventTime);
 
     // Stops the alarm.
-    void stopAlarm(const MetricDimensionKey& dimensionKey);
+    // If it should have already fired, but hasn't yet (e.g. because the AlarmManager is delayed),
+    // declare the anomaly now.
+    void stopAlarm(const MetricDimensionKey& dimensionKey, const uint64_t& timestampNs);
 
-    // Stop all the alarms owned by this tracker.
-    void stopAllAlarms();
-
-    // Declares the anomaly when the alarm expired given the current timestamp.
-    void declareAnomalyIfAlarmExpired(const MetricDimensionKey& dimensionKey,
-                                      const uint64_t& timestampNs);
+    // Stop all the alarms owned by this tracker. Does not declare any anomalies.
+    void cancelAllAlarms();
 
     // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker
-    // and removes it from firedAlarms.
+    // and removes it from firedAlarms. The AlarmMonitor is not informed.
     // Note that this will generally be called from a different thread from the other functions;
     // the caller is responsible for thread safety.
     void informAlarmsFired(const uint64_t& timestampNs,
@@ -60,9 +59,6 @@
     // Anomaly alarm monitor.
     sp<AlarmMonitor> mAlarmMonitor;
 
-    // Resets all bucket data. For use when all the data gets stale.
-    void resetStorage() override;
-
     FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
     FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm);
     FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 298f494..31ca13a 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -115,7 +115,6 @@
         HardwareFailed hardware_failed = 72;
         PhysicalDropDetected physical_drop_detected = 73;
         ChargeCyclesReported charge_cycles_reported = 74;
-        // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 
     // Pulled events will start at field 10000.
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index bfb1ec7..991a76a 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -128,11 +128,11 @@
         }
     }
 
-    // Stops the anomaly alarm.
-    void stopAnomalyAlarm() {
+    // Stops the anomaly alarm. If it should have already fired, declare the anomaly now.
+    void stopAnomalyAlarm(const uint64_t timestamp) {
         for (auto& anomalyTracker : mAnomalyTrackers) {
             if (anomalyTracker != nullptr) {
-                anomalyTracker->stopAlarm(mEventKey);
+                anomalyTracker->stopAlarm(mEventKey, timestamp);
             }
         }
     }
@@ -155,14 +155,6 @@
         }
     }
 
-    void declareAnomalyIfAlarmExpired(const uint64_t& timestamp) {
-        for (auto& anomalyTracker : mAnomalyTrackers) {
-            if (anomalyTracker != nullptr) {
-                anomalyTracker->declareAnomalyIfAlarmExpired(mEventKey, timestamp);
-            }
-        }
-    }
-
     // Convenience to compute the current bucket's end time, which is always aligned with the
     // start time of the metric.
     uint64_t getCurrentBucketEndTimeNs() {
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 058940d..335ec4c 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -130,7 +130,7 @@
         case DurationState::kStarted: {
             duration.startCount--;
             if (forceStop || !mNested || duration.startCount <= 0) {
-                stopAnomalyAlarm();
+                stopAnomalyAlarm(eventTime);
                 duration.state = DurationState::kStopped;
                 int64_t durationTime = eventTime - duration.lastStartTime;
                 VLOG("Max, key %s, Stop %lld %lld %lld", key.toString().c_str(),
@@ -284,7 +284,7 @@
             // If condition becomes false, kStarted -> kPaused. Record the current duration and
             // stop anomaly alarm.
             if (!conditionMet) {
-                stopAnomalyAlarm();
+                stopAnomalyAlarm(timestamp);
                 it->second.state = DurationState::kPaused;
                 it->second.lastDuration += (timestamp - it->second.lastStartTime);
                 if (anyStarted()) {
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index c8a5016..b418a85 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -92,7 +92,6 @@
 
 void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp,
                                     const bool stopAll) {
-    declareAnomalyIfAlarmExpired(timestamp);
     VLOG("Oring: %s stop", key.toString().c_str());
     auto it = mStarted.find(key);
     if (it != mStarted.end()) {
@@ -118,12 +117,11 @@
         }
     }
     if (mStarted.empty()) {
-        stopAnomalyAlarm();
+        stopAnomalyAlarm(timestamp);
     }
 }
 
 void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
-    declareAnomalyIfAlarmExpired(timestamp);
     if (!mStarted.empty()) {
         mDuration += (timestamp - mLastStartTime);
         VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
@@ -131,7 +129,7 @@
         detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
     }
 
-    stopAnomalyAlarm();
+    stopAnomalyAlarm(timestamp);
     mStarted.clear();
     mPaused.clear();
     mConditionKeyMap.clear();
@@ -213,7 +211,6 @@
 }
 
 void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
-    declareAnomalyIfAlarmExpired(timestamp);
     vector<pair<HashableDimensionKey, int>> startedToPaused;
     vector<pair<HashableDimensionKey, int>> pausedToStarted;
     if (!mStarted.empty()) {
@@ -291,12 +288,11 @@
     mPaused.insert(startedToPaused.begin(), startedToPaused.end());
 
     if (mStarted.empty()) {
-        stopAnomalyAlarm();
+        stopAnomalyAlarm(timestamp);
     }
 }
 
 void OringDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
-    declareAnomalyIfAlarmExpired(timestamp);
     if (condition) {
         if (!mPaused.empty()) {
             VLOG("Condition true, all started");
@@ -319,7 +315,7 @@
         }
     }
     if (mStarted.empty()) {
-        stopAnomalyAlarm();
+        stopAnomalyAlarm(timestamp);
     }
 }
 
@@ -328,12 +324,16 @@
     // TODO: Unit-test this and see if it can be done more efficiently (e.g. use int32).
     // All variables below represent durations (not timestamps).
 
+    const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold();
+
     // The time until the current bucket ends. This is how much more 'space' it can hold.
     const int64_t currRemainingBucketSizeNs =
             mBucketSizeNs - (eventTimestampNs - mCurrentBucketStartTimeNs);
-    // TODO: This should never be < 0. Document/guard against possible failures if it is.
-
-    const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold();
+    if (currRemainingBucketSizeNs < 0) {
+        ALOGE("OringDurationTracker currRemainingBucketSizeNs < 0");
+        // This should never happen. Return the safest thing possible given that data is corrupt.
+        return eventTimestampNs + thresholdNs;
+    }
 
     // As we move into the future, old buckets get overwritten (so their old data is erased).
 
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index b5afef2..901f500 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -562,6 +562,10 @@
                   (long long)alert.metric_id());
             return false;
         }
+        if (!alert.has_trigger_if_sum_gt()) {
+            ALOGW("invalid alert: missing threshold");
+            return false;
+        }
         if (alert.trigger_if_sum_gt() < 0 || alert.num_buckets() <= 0) {
             ALOGW("invalid alert: threshold=%f num_buckets= %d",
                   alert.trigger_if_sum_gt(), alert.num_buckets());
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index b4a7bb7..218d52a 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -16,6 +16,7 @@
 #include "../metrics/metrics_test_helper.h"
 
 #include <gtest/gtest.h>
+#include <math.h>
 #include <stdio.h>
 #include <vector>
 
@@ -104,12 +105,12 @@
     for (const auto& kv : timestamps) {
         if (kv.second < 0) {
             // Make sure that, if there is a refractory period, it is already past.
-            EXPECT_LT(tracker.getRefractoryPeriodEndsSec(kv.first),
-                    currTimestampNs / NS_PER_SEC + 1)
+            EXPECT_LT(tracker.getRefractoryPeriodEndsSec(kv.first) * NS_PER_SEC,
+                    (uint64_t)currTimestampNs)
                     << "Failure was at currTimestampNs " << currTimestampNs;
         } else {
             EXPECT_EQ(tracker.getRefractoryPeriodEndsSec(kv.first),
-                      kv.second / NS_PER_SEC + refractoryPeriodSec)
+                      std::ceil(1.0 * kv.second / NS_PER_SEC) + refractoryPeriodSec)
                       << "Failure was at currTimestampNs " << currTimestampNs;
         }
     }
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index a07683e..fff1155 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -19,6 +19,7 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <math.h>
 #include <stdio.h>
 #include <vector>
 
@@ -379,13 +380,13 @@
     EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second);
     // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            event5.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec);
+            std::ceil(1.0 * event5.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
 
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7);
     EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second);
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec);
+            std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index c0cc0b6..5ef84e6 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -20,6 +20,7 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <math.h>
 #include <stdio.h>
 #include <vector>
 
@@ -392,7 +393,7 @@
                            .mFields->begin()
                            ->mValue.int_value);
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            event2->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec);
+            std::ceil(1.0 * event2->GetElapsedTimestampNs() / NS_PER_SEC) + refPeriodSec);
 
     std::shared_ptr<LogEvent> event3 =
             std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10);
@@ -407,7 +408,7 @@
                            .mFields->begin()
                            ->mValue.int_value);
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            event2->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec);
+            std::ceil(1.0 * event2->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
 
     // The event4 does not have the gauge field. Thus the current bucket value is 0.
     std::shared_ptr<LogEvent> event4 =
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 54abcb2..9b27f3c 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -19,6 +19,7 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <math.h>
 #include <stdio.h>
 #include <set>
 #include <unordered_map>
@@ -416,7 +417,7 @@
     tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 25, false);
     EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs));
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey),
-              (eventStartTimeNs + 2 * bucketSizeNs + 25) / NS_PER_SEC + refPeriodSec);
+              std::ceil((eventStartTimeNs + 2 * bucketSizeNs + 25.0) / NS_PER_SEC + refPeriodSec));
 }
 
 TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) {
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index a0addcc..a8eb2703 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -19,6 +19,7 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <math.h>
 #include <stdio.h>
 #include <vector>
 
@@ -415,16 +416,16 @@
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
     // Anomaly at event 4 since Value sum == 131 > 130!
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            event4->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec);
+            std::ceil(1.0 * event4->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event5);
     // Event 5 is within 3 sec refractory period. Thus last alarm timestamp is still event4.
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            event4->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec);
+            std::ceil(1.0 * event4->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
 
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event6);
     // Anomaly at event 6 since Value sum == 160 > 130 and after refractory period.
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
-            event6->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec);
+            std::ceil(1.0 * event6->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
 }
 
 }  // namespace statsd
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 99d871e..b1f1f1c 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -117,8 +117,8 @@
 Landroid/app/admin/DevicePolicyManager;->packageHasActiveAdmins(Ljava/lang/String;I)Z
 Landroid/app/admin/DevicePolicyManager;->setActiveAdmin(Landroid/content/ComponentName;ZI)V
 Landroid/app/admin/DevicePolicyManager;->setActiveAdmin(Landroid/content/ComponentName;Z)V
-Landroid/app/admin/DevicePolicyManager;->throwIfParentInstance(Ljava/lang/String;)V
 Landroid/app/admin/DevicePolicyManager;->setDefaultSmsApplication(Landroid/content/ComponentName;Ljava/lang/String;)V
+Landroid/app/admin/DevicePolicyManager;->throwIfParentInstance(Ljava/lang/String;)V
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_packageHasActiveAdmins:I
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_removeActiveAdmin:I
 Landroid/app/admin/SecurityLog$SecurityEvent;-><init>([B)V
@@ -148,6 +148,7 @@
 Landroid/app/Application;->mLoadedApk:Landroid/app/LoadedApk;
 Landroid/app/ApplicationPackageManager;->configurationChanged()V
 Landroid/app/ApplicationPackageManager;->deletePackage(Ljava/lang/String;Landroid/content/pm/IPackageDeleteObserver;I)V
+Landroid/app/ApplicationPackageManager;->getPackageCurrentVolume(Landroid/content/pm/ApplicationInfo;)Landroid/os/storage/VolumeInfo;
 Landroid/app/ApplicationPackageManager;->getPackageSizeInfoAsUser(Ljava/lang/String;ILandroid/content/pm/IPackageStatsObserver;)V
 Landroid/app/ApplicationPackageManager;-><init>(Landroid/app/ContextImpl;Landroid/content/pm/IPackageManager;)V
 Landroid/app/ApplicationPackageManager;->mPM:Landroid/content/pm/IPackageManager;
@@ -175,6 +176,7 @@
 Landroid/app/backup/BackupHelperDispatcher$Header;->keyPrefix:Ljava/lang/String;
 Landroid/app/backup/FullBackup;->backupToTar(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/app/backup/FullBackupDataOutput;)I
 Landroid/app/backup/FullBackupDataOutput;->addSize(J)V
+Landroid/app/backup/FullBackupDataOutput;-><init>(Landroid/os/ParcelFileDescriptor;)V
 Landroid/app/backup/FullBackupDataOutput;->mData:Landroid/app/backup/BackupDataOutput;
 Landroid/app/ContentProviderHolder;->info:Landroid/content/pm/ProviderInfo;
 Landroid/app/ContentProviderHolder;-><init>(Landroid/content/pm/ProviderInfo;)V
@@ -281,6 +283,7 @@
 Landroid/app/Notification;->mLargeIcon:Landroid/graphics/drawable/Icon;
 Landroid/app/Notification;->mSmallIcon:Landroid/graphics/drawable/Icon;
 Landroid/app/Notification;->setLatestEventInfo(Landroid/content/Context;Ljava/lang/CharSequence;Ljava/lang/CharSequence;Landroid/app/PendingIntent;)V
+Landroid/app/Notification;->setSmallIcon(Landroid/graphics/drawable/Icon;)V
 Landroid/app/PendingIntent;->getActivityAsUser(Landroid/content/Context;ILandroid/content/Intent;ILandroid/os/Bundle;Landroid/os/UserHandle;)Landroid/app/PendingIntent;
 Landroid/app/PendingIntent;->getIntent()Landroid/content/Intent;
 Landroid/app/PendingIntent;->isActivity()Z
@@ -327,7 +330,9 @@
 Landroid/appwidget/AppWidgetManager;->mService:Lcom/android/internal/appwidget/IAppWidgetService;
 Landroid/bluetooth/BluetoothA2dp;->connect(Landroid/bluetooth/BluetoothDevice;)Z
 Landroid/bluetooth/BluetoothAdapter;->disable(Z)Z
+Landroid/bluetooth/BluetoothAdapter;->factoryReset()Z
 Landroid/bluetooth/BluetoothAdapter;->getDiscoverableTimeout()I
+Landroid/bluetooth/BluetoothAdapter;->getLeState()I
 Landroid/bluetooth/BluetoothAdapter;->mService:Landroid/bluetooth/IBluetooth;
 Landroid/bluetooth/BluetoothAdapter;->setScanMode(II)Z
 Landroid/bluetooth/BluetoothAdapter;->setScanMode(I)Z
@@ -337,8 +342,11 @@
 Landroid/bluetooth/BluetoothGattCharacteristic;->mService:Landroid/bluetooth/BluetoothGattService;
 Landroid/bluetooth/BluetoothGattDescriptor;->mCharacteristic:Landroid/bluetooth/BluetoothGattCharacteristic;
 Landroid/bluetooth/BluetoothGattDescriptor;->mInstance:I
+Landroid/bluetooth/BluetoothGatt;->mAuthRetryState:I
 Landroid/bluetooth/BluetoothGatt;->refresh()Z
 Landroid/bluetooth/BluetoothHeadset;->close()V
+Landroid/bluetooth/BluetoothPan;->isTetheringOn()Z
+Landroid/bluetooth/BluetoothPan;->setBluetoothTethering(Z)V
 Landroid/bluetooth/BluetoothUuid;->RESERVED_UUIDS:[Landroid/os/ParcelUuid;
 Landroid/bluetooth/IBluetooth;->getAddress()Ljava/lang/String;
 Landroid/bluetooth/IBluetoothManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
@@ -610,6 +618,12 @@
 Landroid/graphics/drawable/GradientDrawable$GradientState;->mThicknessRatio:F
 Landroid/graphics/drawable/GradientDrawable$GradientState;->mWidth:I
 Landroid/graphics/drawable/GradientDrawable;->mPadding:Landroid/graphics/Rect;
+Landroid/graphics/drawable/Icon;->getBitmap()Landroid/graphics/Bitmap;
+Landroid/graphics/drawable/Icon;->getDataBytes()[B
+Landroid/graphics/drawable/Icon;->getDataLength()I
+Landroid/graphics/drawable/Icon;->getDataOffset()I
+Landroid/graphics/drawable/Icon;->getResources()Landroid/content/res/Resources;
+Landroid/graphics/drawable/Icon;->hasTint()Z
 Landroid/graphics/drawable/Icon;->mType:I
 Landroid/graphics/drawable/InsetDrawable;->mState:Landroid/graphics/drawable/InsetDrawable$InsetState;
 Landroid/graphics/drawable/NinePatchDrawable;->mNinePatchState:Landroid/graphics/drawable/NinePatchDrawable$NinePatchState;
@@ -817,6 +831,7 @@
 Landroid/media/AudioManager;->getService()Landroid/media/IAudioService;
 Landroid/media/AudioManager;->mAudioFocusIdListenerMap:Ljava/util/concurrent/ConcurrentHashMap;
 Landroid/media/AudioManager;->setMasterMute(ZI)V
+Landroid/media/AudioManager;->startBluetoothScoVirtualCall()V
 Landroid/media/AudioManager;->STREAM_BLUETOOTH_SCO:I
 Landroid/media/AudioManager;->STREAM_SYSTEM_ENFORCED:I
 Landroid/media/AudioManager;->STREAM_TTS:I
@@ -860,6 +875,7 @@
 Landroid/media/AudioSystem;->errorCallbackFromNative(I)V
 Landroid/media/AudioSystem;->getPrimaryOutputFrameCount()I
 Landroid/media/AudioSystem;->getPrimaryOutputSamplingRate()I
+Landroid/media/AudioSystem;->isStreamActive(II)Z
 Landroid/media/AudioSystem;->recordingCallbackFromNative(IIII[I)V
 Landroid/media/AudioSystem;->setDeviceConnectionState(IILjava/lang/String;Ljava/lang/String;)I
 Landroid/media/AudioSystem;->setErrorCallback(Landroid/media/AudioSystem$ErrorCallback;)V
@@ -1045,6 +1061,22 @@
 Landroid/net/wifi/ScanResult;->distanceSdCm:I
 Landroid/net/wifi/ScanResult;->flags:J
 Landroid/net/wifi/ScanResult;->hessid:J
+Landroid/net/wifi/ScanResult$InformationElement;->bytes:[B
+Landroid/net/wifi/ScanResult$InformationElement;->EID_BSS_LOAD:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_ERP:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_EXTENDED_CAPS:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_EXTENDED_SUPPORTED_RATES:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_HT_OPERATION:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_INTERWORKING:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_ROAMING_CONSORTIUM:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_RSN:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_SSID:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_SUPPORTED_RATES:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_TIM:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_VHT_OPERATION:I
+Landroid/net/wifi/ScanResult$InformationElement;->EID_VSA:I
+Landroid/net/wifi/ScanResult$InformationElement;->id:I
+Landroid/net/wifi/ScanResult;->informationElements:[Landroid/net/wifi/ScanResult$InformationElement;
 Landroid/net/wifi/ScanResult;->numUsage:I
 Landroid/net/wifi/ScanResult;->seen:J
 Landroid/net/wifi/ScanResult;->untrusted:Z
@@ -1182,6 +1214,7 @@
 Landroid/os/Parcel;->readArrayMap(Landroid/util/ArrayMap;Ljava/lang/ClassLoader;)V
 Landroid/os/Parcel$ReadWriteHelper;-><init>()V
 Landroid/os/Parcel;->writeArrayMap(Landroid/util/ArrayMap;)V
+Landroid/os/PowerManager;->getDefaultScreenBrightnessSetting()I
 Landroid/os/PowerManager;->getMaximumScreenBrightnessSetting()I
 Landroid/os/PowerManager;->getMinimumScreenBrightnessSetting()I
 Landroid/os/PowerManager;->isLightDeviceIdleMode()Z
@@ -1371,6 +1404,8 @@
 Landroid/provider/Settings$Global;->ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED:Ljava/lang/String;
 Landroid/provider/Settings$Global;->PACKAGE_VERIFIER_ENABLE:Ljava/lang/String;
 Landroid/provider/Settings$Global;->sNameValueCache:Landroid/provider/Settings$NameValueCache;
+Landroid/provider/Settings;->isCallingPackageAllowedToDrawOverlays(Landroid/content/Context;ILjava/lang/String;Z)Z
+Landroid/provider/Settings;->isCallingPackageAllowedToWriteSettings(Landroid/content/Context;ILjava/lang/String;Z)Z
 Landroid/provider/Settings$NameValueCache;->mProviderHolder:Landroid/provider/Settings$ContentProviderHolder;
 Landroid/provider/Settings$Secure;->ACCESSIBILITY_AUTOCLICK_ENABLED:Ljava/lang/String;
 Landroid/provider/Settings$Secure;->ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED:Ljava/lang/String;
@@ -1385,6 +1420,7 @@
 Landroid/provider/Settings$System;->HEARING_AID:Ljava/lang/String;
 Landroid/provider/Settings$System;->MASTER_MONO:Ljava/lang/String;
 Landroid/provider/Settings$System;->putStringForUser(Landroid/content/ContentResolver;Ljava/lang/String;Ljava/lang/String;I)Z
+Landroid/provider/Settings$System;->SCREEN_AUTO_BRIGHTNESS_ADJ:Ljava/lang/String;
 Landroid/provider/Settings$System;->sNameValueCache:Landroid/provider/Settings$NameValueCache;
 Landroid/provider/Telephony$Sms;->addMessageToUri(ILandroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;ZZJ)Landroid/net/Uri;
 Landroid/provider/Telephony$Sms;->addMessageToUri(ILandroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;ZZ)Landroid/net/Uri;
@@ -2036,6 +2072,7 @@
 Landroid/view/View;->dispatchDetachedFromWindow()V
 Landroid/view/View;->fitsSystemWindows()Z
 Landroid/view/View;->getAccessibilityDelegate()Landroid/view/View$AccessibilityDelegate;
+Landroid/view/View;->getInverseMatrix()Landroid/graphics/Matrix;
 Landroid/view/View;->getListenerInfo()Landroid/view/View$ListenerInfo;
 Landroid/view/View;->getLocationOnScreen()[I
 Landroid/view/View;->getTransitionAlpha()F
@@ -2207,6 +2244,7 @@
 Landroid/widget/EdgeEffect;->mPaint:Landroid/graphics/Paint;
 Landroid/widget/Editor;->invalidateTextDisplayList()V
 Landroid/widget/Editor;->mShowCursor:J
+Landroid/widget/Editor;->mShowSoftInputOnFocus:Z
 Landroid/widget/ExpandableListView;->mChildDivider:Landroid/graphics/drawable/Drawable;
 Landroid/widget/FastScroller;->mContainerRect:Landroid/graphics/Rect;
 Landroid/widget/FastScroller;->mHeaderCount:I
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index cab6744..2752fa9 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -69,6 +69,13 @@
               AppProtoEnums.APP_TRANSITION_SNAPSHOT; // 4
 
     /**
+     * Type for {@link #notifyAppTransitionStarting}: The transition was started because it was a
+     * recents animation and we only needed to wait on the wallpaper.
+     */
+    public static final int APP_TRANSITION_RECENTS_ANIM =
+            AppProtoEnums.APP_TRANSITION_RECENTS_ANIM; // 5
+
+    /**
      * The bundle key to extract the assist data.
      */
     public static final String ASSIST_KEY_DATA = "data";
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index b12e3bc..7325daa7 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -103,12 +103,12 @@
             try {
                 IStatsManager service = getIStatsManagerLocked();
                 if (service == null) {
-                    if (DEBUG) Slog.d(TAG, "Failed to find statsd when adding configuration");
+                    Slog.e(TAG, "Failed to find statsd when adding configuration");
                     return false;
                 }
                 return service.addConfiguration(configKey, config);
             } catch (RemoteException e) {
-                if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when adding configuration");
+                Slog.e(TAG, "Failed to connect to statsd when adding configuration");
                 return false;
             }
         }
@@ -126,12 +126,12 @@
             try {
                 IStatsManager service = getIStatsManagerLocked();
                 if (service == null) {
-                    if (DEBUG) Slog.d(TAG, "Failed to find statsd when removing configuration");
+                    Slog.e(TAG, "Failed to find statsd when removing configuration");
                     return false;
                 }
                 return service.removeConfiguration(configKey);
             } catch (RemoteException e) {
-                if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when removing configuration");
+                Slog.e(TAG, "Failed to connect to statsd when removing configuration");
                 return false;
             }
         }
@@ -173,7 +173,7 @@
             try {
                 IStatsManager service = getIStatsManagerLocked();
                 if (service == null) {
-                    Slog.w(TAG, "Failed to find statsd when adding broadcast subscriber");
+                    Slog.e(TAG, "Failed to find statsd when adding broadcast subscriber");
                     return false;
                 }
                 if (pendingIntent != null) {
@@ -184,7 +184,7 @@
                     return service.unsetBroadcastSubscriber(configKey, subscriberId);
                 }
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
+                Slog.e(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
                 return false;
             }
         }
@@ -210,7 +210,7 @@
             try {
                 IStatsManager service = getIStatsManagerLocked();
                 if (service == null) {
-                    Slog.d(TAG, "Failed to find statsd when registering data listener.");
+                    Slog.e(TAG, "Failed to find statsd when registering data listener.");
                     return false;
                 }
                 if (pendingIntent == null) {
@@ -222,7 +222,7 @@
                 }
 
             } catch (RemoteException e) {
-                Slog.d(TAG, "Failed to connect to statsd when registering data listener.");
+                Slog.e(TAG, "Failed to connect to statsd when registering data listener.");
                 return false;
             }
         }
@@ -241,12 +241,12 @@
             try {
                 IStatsManager service = getIStatsManagerLocked();
                 if (service == null) {
-                    if (DEBUG) Slog.d(TAG, "Failed to find statsd when getting data");
+                    Slog.e(TAG, "Failed to find statsd when getting data");
                     return null;
                 }
                 return service.getData(configKey);
             } catch (RemoteException e) {
-                if (DEBUG) Slog.d(TAG, "Failed to connecto statsd when getting data");
+                Slog.e(TAG, "Failed to connect to statsd when getting data");
                 return null;
             }
         }
@@ -265,12 +265,12 @@
             try {
                 IStatsManager service = getIStatsManagerLocked();
                 if (service == null) {
-                    if (DEBUG) Slog.d(TAG, "Failed to find statsd when getting metadata");
+                    Slog.e(TAG, "Failed to find statsd when getting metadata");
                     return null;
                 }
                 return service.getMetadata();
             } catch (RemoteException e) {
-                if (DEBUG) Slog.d(TAG, "Failed to connecto statsd when getting metadata");
+                Slog.e(TAG, "Failed to connect to statsd when getting metadata");
                 return null;
             }
         }
diff --git a/core/java/android/content/SyncResult.java b/core/java/android/content/SyncResult.java
index 4f86af9..f67d7f5 100644
--- a/core/java/android/content/SyncResult.java
+++ b/core/java/android/content/SyncResult.java
@@ -79,7 +79,17 @@
 
     /**
      * Used to indicate to the SyncManager that future sync requests that match the request's
-     * Account and authority should be delayed at least this many seconds.
+     * Account and authority should be delayed until a time in seconds since Java epoch.
+     *
+     * <p>For example, if you want to delay the next sync for at least 5 minutes, then:
+     * <pre>
+     * result.delayUntil = (System.currentTimeMillis() / 1000) + 5 * 60;
+     * </pre>
+     *
+     * <p>By default, when a sync fails, the system retries later with an exponential back-off
+     * with the system default initial delay time, which always wins over {@link #delayUntil} --
+     * i.e. if the system back-off time is larger than {@link #delayUntil}, {@link #delayUntil}
+     * will essentially be ignored.
      */
     public long delayUntil;
 
diff --git a/core/java/android/security/ConfirmationDialog.java b/core/java/android/security/ConfirmationDialog.java
index f6127e1..1697106 100644
--- a/core/java/android/security/ConfirmationDialog.java
+++ b/core/java/android/security/ConfirmationDialog.java
@@ -227,12 +227,32 @@
         return uiOptionsAsFlags;
     }
 
+    private boolean isAccessibilityServiceRunning() {
+        boolean serviceRunning = false;
+        try {
+            ContentResolver contentResolver = mContext.getContentResolver();
+            int a11yEnabled = Settings.Secure.getInt(contentResolver,
+                    Settings.Secure.ACCESSIBILITY_ENABLED);
+            if (a11yEnabled == 1) {
+                serviceRunning = true;
+            }
+        } catch (SettingNotFoundException e) {
+            Log.w(TAG, "Unexpected SettingNotFoundException");
+            e.printStackTrace();
+        }
+        return serviceRunning;
+    }
+
     /**
      * Requests a confirmation prompt to be presented to the user.
      *
      * When the prompt is no longer being presented, one of the methods in
      * {@link ConfirmationCallback} is called on the supplied callback object.
      *
+     * Confirmation dialogs may not be available when accessibility services are running so this
+     * may fail with a {@link ConfirmationNotAvailableException} exception even if
+     * {@link #isSupported} returns {@code true}.
+     *
      * @param executor the executor identifying the thread that will receive the callback.
      * @param callback the callback to use when the dialog is done showing.
      * @throws IllegalArgumentException if the prompt text is too long or malfomed.
@@ -245,6 +265,9 @@
         if (mCallback != null) {
             throw new ConfirmationAlreadyPresentingException();
         }
+        if (isAccessibilityServiceRunning()) {
+            throw new ConfirmationNotAvailableException();
+        }
         mCallback = callback;
         mExecutor = executor;
 
diff --git a/core/proto/android/app/enums.proto b/core/proto/android/app/enums.proto
index 5eb05be..1754e42 100644
--- a/core/proto/android/app/enums.proto
+++ b/core/proto/android/app/enums.proto
@@ -32,6 +32,9 @@
     APP_TRANSITION_TIMEOUT = 3;
     // The transition was started because of a we drew a task snapshot.
     APP_TRANSITION_SNAPSHOT = 4;
+    // The transition was started because it was a recents animation and we only needed to wait on
+    // the wallpaper.
+    APP_TRANSITION_RECENTS_ANIM = 5;
 }
 
 // ActivityManager.java PROCESS_STATEs
diff --git a/media/java/android/media/MediaPlayerBase.java b/media/java/android/media/MediaPlayerBase.java
index f18e347..48b7a51 100644
--- a/media/java/android/media/MediaPlayerBase.java
+++ b/media/java/android/media/MediaPlayerBase.java
@@ -129,6 +129,33 @@
      */
     public abstract void seekTo(long pos);
 
+    /**
+     * Fast forwards playback. If playback is already fast forwarding this may increase the rate.
+     * <p>
+     * Default implementation sets the playback speed to the 2.0f
+     * @see #setPlaybackSpeed(float)
+     * @hide
+     */
+    // TODO(jaewan): Unhide (b/74724709)
+    public void fastForward() {
+        setPlaybackSpeed(2.0f);
+    }
+
+    /**
+     * Rewinds playback. If playback is already rewinding this may increase the rate.
+     * <p>
+     * Default implementation sets the playback speed to the -1.0f if
+     * {@link #isReversePlaybackSupported()} returns {@code true}.
+     * @see #setPlaybackSpeed(float)
+     * @hide
+     */
+    // TODO(jaewan): Unhide (b/74724709)
+    public void rewind() {
+        if (isReversePlaybackSupported()) {
+            setPlaybackSpeed(-1.0f);
+        }
+    }
+
     public static final long UNKNOWN_TIME = -1;
 
     /**
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 60714df..6647831 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -1418,14 +1418,14 @@
     }
 
     /**
-     * Start fast forwarding. If playback is already fast forwarding this may increase the rate.
+     * Fast forwards playback. If playback is already fast forwarding this may increase the rate.
      */
     public void fastForward() {
         mProvider.fastForward_impl();
     }
 
     /**
-     * Start rewinding. If playback is already rewinding this may increase the rate.
+     * Rewinds playback. If playback is already rewinding this may increase the rate.
      */
     public void rewind() {
         mProvider.rewind_impl();
diff --git a/media/java/android/media/update/ApiLoader.java b/media/java/android/media/update/ApiLoader.java
index 0d190a7..cf18bdd 100644
--- a/media/java/android/media/update/ApiLoader.java
+++ b/media/java/android/media/update/ApiLoader.java
@@ -16,49 +16,73 @@
 
 package android.media.update;
 
-import android.annotation.NonNull;
-import android.content.res.Resources;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Build;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.GuardedBy;
+
+import dalvik.system.PathClassLoader;
+
+import java.io.File;
 
 /**
  * @hide
  */
 public final class ApiLoader {
-    private static Object sMediaLibrary;
+    @GuardedBy("this")
+    private static StaticProvider sMediaUpdatable;
 
     private static final String UPDATE_PACKAGE = "com.android.media.update";
     private static final String UPDATE_CLASS = "com.android.media.update.ApiFactory";
     private static final String UPDATE_METHOD = "initialize";
+    private static final boolean REGISTER_UPDATE_DEPENDENCY = true;
 
     private ApiLoader() { }
 
-    public static StaticProvider getProvider(@NonNull Context context) {
-        if (context == null) {
-            throw new IllegalArgumentException("context shouldn't be null");
-        }
+    @Deprecated
+    public static StaticProvider getProvider(Context context) {
+        return getProvider();
+    }
+
+    public static StaticProvider getProvider() {
+        if (sMediaUpdatable != null) return sMediaUpdatable;
+
         try {
-            return (StaticProvider) getMediaLibraryImpl(context);
-        } catch (PackageManager.NameNotFoundException | ReflectiveOperationException e) {
+            return getMediaUpdatable();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (NameNotFoundException | ReflectiveOperationException e) {
             throw new RuntimeException(e);
         }
     }
 
     // TODO This method may do I/O; Ensure it does not violate (emit warnings in) strict mode.
-    private static synchronized Object getMediaLibraryImpl(Context context)
-            throws PackageManager.NameNotFoundException, ReflectiveOperationException {
-        if (sMediaLibrary != null) return sMediaLibrary;
+    private static synchronized StaticProvider getMediaUpdatable()
+            throws NameNotFoundException, ReflectiveOperationException, RemoteException {
+        if (sMediaUpdatable != null) return sMediaUpdatable;
 
         // TODO Figure out when to use which package (query media update service)
         int flags = Build.IS_DEBUGGABLE ? 0 : PackageManager.MATCH_FACTORY_ONLY;
-        Context libContext = context.createApplicationContext(
-                context.getPackageManager().getPackageInfo(UPDATE_PACKAGE, flags).applicationInfo,
-                Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
-        sMediaLibrary = libContext.getClassLoader()
-                .loadClass(UPDATE_CLASS)
-                .getMethod(UPDATE_METHOD, Resources.class, Resources.Theme.class)
-                .invoke(null, libContext.getResources(), libContext.getTheme());
-        return sMediaLibrary;
+        flags |= PackageManager.GET_SHARED_LIBRARY_FILES;
+        ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
+                UPDATE_PACKAGE, flags, UserHandle.myUserId());
+
+        if (REGISTER_UPDATE_DEPENDENCY) {
+            // Register a dependency to the updatable in order to be killed during updates
+            ActivityManager.getService().addPackageDependency(ai.packageName);
+        }
+
+        PathClassLoader classLoader = new PathClassLoader(ai.sourceDir,
+                ai.nativeLibraryDir + File.pathSeparator + System.getProperty("java.library.path"),
+                ClassLoader.getSystemClassLoader().getParent());
+        return sMediaUpdatable = (StaticProvider) classLoader.loadClass(UPDATE_CLASS)
+                .getMethod(UPDATE_METHOD, ApplicationInfo.class).invoke(null, ai);
     }
 }
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index e77db82..fe785c2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -887,13 +887,13 @@
     <string name="power_remaining_duration_only_short"><xliff:g id="time">%1$s</xliff:g> left</string>
 
     <!-- [CHAR_LIMIT=100] Label for enhanced estimated time that phone will run out of battery -->
-    <string name="power_discharge_by_enhanced">Will last until about <xliff:g id="time">%1$s</xliff:g> based on your usage (<xliff:g id="level">%2$s</xliff:g>)</string>
+    <string name="power_discharge_by_enhanced">Should last until about <xliff:g id="time">%1$s</xliff:g> based on your usage (<xliff:g id="level">%2$s</xliff:g>)</string>
     <!-- [CHAR_LIMIT=100] Label for enhanced estimated time that phone will run out of battery with no percentage -->
-    <string name="power_discharge_by_only_enhanced">Will last until about <xliff:g id="time">%1$s</xliff:g> based on your usage</string>
+    <string name="power_discharge_by_only_enhanced">Should last until about <xliff:g id="time">%1$s</xliff:g> based on your usage</string>
     <!-- [CHAR_LIMIT=100] Label for estimated time that phone will run out of battery -->
-    <string name="power_discharge_by">Will last until about <xliff:g id="time">%1$s</xliff:g> (<xliff:g id="level">%2$s</xliff:g>)</string>
+    <string name="power_discharge_by">Should last until about <xliff:g id="time">%1$s</xliff:g> (<xliff:g id="level">%2$s</xliff:g>)</string>
     <!-- [CHAR_LIMIT=100] Label for estimated time that phone will run out of battery -->
-    <string name="power_discharge_by_only">Will last until about <xliff:g id="time">%1$s</xliff:g></string>
+    <string name="power_discharge_by_only">Should last until about <xliff:g id="time">%1$s</xliff:g></string>
 
     <!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount -->
     <string name="power_remaining_less_than_duration_only">Less than <xliff:g id="threshold">%1$s</xliff:g> remaining</string>
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
index 5758467..c11687b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
@@ -40,7 +40,7 @@
     public static final long TEN_MINUTES_MILLIS = Duration.ofMinutes(10).toMillis();
     public static final long THREE_DAYS_MILLIS = Duration.ofDays(3).toMillis();
     public static final long THIRTY_HOURS_MILLIS = Duration.ofHours(30).toMillis();
-    public static final String NORMAL_CASE_EXPECTED_PREFIX = "Will last until about";
+    public static final String NORMAL_CASE_EXPECTED_PREFIX = "Should last until about";
     public static final String ENHANCED_SUFFIX = " based on your usage";
     // matches a time (ex: '1:15 PM', '2 AM')
     public static final String TIME_OF_DAY_REGEX = " (\\d)+:?(\\d)* (AM)|(PM)";
diff --git a/packages/SystemUI/src/com/android/systemui/DejankUtils.java b/packages/SystemUI/src/com/android/systemui/DejankUtils.java
index eba1d0f..4ee3bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/DejankUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/DejankUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.util.Assert;
 
 import android.os.Handler;
@@ -30,9 +31,13 @@
 
     private static final Choreographer sChoreographer = Choreographer.getInstance();
     private static final Handler sHandler = new Handler();
-
     private static final ArrayList<Runnable> sPendingRunnables = new ArrayList<>();
 
+    /**
+     * Only for testing.
+     */
+    private static boolean sImmediate;
+
     private static final Runnable sAnimationCallbackRunnable = new Runnable() {
         @Override
         public void run() {
@@ -51,6 +56,10 @@
      * <p>Needs to be called from the main thread.
      */
     public static void postAfterTraversal(Runnable r) {
+        if (sImmediate) {
+            r.run();
+            return;
+        }
         Assert.isMainThread();
         sPendingRunnables.add(r);
         postAnimationCallback();
@@ -71,4 +80,9 @@
         sChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, sAnimationCallbackRunnable,
                 null);
     }
+
+    @VisibleForTesting
+    public static void setImmediate(boolean immediate) {
+        sImmediate = immediate;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index d1834e9..91edfda 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -27,6 +27,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Dependency.DependencyProvider;
+import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -95,7 +96,7 @@
             LockPatternUtils lockPatternUtils,
             ViewGroup container, DismissCallbackRegistry dismissCallbackRegistry) {
         return new KeyguardBouncer(context, callback, lockPatternUtils, container,
-                dismissCallbackRegistry);
+                dismissCallbackRegistry, FalsingManager.getInstance(context));
     }
 
     public ScrimController createScrimController(LightBarController lightBarController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index 475a609..1807465 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -107,11 +107,6 @@
     }
 
     @Override
-    public void setBackground(Drawable background) {
-        Log.wtfStack(TAG, "ScrimView should never have a background.");
-    }
-
-    @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         int densityDpi = newConfig.densityDpi;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 1d9cdf7..3bbfe3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -77,6 +77,9 @@
 
     public RemoteAnimationAdapter getLaunchAnimation(
             ExpandableNotificationRow sourceNotification) {
+        if (mStatusBar.getBarState() != StatusBarState.SHADE) {
+            return null;
+        }
         AnimationRunner animationRunner = new AnimationRunner(sourceNotification);
         return new RemoteAnimationAdapter(animationRunner, ANIMATION_DURATION,
                 0 /* statusBarTransitionDelay */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 010b165..2a1813f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -16,10 +16,14 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.keyguard.KeyguardHostView.OnDismissAction;
+import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+
 import android.content.Context;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 import android.util.MathUtils;
 import android.util.Slog;
 import android.util.StatsLog;
@@ -29,7 +33,6 @@
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
-import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardHostView;
@@ -42,9 +45,6 @@
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 
-import static com.android.keyguard.KeyguardHostView.OnDismissAction;
-import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-
 /**
  * A class which manages the bouncer on the lockscreen.
  */
@@ -76,13 +76,13 @@
 
     public KeyguardBouncer(Context context, ViewMediatorCallback callback,
             LockPatternUtils lockPatternUtils, ViewGroup container,
-            DismissCallbackRegistry dismissCallbackRegistry) {
+            DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager) {
         mContext = context;
         mCallback = callback;
         mLockPatternUtils = lockPatternUtils;
         mContainer = container;
         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
-        mFalsingManager = FalsingManager.getInstance(mContext);
+        mFalsingManager = falsingManager;
         mDismissCallbackRegistry = dismissCallbackRegistry;
         mHandler = new Handler();
     }
@@ -91,7 +91,14 @@
         show(resetSecuritySelection, true /* notifyFalsing */);
     }
 
-    public void show(boolean resetSecuritySelection, boolean notifyFalsing) {
+    /**
+     * Shows the bouncer.
+     *
+     * @param resetSecuritySelection Cleans keyguard view
+     * @param animated true when the bouncer show show animated, false when the user will be
+     *                 dragging it and animation should be deferred.
+     */
+    public void show(boolean resetSecuritySelection, boolean animated) {
         final int keyguardUserId = KeyguardUpdateMonitor.getCurrentUser();
         if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
             // In split system user mode, we never unlock system user.
@@ -104,9 +111,11 @@
         // are valid.
         // Later, at the end of the animation, when the bouncer is at the top of the screen,
         // onFullyShown() will be called and FalsingManager will stop recording touches.
-        if (notifyFalsing) {
+        if (animated) {
             mFalsingManager.onBouncerShown();
+            setExpansion(0);
         }
+
         if (resetSecuritySelection) {
             // showPrimarySecurityScreen() updates the current security method. This is needed in
             // case we are already showing and the current security method changed.
@@ -157,7 +166,9 @@
     public void onFullyHidden() {
         if (!mShowingSoon) {
             cancelShowRunnable();
-            mRoot.setVisibility(View.INVISIBLE);
+            if (mRoot != null) {
+                mRoot.setVisibility(View.INVISIBLE);
+            }
             mFalsingManager.onBouncerHidden();
         }
     }
@@ -202,11 +213,19 @@
      *               and {@link KeyguardSecurityView#PROMPT_REASON_RESTART}
      */
     public void showPromptReason(int reason) {
-        mKeyguardView.showPromptReason(reason);
+        if (mKeyguardView != null) {
+            mKeyguardView.showPromptReason(reason);
+        } else {
+            Log.w(TAG, "Trying to show prompt reason on empty bouncer");
+        }
     }
 
     public void showMessage(String message, int color) {
-        mKeyguardView.showMessage(message, color);
+        if (mKeyguardView != null) {
+            mKeyguardView.showMessage(message, color);
+        } else {
+            Log.w(TAG, "Trying to show message on empty bouncer");
+        }
     }
 
     private void cancelShowRunnable() {
@@ -290,7 +309,8 @@
      */
     public void setExpansion(float fraction) {
         if (mKeyguardView != null) {
-            mKeyguardView.setAlpha(MathUtils.map(ALPHA_EXPANSION_THRESHOLD, 1, 1, 0, fraction));
+            float alpha = MathUtils.map(ALPHA_EXPANSION_THRESHOLD, 1, 1, 0, fraction);
+            mKeyguardView.setAlpha(MathUtils.constrain(alpha, 0f, 1f));
             mKeyguardView.setTranslationY(fraction * mKeyguardView.getHeight());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 7f1e9d0..04cb620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -947,6 +947,17 @@
         return mClosing || mLaunchingNotification;
     }
 
+    /**
+     * Bouncer might need a scrim when you double tap on notifications or edit QS.
+     * On other cases, when you drag up the bouncer with the finger or just fling,
+     * the scrim should be hidden to avoid occluding the clock.
+     *
+     * @return true when we need a scrim to show content on top of the notification panel.
+     */
+    public boolean needsScrimming() {
+        return !isTracking() && !isCollapsing() && !isFullyCollapsed();
+    }
+
     public boolean isTracking() {
         return mTracking;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index f8b4e07..ae55ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -239,6 +239,11 @@
         mCurrentBehindAlpha = state.getBehindAlpha(mNotificationDensity);
         applyExpansionToAlpha();
 
+        // Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
+        // We need to disable focus otherwise AOD would end up with a gray overlay.
+        mScrimInFront.setFocusable(!state.isLowPowerState());
+        mScrimBehind.setFocusable(!state.isLowPowerState());
+
         // Cancel blanking transitions that were pending before we requested a new state
         if (mPendingFrameCallback != null) {
             Choreographer.getInstance().removeFrameCallback(mPendingFrameCallback);
@@ -257,7 +262,7 @@
         // the animation plays properly until the last frame.
         // It's important to avoid holding the wakelock unless necessary because
         // WakeLock#aqcuire will trigger an IPC and will cause jank.
-        if (mState == ScrimState.AOD) {
+        if (mState.isLowPowerState()) {
             holdWakeLock();
         }
 
@@ -449,7 +454,7 @@
         if (mNeedsDrawableColorUpdate) {
             mNeedsDrawableColorUpdate = false;
             final GradientColors currentScrimColors;
-            if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER_OCCLUDED
+            if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER_SCRIMMED
                     || mState == ScrimState.BOUNCER) {
                 // Always animate color changes if we're seeing the keyguard
                 mScrimInFront.setColors(mLockColors, true /* animated */);
@@ -528,7 +533,7 @@
             scrim.setClickable(false);
         } else {
             // Eat touch events (unless dozing).
-            scrim.setClickable(!(mState == ScrimState.AOD));
+            scrim.setClickable(!mState.isLowPowerState());
         }
         updateScrim(scrim, alpha);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 58100ef..55e8714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -79,7 +79,7 @@
     /**
      * Showing password challenge on top of a FLAG_SHOW_WHEN_LOCKED activity.
      */
-    BOUNCER_OCCLUDED(2) {
+    BOUNCER_SCRIMMED(2) {
         @Override
         public void prepare(ScrimState previousState) {
             mCurrentBehindAlpha = 0;
@@ -116,6 +116,11 @@
             // to set our state.
             mAnimateChange = mCanControlScreenOff;
         }
+
+        @Override
+        public boolean isLowPowerState() {
+            return true;
+        }
     },
 
     /**
@@ -250,4 +255,8 @@
     public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
         mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
     }
+
+    public boolean isLowPowerState() {
+        return false;
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2e45b12..5bc45cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3907,12 +3907,12 @@
 
     private void showBouncerIfKeyguard() {
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
-            showBouncer();
+            showBouncer(true /* animated */);
         }
     }
 
-    protected void showBouncer() {
-        mStatusBarKeyguardViewManager.dismiss();
+    protected void showBouncer(boolean animated) {
+        mStatusBarKeyguardViewManager.showBouncer(animated);
     }
 
     private void instantExpandNotificationsPanel() {
@@ -4026,7 +4026,7 @@
     public void onTrackingStopped(boolean expand) {
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
             if (!expand && !mUnlockMethodCache.canSkipBouncer()) {
-                showBouncerIfKeyguard();
+                showBouncer(false /* animated */);
             }
         }
     }
@@ -4165,7 +4165,7 @@
     @Override
     public void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
         mLeaveOpenOnKeyguardHide = true;
-        showBouncer();
+        showBouncer(true /* animated */);
         mPendingRemoteInputView = clicked;
     }
 
@@ -4631,9 +4631,10 @@
                 != FingerprintUnlockController.MODE_UNLOCK);
 
         if (mBouncerShowing) {
-            final boolean qsExpanded = mQSPanel != null && mQSPanel.isExpanded();
-            mScrimController.transitionTo(mIsOccluded || qsExpanded ?
-                    ScrimState.BOUNCER_OCCLUDED : ScrimState.BOUNCER);
+            // Bouncer needs the front scrim when it's on top of an activity,
+            // tapping on a notification or editing QS.
+            mScrimController.transitionTo(mIsOccluded || mNotificationPanel.needsScrimming() ?
+                    ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER);
         } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
             mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
         } else if (mBrightnessMirrorVisible) {
@@ -5043,8 +5044,11 @@
                     RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(
                             row);
                     try {
-                        ActivityManager.getService().registerRemoteAnimationForNextActivityStart(
-                                intent.getCreatorPackage(), adapter);
+                        if (adapter != null) {
+                            ActivityManager.getService()
+                                    .registerRemoteAnimationForNextActivityStart(
+                                            intent.getCreatorPackage(), adapter);
+                        }
                         launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
                                 null, null, getActivityOptions(adapter));
                         mActivityLaunchAnimator.setLaunchResult(launchResult);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8d536d8..b26b7c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -198,9 +198,9 @@
         cancelPendingWakeupAction();
     }
 
-    private void showBouncer() {
+    public void showBouncer(boolean animated) {
         if (mShowing) {
-            mBouncer.show(false /* resetSecuritySelection */);
+            mBouncer.show(false /* resetSecuritySelection */, animated);
         }
         updateStates();
     }
@@ -485,10 +485,6 @@
         mStatusBar.executeRunnableDismissingKeyguard(null, null, true, false, true);
     }
 
-    public void dismiss() {
-        showBouncer();
-    }
-
     /**
      * WARNING: This method might cause Binder calls.
      */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index 4051220..f3a8417 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -16,39 +16,268 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static org.mockito.Mockito.mock;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.content.Context;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.calls;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Color;
 import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.test.UiThreadTest;
-import android.view.ContextThemeWrapper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardHostView;
+import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.R;
+import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 
+import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class KeyguardBouncerTest extends SysuiTestCase {
 
-    @UiThreadTest
-    @Test
-    public void inflateDetached() {
-        final ViewGroup container = new FrameLayout(getContext());
-        final KeyguardBouncer bouncer = new KeyguardBouncer(getContext(),
-                mock(ViewMediatorCallback.class), mock(LockPatternUtils.class), container, mock(
-                DismissCallbackRegistry.class));
+    @Mock
+    private FalsingManager mFalsingManager;
+    @Mock
+    private ViewMediatorCallback mViewMediatorCallback;
+    @Mock
+    private LockPatternUtils mLockPatternUtils;
+    @Mock
+    private DismissCallbackRegistry mDismissCallbackRegistry;
+    @Mock
+    private KeyguardHostView mKeyguardHostView;
+    @Mock
+    private ViewTreeObserver mViewTreeObserver;
 
-        // Detached bouncer should still be able to be inflated
-        bouncer.inflateView();
+    private KeyguardBouncer mBouncer;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        DejankUtils.setImmediate(true);
+        final ViewGroup container = new FrameLayout(getContext());
+        when(mKeyguardHostView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+        when(mKeyguardHostView.getHeight()).thenReturn(500);
+        mBouncer = new KeyguardBouncer(getContext(), mViewMediatorCallback,
+                mLockPatternUtils, container, mDismissCallbackRegistry, mFalsingManager) {
+            @Override
+            protected void inflateView() {
+                super.inflateView();
+                mKeyguardView = mKeyguardHostView;
+            }
+        };
     }
 
+    @Test
+    public void testInflateView_doesntCrash() {
+        mBouncer.inflateView();
+    }
+
+    @Test
+    public void testShow_notifiesFalsingManager() {
+        mBouncer.show(true);
+        verify(mFalsingManager).onBouncerShown();
+
+        mBouncer.show(true, false);
+        verifyNoMoreInteractions(mFalsingManager);
+    }
+
+    /**
+     * Regression test: Invisible bouncer when occluded.
+     */
+    @Test
+    public void testShow_bouncerIsVisible() {
+        // Expand notification panel as if we were in the keyguard.
+        mBouncer.ensureView();
+        mBouncer.setExpansion(1);
+
+        reset(mKeyguardHostView);
+        when(mKeyguardHostView.getHeight()).thenReturn(500);
+
+        mBouncer.show(true);
+        verify(mKeyguardHostView).setAlpha(eq(1f));
+        verify(mKeyguardHostView).setTranslationY(eq(0f));
+    }
+
+    @Test
+    public void testShow_notifiesVisibility() {
+        mBouncer.show(true);
+        verify(mViewMediatorCallback).onBouncerVisiblityChanged(eq(true));
+
+        // Not called again when visible
+        reset(mViewMediatorCallback);
+        mBouncer.show(true);
+        verifyNoMoreInteractions(mViewMediatorCallback);
+    }
+
+    @Test
+    public void testShow_triesToDismissKeyguard() {
+        mBouncer.show(true);
+        verify(mKeyguardHostView).dismiss(anyInt());
+    }
+
+    @Test
+    public void testShow_resetsSecuritySelection() {
+        mBouncer.show(false);
+        verify(mKeyguardHostView, never()).showPrimarySecurityScreen();
+
+        mBouncer.hide(false);
+        mBouncer.show(true);
+        verify(mKeyguardHostView).showPrimarySecurityScreen();
+    }
+
+    @Test
+    public void testShow_animatesKeyguardView() {
+        mBouncer.show(true);
+        verify(mKeyguardHostView).startAppearAnimation();
+    }
+
+    @Test
+    public void testShow_showsErrorMessage() {
+        final String errorMessage = "an error message";
+        when(mViewMediatorCallback.consumeCustomMessage()).thenReturn(errorMessage);
+        mBouncer.show(true);
+        verify(mKeyguardHostView).showErrorMessage(eq(errorMessage));
+    }
+
+    @Test
+    public void testOnFullyShown_notifiesFalsingManager() {
+        mBouncer.onFullyShown();
+        verify(mFalsingManager).onBouncerShown();
+    }
+
+    @Test
+    public void testOnFullyHidden_notifiesFalsingManager() {
+        mBouncer.onFullyHidden();
+        verify(mFalsingManager).onBouncerHidden();
+    }
+
+    @Test
+    public void testHide_notifiesFalsingManager() {
+        mBouncer.hide(false);
+        verify(mFalsingManager).onBouncerHidden();
+    }
+
+    @Test
+    public void testHide_notifiesVisibility() {
+        mBouncer.hide(false);
+        verify(mViewMediatorCallback).onBouncerVisiblityChanged(eq(false));
+    }
+
+    @Test
+    public void testHide_notifiesDismissCallbackIfVisible() {
+        mBouncer.hide(false);
+        verifyZeroInteractions(mDismissCallbackRegistry);
+        mBouncer.show(false);
+        mBouncer.hide(false);
+        verify(mDismissCallbackRegistry).notifyDismissCancelled();
+    }
+
+    @Test
+    public void testShowPromptReason_propagates() {
+        mBouncer.ensureView();
+        mBouncer.showPromptReason(1);
+        verify(mKeyguardHostView).showPromptReason(eq(1));
+    }
+
+    @Test
+    public void testShowMessage_propagates() {
+        final String message = "a message";
+        mBouncer.ensureView();
+        mBouncer.showMessage(message, Color.GREEN);
+        verify(mKeyguardHostView).showMessage(eq(message), eq(Color.GREEN));
+    }
+
+    @Test
+    public void testShowOnDismissAction_showsBouncer() {
+        final KeyguardHostView.OnDismissAction dismissAction = () -> false;
+        final Runnable cancelAction = () -> {};
+        mBouncer.showWithDismissAction(dismissAction, cancelAction);
+        verify(mKeyguardHostView).setOnDismissAction(dismissAction, cancelAction);
+        Assert.assertTrue("Should be showing", mBouncer.isShowing());
+    }
+
+    @Test
+    public void testStartPreHideAnimation_notifiesView() {
+        final boolean[] ran = {false};
+        final Runnable r = () -> ran[0] = true;
+        mBouncer.startPreHideAnimation(r);
+        Assert.assertTrue("Callback should have been invoked", ran[0]);
+
+        ran[0] = false;
+        mBouncer.ensureView();
+        mBouncer.startPreHideAnimation(r);
+        verify(mKeyguardHostView).startDisappearAnimation(r);
+        Assert.assertFalse("Callback should have been deferred", ran[0]);
+    }
+
+    @Test
+    public void testIsShowing() {
+        Assert.assertFalse("Show wasn't invoked yet", mBouncer.isShowing());
+        mBouncer.show(true);
+        Assert.assertTrue("Should be showing", mBouncer.isShowing());
+    }
+
+    @Test
+    public void testSetExpansion() {
+        mBouncer.ensureView();
+        mBouncer.setExpansion(0.5f);
+        verify(mKeyguardHostView).setAlpha(anyFloat());
+        verify(mKeyguardHostView).setTranslationY(anyFloat());
+    }
+
+    @Test
+    public void testNeedsFullscreenBouncer_asksKeyguardView() {
+        mBouncer.ensureView();
+        mBouncer.needsFullscreenBouncer();
+        verify(mKeyguardHostView).getSecurityMode();
+        verify(mKeyguardHostView, never()).getCurrentSecurityMode();
+    }
+
+    @Test
+    public void testIsFullscreenBouncer_asksKeyguardView() {
+        mBouncer.ensureView();
+        mBouncer.isFullscreenBouncer();
+        verify(mKeyguardHostView).getCurrentSecurityMode();
+        verify(mKeyguardHostView, never()).getSecurityMode();
+    }
+
+    @Test
+    public void testIsSecure() {
+        Assert.assertTrue("Bouncer is secure before inflating views", mBouncer.isSecure());
+
+        mBouncer.ensureView();
+        for (KeyguardSecurityModel.SecurityMode mode : KeyguardSecurityModel.SecurityMode.values()){
+            reset(mKeyguardHostView);
+            when(mKeyguardHostView.getSecurityMode()).thenReturn(mode);
+            Assert.assertEquals("Security doesn't match for mode: " + mode,
+                    mBouncer.isSecure(), mode != KeyguardSecurityModel.SecurityMode.None);
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 6fbc0d7..f088c0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -42,6 +42,7 @@
 import android.view.Choreographer;
 import android.view.View;
 
+import com.android.internal.util.Preconditions;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.ScrimView;
@@ -181,7 +182,7 @@
 
     @Test
     public void transitionToBouncer() {
-        mScrimController.transitionTo(ScrimState.BOUNCER_OCCLUDED);
+        mScrimController.transitionTo(ScrimState.BOUNCER_SCRIMMED);
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible without tint
@@ -463,6 +464,17 @@
                 mHeadsUpScrim.getAlpha(), 0.01f);
     }
 
+    @Test
+    public void testScrimFocus() {
+        mScrimController.transitionTo(ScrimState.AOD);
+        Assert.assertFalse("Should not be focusable on AOD", mScrimBehind.isFocusable());
+        Assert.assertFalse("Should not be focusable on AOD", mScrimInFront.isFocusable());
+
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        Assert.assertTrue("Should be focusable on keyguard", mScrimBehind.isFocusable());
+        Assert.assertTrue("Should be focusable on keyguard", mScrimInFront.isFocusable());
+    }
+
     /**
      * Conserves old notification density after leaving state and coming back.
      *
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
index 56de32d..11bb398 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
@@ -24,6 +24,7 @@
 import com.android.systemui.util.leak.ReferenceTestUtils.CollectionWaiter;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -82,6 +83,7 @@
         collectionWaiter.waitForCollection();
     }
 
+    @Ignore("b/75329085")
     @Test
     public void trackCollection_doesNotLeakTrackedObject() {
         CollectionWaiter collectionWaiter = trackCollectionWith(mLeakDetector::trackCollection);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 04d46aa..063c1f1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -110,6 +110,7 @@
 import static android.os.Process.setThreadScheduler;
 import static android.os.Process.startWebView;
 import static android.os.Process.zygoteProcess;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
 import static android.provider.Settings.Global.DEBUG_APP;
 import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
@@ -737,6 +738,13 @@
     private ActivityRecord mLastResumedActivity;
 
     /**
+     * The activity that is currently being traced as the active resumed activity.
+     *
+     * @see #updateResumedAppTrace
+     */
+    private @Nullable ActivityRecord mTracedResumedActivity;
+
+    /**
      * If non-null, we are tracking the time the user spends in the currently focused app.
      */
     private AppTimeTracker mCurAppTimeTracker;
@@ -3423,6 +3431,7 @@
         if (mLastResumedActivity != null && r.userId != mLastResumedActivity.userId) {
             mUserController.sendForegroundProfileChanged(r.userId);
         }
+        updateResumedAppTrace(r);
         mLastResumedActivity = r;
 
         mWindowManager.setFocusedApp(r.appToken, true);
@@ -3436,6 +3445,22 @@
                 reason);
     }
 
+    private void updateResumedAppTrace(@Nullable ActivityRecord resumed) {
+        if (mTracedResumedActivity != null) {
+            Trace.asyncTraceEnd(TRACE_TAG_ACTIVITY_MANAGER,
+                    constructResumedTraceName(mTracedResumedActivity.packageName), 0);
+        }
+        if (resumed != null) {
+            Trace.asyncTraceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+                    constructResumedTraceName(resumed.packageName), 0);
+        }
+        mTracedResumedActivity = resumed;
+    }
+
+    private String constructResumedTraceName(String packageName) {
+        return "focused app: " + packageName;
+    }
+
     @Override
     public void setFocusedStack(int stackId) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setFocusedStack()");
@@ -7575,6 +7600,9 @@
 
             if (profilerInfo != null && profilerInfo.profileFd != null) {
                 profilerInfo.profileFd = profilerInfo.profileFd.dup();
+                if (TextUtils.equals(mProfileApp, processName) && mProfilerInfo != null) {
+                    clearProfilerLocked();
+                }
             }
 
             // We deprecated Build.SERIAL and it is not accessible to
@@ -7659,7 +7687,10 @@
                         mCoreSettingsObserver.getCoreSettingsLocked(),
                         buildSerial, isAutofillCompatEnabled);
             }
-
+            if (profilerInfo != null) {
+                profilerInfo.closeFd();
+                profilerInfo = null;
+            }
             checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
             updateLruProcessLocked(app, false, null);
             checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked");
@@ -13073,6 +13104,7 @@
             }
             mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
             mStackSupervisor.goingToSleepLocked();
+            updateResumedAppTrace(null /* resumed */);
             updateOomAdjLocked();
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index 352b757..724dd3f 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -299,7 +299,7 @@
 
         final boolean otherWindowModesLaunching =
                 mWindowingModeTransitionInfo.size() > 0 && info == null;
-        if ((resultCode < 0 || launchedActivity == null || !processSwitch
+        if ((!isLoggableResultCode(resultCode) || launchedActivity == null || !processSwitch
                 || windowingMode == WINDOWING_MODE_UNDEFINED) && !otherWindowModesLaunching) {
 
             // Failed to launch or it was not a process switch, so we don't care about the timing.
@@ -322,6 +322,14 @@
     }
 
     /**
+     * @return True if we should start logging an event for an activity start that returned
+     *         {@code resultCode} and that we'll indeed get a windows drawn event.
+     */
+    private boolean isLoggableResultCode(int resultCode) {
+        return resultCode == START_SUCCESS || resultCode == START_TASK_TO_FRONT;
+    }
+
+    /**
      * Notifies the tracker that all windows of the app have been drawn.
      */
     void notifyWindowsDrawn(int windowingMode, long timestamp) {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 8c49472..68c47d5 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1193,6 +1193,9 @@
     }
 
     boolean isFocusable() {
+        if (inSplitScreenPrimaryWindowingMode() && mStackSupervisor.mIsDockMinimized) {
+            return false;
+        }
         return getWindowConfiguration().canReceiveKeys() || isAlwaysFocusable();
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 8f987be..6101420 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -3420,7 +3420,7 @@
     }
 
     /** Find next proper focusable stack and make it focused. */
-    private boolean adjustFocusToNextFocusableStack(String reason) {
+    boolean adjustFocusToNextFocusableStack(String reason) {
         return adjustFocusToNextFocusableStack(reason, false /* allowFocusSelf */);
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index efc0d7d..be826f7 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2455,19 +2455,20 @@
 
                 if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         && candidate == null && stack.inSplitScreenPrimaryWindowingMode()) {
-                    // If the currently focused stack is in split-screen secondary we would prefer
-                    // the focus to move to another split-screen secondary stack or fullscreen stack
-                    // over the primary split screen stack to avoid:
-                    // - Moving the focus to the primary split-screen stack when it can't be focused
-                    //   because it will be minimized, but AM doesn't know that yet
-                    // - primary split-screen stack overlapping with a fullscreen stack when a
-                    //   fullscreen stack is higher in z than the next split-screen stack. Assistant
-                    //   stack, I am looking at you...
+                    // If the currently focused stack is in split-screen secondary we save off the
+                    // top primary split-screen stack as a candidate for focus because we might
+                    // prefer focus to move to an other stack to avoid primary split-screen stack
+                    // overlapping with a fullscreen stack when a fullscreen stack is higher in z
+                    // than the next split-screen stack. Assistant stack, I am looking at you...
                     // We only move the focus to the primary-split screen stack if there isn't a
                     // better alternative.
                     candidate = stack;
                     continue;
                 }
+                if (candidate != null && stack.inSplitScreenSecondaryWindowingMode()) {
+                    // Use the candidate stack since we are now at the secondary split-screen.
+                    return candidate;
+                }
                 return stack;
             }
         }
@@ -4413,6 +4414,14 @@
 
     void setDockedStackMinimized(boolean minimized) {
         mIsDockMinimized = minimized;
+        if (mIsDockMinimized) {
+            final ActivityStack current = getFocusedStack();
+            if (current.inSplitScreenPrimaryWindowingMode()) {
+                // The primary split-screen stack can't be focused while it is minimize, so move
+                // focus to something else.
+                current.adjustFocusToNextFocusableStack("setDockedStackMinimized");
+            }
+        }
     }
 
     void wakeUp(String reason) {
diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java
index 9121568..da56ffd 100644
--- a/services/core/java/com/android/server/am/RecentsAnimation.java
+++ b/services/core/java/com/android/server/am/RecentsAnimation.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
@@ -95,6 +96,8 @@
             }
         }
 
+        mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunching();
+
         mService.setRunningRemoteAnimation(mCallingPid, true);
 
         mWindowManager.deferSurfaceLayout();
@@ -143,6 +146,9 @@
             // If we updated the launch-behind state, update the visibility of the activities after
             // we fetch the visible tasks to be controlled by the animation
             mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
+
+            mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunched(START_TASK_TO_FRONT,
+                    homeActivity);
         } finally {
             mWindowManager.continueSurfaceLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 1170148..1575694 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -617,7 +617,8 @@
 
             if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Schedule remove starting " + mContainer
                     + " startingWindow=" + mContainer.startingWindow
-                    + " startingView=" + mContainer.startingSurface);
+                    + " startingView=" + mContainer.startingSurface
+                    + " Callers=" + Debug.getCallers(5));
 
             // Use the same thread to remove the window as we used to add it, as otherwise we end up
             // with things in the view hierarchy being called from different threads.
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 1f71b8f..88cb54d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -2079,6 +2079,13 @@
         super.prepareSurfaces();
     }
 
+    /**
+     * @return Whether our {@link #getSurfaceControl} is currently showing.
+     */
+    boolean isSurfaceShowing() {
+        return mLastSurfaceShowing;
+    }
+
     boolean isFreezingScreen() {
         return mFreezingScreen;
     }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 0b0df6f..3e72a71 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -16,13 +16,16 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityManagerInternal.APP_TRANSITION_RECENTS_ANIM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
 import static com.android.server.wm.proto.RemoteAnimationAdapterWrapperProto.TARGET;
 import static com.android.server.wm.proto.AnimationAdapterProto.REMOTE;
 
@@ -37,6 +40,7 @@
 import android.util.Log;
 import android.util.Slog;import android.util.proto.ProtoOutputStream;
 import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.IRecentsAnimationController;
 import android.view.IRecentsAnimationRunner;
@@ -279,6 +283,10 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to start recents animation", e);
         }
+        final SparseIntArray reasons = new SparseIntArray();
+        reasons.put(WINDOWING_MODE_FULLSCREEN, APP_TRANSITION_RECENTS_ANIM);
+        mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING,
+                reasons).sendToTarget();
     }
 
     void cancelAnimation() {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 9310dc4..970a8d7 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -247,7 +247,8 @@
                 // Ensure at least one window for the top app is visible before attempting to take
                 // a screenshot. Visible here means that the WSA surface is shown and has an alpha
                 // greater than 0.
-                ws -> ws.mWinAnimator != null && ws.mWinAnimator.getShown()
+                ws -> (ws.mAppToken == null || ws.mAppToken.isSurfaceShowing())
+                        && ws.mWinAnimator != null && ws.mWinAnimator.getShown()
                         && ws.mWinAnimator.mLastAlpha > 0f, true);
 
         if (!hasVisibleChild) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c7ae570..5a3b268 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -566,7 +566,7 @@
         @NonNull PasswordMetrics mActivePasswordMetrics = new PasswordMetrics();
         int mFailedPasswordAttempts = 0;
         boolean mPasswordStateHasBeenSetSinceBoot = false;
-        boolean mPasswordValidAtLastCheckpoint = false;
+        boolean mPasswordValidAtLastCheckpoint = true;
 
         int mUserHandle;
         int mPasswordOwner = -1;
@@ -3887,23 +3887,28 @@
             final PasswordMetrics metrics = ap.minimumPasswordMetrics;
             if (metrics.quality != quality) {
                 metrics.quality = quality;
-                updatePasswordValidityCheckpointLocked(userId);
-                saveSettingsLocked(userId);
+                updatePasswordValidityCheckpointLocked(userId, parent);
             }
             maybeLogPasswordComplexitySet(who, userId, parent, metrics);
         }
     }
 
     /**
-     * Updates flag in memory that tells us whether the user's password currently satisfies the
-     * requirements set by all of the user's active admins.  This should be called before
-     * {@link #saveSettingsLocked} whenever the password or the admin policies have changed.
+     * Updates a flag that tells us whether the user's password currently satisfies the
+     * requirements set by all of the user's active admins. The flag is updated both in memory
+     * and persisted to disk by calling {@link #saveSettingsLocked}, for the value of the flag
+     * be the correct one upon boot.
+     * This should be called whenever the password or the admin policies have changed.
      */
     @GuardedBy("DevicePolicyManagerService.this")
-    private void updatePasswordValidityCheckpointLocked(int userHandle) {
-        DevicePolicyData policy = getUserData(userHandle);
-        policy.mPasswordValidAtLastCheckpoint = isActivePasswordSufficientForUserLocked(
-                policy, policy.mUserHandle, false);
+    private void updatePasswordValidityCheckpointLocked(int userHandle, boolean parent) {
+        final int credentialOwner = getCredentialOwner(userHandle, parent);
+        DevicePolicyData policy = getUserData(credentialOwner);
+        policy.mPasswordValidAtLastCheckpoint =
+                isPasswordSufficientForUserWithoutCheckpointLocked(
+                        policy.mActivePasswordMetrics, userHandle, parent);
+
+        saveSettingsLocked(credentialOwner);
     }
 
     @Override
@@ -3990,8 +3995,7 @@
             final PasswordMetrics metrics = ap.minimumPasswordMetrics;
             if (metrics.length != length) {
                 metrics.length = length;
-                updatePasswordValidityCheckpointLocked(userId);
-                saveSettingsLocked(userId);
+                updatePasswordValidityCheckpointLocked(userId, parent);
             }
             maybeLogPasswordComplexitySet(who, userId, parent, metrics);
         }
@@ -4015,8 +4019,7 @@
                     who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
             if (ap.passwordHistoryLength != length) {
                 ap.passwordHistoryLength = length;
-                updatePasswordValidityCheckpointLocked(userId);
-                saveSettingsLocked(userId);
+                updatePasswordValidityCheckpointLocked(userId, parent);
             }
         }
         if (SecurityLog.isLoggingEnabled()) {
@@ -4217,8 +4220,7 @@
             final PasswordMetrics metrics = ap.minimumPasswordMetrics;
             if (metrics.upperCase != length) {
                 metrics.upperCase = length;
-                updatePasswordValidityCheckpointLocked(userId);
-                saveSettingsLocked(userId);
+                updatePasswordValidityCheckpointLocked(userId, parent);
             }
             maybeLogPasswordComplexitySet(who, userId, parent, metrics);
         }
@@ -4240,8 +4242,7 @@
             final PasswordMetrics metrics = ap.minimumPasswordMetrics;
             if (metrics.lowerCase != length) {
                 metrics.lowerCase = length;
-                updatePasswordValidityCheckpointLocked(userId);
-                saveSettingsLocked(userId);
+                updatePasswordValidityCheckpointLocked(userId, parent);
             }
             maybeLogPasswordComplexitySet(who, userId, parent, metrics);
         }
@@ -4266,8 +4267,7 @@
             final PasswordMetrics metrics = ap.minimumPasswordMetrics;
             if (metrics.letters != length) {
                 metrics.letters = length;
-                updatePasswordValidityCheckpointLocked(userId);
-                saveSettingsLocked(userId);
+                updatePasswordValidityCheckpointLocked(userId, parent);
             }
             maybeLogPasswordComplexitySet(who, userId, parent, metrics);
         }
@@ -4292,8 +4292,7 @@
             final PasswordMetrics metrics = ap.minimumPasswordMetrics;
             if (metrics.numeric != length) {
                 metrics.numeric = length;
-                updatePasswordValidityCheckpointLocked(userId);
-                saveSettingsLocked(userId);
+                updatePasswordValidityCheckpointLocked(userId, parent);
             }
             maybeLogPasswordComplexitySet(who, userId, parent, metrics);
         }
@@ -4318,8 +4317,7 @@
             final PasswordMetrics metrics = ap.minimumPasswordMetrics;
             if (metrics.symbols != length) {
                 ap.minimumPasswordMetrics.symbols = length;
-                updatePasswordValidityCheckpointLocked(userId);
-                saveSettingsLocked(userId);
+                updatePasswordValidityCheckpointLocked(userId, parent);
             }
             maybeLogPasswordComplexitySet(who, userId, parent, metrics);
         }
@@ -4344,8 +4342,7 @@
             final PasswordMetrics metrics = ap.minimumPasswordMetrics;
             if (metrics.nonLetter != length) {
                 ap.minimumPasswordMetrics.nonLetter = length;
-                updatePasswordValidityCheckpointLocked(userId);
-                saveSettingsLocked(userId);
+                updatePasswordValidityCheckpointLocked(userId, parent);
             }
             maybeLogPasswordComplexitySet(who, userId, parent, metrics);
         }
@@ -4566,16 +4563,6 @@
 
     private boolean isActivePasswordSufficientForUserLocked(
             DevicePolicyData policy, int userHandle, boolean parent) {
-        final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent);
-        if (requiredPasswordQuality == PASSWORD_QUALITY_UNSPECIFIED) {
-            // A special case is when there is no required password quality, then we just return
-            // true since any password would be sufficient. This is for the scenario when a work
-            // profile is first created so there is no information about the current password but
-            // it should be considered sufficient as there is no password requirement either.
-            // This is useful since it short-circuits the password checkpoint for FDE device below.
-            return true;
-        }
-
         if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()
                 && !policy.mPasswordStateHasBeenSetSinceBoot) {
             // Before user enters their password for the first time after a reboot, return the
@@ -4586,28 +4573,41 @@
             return policy.mPasswordValidAtLastCheckpoint;
         }
 
-        if (policy.mActivePasswordMetrics.quality < requiredPasswordQuality) {
+        return isPasswordSufficientForUserWithoutCheckpointLocked(
+                policy.mActivePasswordMetrics, userHandle, parent);
+    }
+
+    /**
+     * Returns {@code true} if the password represented by the {@code passwordMetrics} argument
+     * sufficiently fulfills the password requirements for the user corresponding to
+     * {@code userHandle} (or its parent, if {@code parent} is set to {@code true}).
+     */
+    private boolean isPasswordSufficientForUserWithoutCheckpointLocked(
+            PasswordMetrics passwordMetrics, int userHandle, boolean parent) {
+        final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent);
+
+        if (passwordMetrics.quality < requiredPasswordQuality) {
             return false;
         }
         if (requiredPasswordQuality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
-                && policy.mActivePasswordMetrics.length < getPasswordMinimumLength(
+                && passwordMetrics.length < getPasswordMinimumLength(
                         null, userHandle, parent)) {
             return false;
         }
         if (requiredPasswordQuality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
             return true;
         }
-        return policy.mActivePasswordMetrics.upperCase >= getPasswordMinimumUpperCase(
+        return passwordMetrics.upperCase >= getPasswordMinimumUpperCase(
                     null, userHandle, parent)
-                && policy.mActivePasswordMetrics.lowerCase >= getPasswordMinimumLowerCase(
+                && passwordMetrics.lowerCase >= getPasswordMinimumLowerCase(
                         null, userHandle, parent)
-                && policy.mActivePasswordMetrics.letters >= getPasswordMinimumLetters(
+                && passwordMetrics.letters >= getPasswordMinimumLetters(
                         null, userHandle, parent)
-                && policy.mActivePasswordMetrics.numeric >= getPasswordMinimumNumeric(
+                && passwordMetrics.numeric >= getPasswordMinimumNumeric(
                         null, userHandle, parent)
-                && policy.mActivePasswordMetrics.symbols >= getPasswordMinimumSymbols(
+                && passwordMetrics.symbols >= getPasswordMinimumSymbols(
                         null, userHandle, parent)
-                && policy.mActivePasswordMetrics.nonLetter >= getPasswordMinimumNonLetter(
+                && passwordMetrics.nonLetter >= getPasswordMinimumNonLetter(
                         null, userHandle, parent);
     }
 
@@ -6148,8 +6148,7 @@
         try {
             synchronized (this) {
                 policy.mFailedPasswordAttempts = 0;
-                updatePasswordValidityCheckpointLocked(userId);
-                saveSettingsLocked(userId);
+                updatePasswordValidityCheckpointLocked(userId, /* parent */ false);
                 updatePasswordExpirationsLocked(userId);
                 setExpirationAlarmCheckLocked(mContext, userId, /* parent */ false);
 
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index ccde049..4f49a4a 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -33,7 +33,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import android.support.test.filters.FlakyTest;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -302,7 +301,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 37908381)
     public void testFocusedWindowMultipleDisplays() throws Exception {
         // Create a focusable window and check that focus is calculated correctly
         final WindowState window1 =
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index fc459a0..4c70466 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -27,6 +27,7 @@
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
+    <uses-permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" />
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index ac28227..4a4260d 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -22,6 +22,7 @@
 #include "format/binary/TableFlattener.h"
 #include "format/binary/XmlFlattener.h"
 #include "format/proto/ProtoDeserialize.h"
+#include "format/proto/ProtoSerialize.h"
 #include "io/BigBufferStream.h"
 #include "io/Util.h"
 #include "xml/XmlDom.h"
@@ -110,7 +111,7 @@
     return {};
   }
   return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
-                                      std::move(manifest));
+                                      std::move(manifest), ApkFormat::kProto);
 }
 
 std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
@@ -153,7 +154,7 @@
     return {};
   }
   return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
-                                      std::move(manifest));
+                                      std::move(manifest), ApkFormat::kBinary);
 }
 
 bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
@@ -205,7 +206,7 @@
     }
 
     // The resource table needs to be re-serialized since it might have changed.
-    if (path == "resources.arsc") {
+    if (format_ == ApkFormat::kBinary && path == kApkResourceTablePath) {
       BigBuffer buffer(4096);
       // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
       // with sparse entries) b/35389232.
@@ -215,11 +216,22 @@
       }
 
       io::BigBufferInputStream input_stream(&buffer);
-      if (!io::CopyInputStreamToArchive(context, &input_stream, path, ArchiveEntry::kAlign,
+      if (!io::CopyInputStreamToArchive(context,
+                                        &input_stream,
+                                        path,
+                                        ArchiveEntry::kAlign,
                                         writer)) {
         return false;
       }
-
+    } else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) {
+      pb::ResourceTable pb_table;
+      SerializeTableToPb(*split_table, &pb_table);
+      if (!io::CopyProtoToArchive(context,
+                                  &pb_table,
+                                  path,
+                                  ArchiveEntry::kAlign, writer)) {
+        return false;
+      }
     } else if (manifest != nullptr && path == "AndroidManifest.xml") {
       BigBuffer buffer(8192);
       XmlFlattenerOptions xml_flattener_options;
@@ -246,9 +258,9 @@
 }
 
 ApkFormat LoadedApk::DetermineApkFormat(io::IFileCollection* apk) {
-  if (apk->FindFile("resources.arsc") != nullptr) {
+  if (apk->FindFile(kApkResourceTablePath) != nullptr) {
     return ApkFormat::kBinary;
-  } else if (apk->FindFile("resources.pb") != nullptr) {
+  } else if (apk->FindFile(kProtoResourceTablePath) != nullptr) {
     return ApkFormat::kProto;
   } else {
     // If the resource table is not present, attempt to read the manifest.
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index 81bcecc..41f879d 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -57,11 +57,13 @@
       const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag);
 
   LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
-            std::unique_ptr<ResourceTable> table, std::unique_ptr<xml::XmlResource> manifest)
+            std::unique_ptr<ResourceTable> table, std::unique_ptr<xml::XmlResource> manifest,
+            const ApkFormat& format)
       : source_(source),
         apk_(std::move(apk)),
         table_(std::move(table)),
-        manifest_(std::move(manifest)) {
+        manifest_(std::move(manifest)),
+        format_(format) {
   }
 
   io::IFileCollection* GetFileCollection() {
@@ -112,6 +114,7 @@
   std::unique_ptr<io::IFileCollection> apk_;
   std::unique_ptr<ResourceTable> table_;
   std::unique_ptr<xml::XmlResource> manifest_;
+  ApkFormat format_;
 
   static ApkFormat DetermineApkFormat(io::IFileCollection* apk);
 };
diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
index e1d951f..80eb737 100644
--- a/tools/aapt2/optimize/MultiApkGenerator_test.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
@@ -104,7 +104,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}, kBinary};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build();
   FilterChain chain;
 
@@ -131,7 +131,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}, kBinary};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
   FilterChain chain;
 
@@ -156,7 +156,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterNoVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}, kBinary};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
   FilterChain chain;