Merge "Clear slice access grants on package clear/remove"
diff --git a/api/current.txt b/api/current.txt
index bbe3c49..d415f52 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22150,6 +22150,20 @@
     field public static final android.os.Parcelable.Creator<android.media.AudioPlaybackConfiguration> CREATOR;
   }
 
+  public final class AudioPresentation {
+    method public java.util.Map<java.util.Locale, java.lang.String> getLabels();
+    method public java.util.Locale getLocale();
+    method public int getMasteringIndication();
+    method public boolean hasAudioDescription();
+    method public boolean hasDialogueEnhancement();
+    method public boolean hasSpokenSubtitles();
+    field public static final int MASTERED_FOR_3D = 3; // 0x3
+    field public static final int MASTERED_FOR_HEADPHONE = 4; // 0x4
+    field public static final int MASTERED_FOR_STEREO = 1; // 0x1
+    field public static final int MASTERED_FOR_SURROUND = 2; // 0x2
+    field public static final int MASTERING_NOT_INDICATED = 0; // 0x0
+  }
+
   public class AudioRecord implements android.media.AudioRouting {
     ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
     method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
@@ -22316,6 +22330,7 @@
     method public int setPlaybackRate(int);
     method public int setPositionNotificationPeriod(int);
     method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
+    method public int setPresentation(android.media.AudioPresentation);
     method protected deprecated void setState(int);
     method public deprecated int setStereoVolume(float, float);
     method public void setStreamEventCallback(java.util.concurrent.Executor, android.media.AudioTrack.StreamEventCallback);
@@ -23472,6 +23487,7 @@
     ctor public MediaExtractor();
     method public boolean advance();
     method protected void finalize();
+    method public java.util.List<android.media.AudioPresentation> getAudioPresentations(int);
     method public long getCachedDuration();
     method public android.media.MediaExtractor.CasInfo getCasInfo(int);
     method public android.media.DrmInitData getDrmInitData();
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index eabbb96..76dbd6a 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -165,6 +165,7 @@
 
 LOCAL_SRC_FILES := \
     $(statsd_common_src) \
+    tests/dimension_test.cpp \
     tests/AnomalyMonitor_test.cpp \
     tests/anomaly/AnomalyTracker_test.cpp \
     tests/ConfigManager_test.cpp \
@@ -190,7 +191,8 @@
     tests/e2e/WakelockDuration_e2e_test.cpp \
     tests/e2e/MetricConditionLink_e2e_test.cpp \
     tests/e2e/Attribution_e2e_test.cpp \
-    tests/e2e/GaugeMetric_e2e_test.cpp
+    tests/e2e/GaugeMetric_e2e_test.cpp \
+    tests/e2e/DimensionInCondition_e2e_test.cpp
 
 LOCAL_STATIC_LIBRARIES := \
     $(statsd_common_static_libraries) \
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 857a6dd..f0eaeff 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -67,12 +67,16 @@
     return hashDimensionsValue(0, value);
 }
 
+android::hash_t hashMetricDimensionKey(int64_t seed, const MetricDimensionKey& dimensionKey) {
+    android::hash_t hash = seed;
+    hash = android::JenkinsHashMix(hash, std::hash<MetricDimensionKey>{}(dimensionKey));
+    return JenkinsHashWhiten(hash);
+}
+
 using std::string;
 
 string HashableDimensionKey::toString() const {
-    string flattened;
-    DimensionsValueToString(getDimensionsValue(), &flattened);
-    return flattened;
+    return DimensionsValueToString(getDimensionsValue());
 }
 
 bool EqualsTo(const DimensionsValue& s1, const DimensionsValue& s2) {
@@ -162,6 +166,22 @@
     return LessThan(getDimensionsValue(), that.getDimensionsValue());
 };
 
+string MetricDimensionKey::toString() const {
+    string flattened = mDimensionKeyInWhat.toString();
+    flattened += mDimensionKeyInCondition.toString();
+    return flattened;
+}
+
+bool MetricDimensionKey::operator==(const MetricDimensionKey& that) const {
+    return mDimensionKeyInWhat == that.getDimensionKeyInWhat() &&
+        mDimensionKeyInCondition == that.getDimensionKeyInCondition();
+};
+
+bool MetricDimensionKey::operator<(const MetricDimensionKey& that) const {
+    return toString().compare(that.toString()) < 0;
+};
+
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 85c317f..a31d7a6 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -41,6 +41,10 @@
         return mDimensionsValue;
     }
 
+    inline DimensionsValue* getMutableDimensionsValue() {
+        return &mDimensionsValue;
+    }
+
     bool operator==(const HashableDimensionKey& that) const;
 
     bool operator<(const HashableDimensionKey& that) const;
@@ -53,8 +57,52 @@
     DimensionsValue mDimensionsValue;
 };
 
+class MetricDimensionKey {
+ public:
+    explicit MetricDimensionKey(const HashableDimensionKey& dimensionKeyInWhat,
+                                const HashableDimensionKey& dimensionKeyInCondition)
+        : mDimensionKeyInWhat(dimensionKeyInWhat),
+          mDimensionKeyInCondition(dimensionKeyInCondition) {};
+
+    MetricDimensionKey(){};
+
+    MetricDimensionKey(const MetricDimensionKey& that)
+        : mDimensionKeyInWhat(that.getDimensionKeyInWhat()),
+          mDimensionKeyInCondition(that.getDimensionKeyInCondition()) {};
+
+    MetricDimensionKey& operator=(const MetricDimensionKey& from) = default;
+
+    std::string toString() const;
+
+    inline const HashableDimensionKey& getDimensionKeyInWhat() const {
+        return mDimensionKeyInWhat;
+    }
+
+    inline const HashableDimensionKey& getDimensionKeyInCondition() const {
+        return mDimensionKeyInCondition;
+    }
+
+    bool hasDimensionKeyInCondition() const {
+        return mDimensionKeyInCondition.getDimensionsValue().has_field();
+    }
+
+    bool operator==(const MetricDimensionKey& that) const;
+
+    bool operator<(const MetricDimensionKey& that) const;
+
+    inline const char* c_str() const {
+        return toString().c_str();
+    }
+  private:
+      HashableDimensionKey mDimensionKeyInWhat;
+      HashableDimensionKey mDimensionKeyInCondition;
+};
+
+bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2);
+
 android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value);
 android::hash_t hashDimensionsValue(const DimensionsValue& value);
+android::hash_t hashMetricDimensionKey(int64_t see, const MetricDimensionKey& dimensionKey);
 
 }  // namespace statsd
 }  // namespace os
@@ -63,6 +111,7 @@
 namespace std {
 
 using android::os::statsd::HashableDimensionKey;
+using android::os::statsd::MetricDimensionKey;
 
 template <>
 struct hash<HashableDimensionKey> {
@@ -71,4 +120,14 @@
     }
 };
 
-}  // namespace std
+template <>
+struct hash<MetricDimensionKey> {
+    std::size_t operator()(const MetricDimensionKey& key) const {
+        android::hash_t hash = hashDimensionsValue(
+            key.getDimensionKeyInWhat().getDimensionsValue());
+        hash = android::JenkinsHashMix(hash,
+                    hashDimensionsValue(key.getDimensionKeyInCondition().getDimensionsValue()));
+        return android::JenkinsHashWhiten(hash);
+    }
+};
+}  // namespace std
\ No newline at end of file
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index c19ff63..7642aafa 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -104,7 +104,10 @@
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
     FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
-
+    FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink);
+    FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink);
+    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink);
+    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index ded6c4c..c84a5b4 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -96,7 +96,7 @@
     }
 }
 
-void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
+void AnomalyTracker::addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue,
                                    const int64_t& bucketNum) {
     flushPastBuckets(bucketNum);
 
@@ -147,7 +147,7 @@
     }
 }
 
-int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
+int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key,
                                            const int64_t& bucketNum) const {
     const auto& bucket = mPastBuckets[index(bucketNum)];
     if (bucket == nullptr) {
@@ -157,7 +157,7 @@
     return itr == bucket->end() ? 0 : itr->second;
 }
 
-int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
+int64_t AnomalyTracker::getSumOverPastBuckets(const MetricDimensionKey& key) const {
     const auto& itr = mSumOverPastBuckets.find(key);
     if (itr != mSumOverPastBuckets.end()) {
         return itr->second;
@@ -165,7 +165,7 @@
     return 0;
 }
 
-bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
+bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const MetricDimensionKey& key,
                                    const int64_t& currentBucketValue) {
     if (currentBucketNum > mMostRecentBucketNum + 1) {
         // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this.
@@ -175,7 +175,7 @@
             && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
 }
 
-void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key) {
+void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key) {
     // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
     if (isInRefractoryPeriod(timestampNs, key)) {
         VLOG("Skipping anomaly declaration since within refractory period");
@@ -199,14 +199,14 @@
 
     StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
 
-    // TODO: This should also take in the const HashableDimensionKey& key?
+    // TODO: This should also take in the const MetricDimensionKey& key?
     android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
                                mConfigKey.GetId(), mAlert.id());
 }
 
 void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
                                              const int64_t& currBucketNum,
-                                             const HashableDimensionKey& key,
+                                             const MetricDimensionKey& key,
                                              const int64_t& currentBucketValue) {
     if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
         declareAnomaly(timestampNs, key);
@@ -214,7 +214,7 @@
 }
 
 bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs,
-                                          const HashableDimensionKey& key) {
+                                          const MetricDimensionKey& key) {
     const auto& it = mRefractoryPeriodEndsSec.find(key);
     if (it != mRefractoryPeriodEndsSec.end()) {
         if ((timestampNs / NS_PER_SEC) <= it->second) {
@@ -226,7 +226,7 @@
     return false;
 }
 
-void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) {
+void AnomalyTracker::informSubscribers(const MetricDimensionKey& key) {
     VLOG("informSubscribers called.");
     if (mSubscriptions.empty()) {
         ALOGE("Attempt to call with no subscribers.");
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index 472c02c..f01a97f 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -48,19 +48,19 @@
     // Adds a bucket.
     // Bucket index starts from 0.
     void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum);
-    void addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
+    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.
-    bool detectAnomaly(const int64_t& currBucketNum, const HashableDimensionKey& key,
+    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 HashableDimensionKey& key);
+    void declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key);
 
     // Detects the alert and informs the incidentd when applicable.
     void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
-                                 const HashableDimensionKey& key,
+                                 const MetricDimensionKey& key,
                                  const int64_t& currentBucketValue);
 
     // Init the AnomalyMonitor which is shared across anomaly trackers.
@@ -69,10 +69,10 @@
     }
 
     // Helper function to return the sum value of past buckets at given dimension.
-    int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const;
+    int64_t getSumOverPastBuckets(const MetricDimensionKey& key) const;
 
     // Helper function to return the value for a past bucket.
-    int64_t getPastBucketValue(const HashableDimensionKey& key, const int64_t& bucketNum) const;
+    int64_t getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const;
 
     // Returns the anomaly threshold.
     inline int64_t getAnomalyThreshold() const {
@@ -81,7 +81,7 @@
 
     // Returns the refractory period timestamp (in seconds) for the given key.
     // If there is no stored refractory period ending timestamp, returns 0.
-    uint32_t getRefractoryPeriodEndsSec(const HashableDimensionKey& key) const {
+    uint32_t getRefractoryPeriodEndsSec(const MetricDimensionKey& key) const {
         const auto& it = mRefractoryPeriodEndsSec.find(key);
         return it != mRefractoryPeriodEndsSec.end() ? it->second : 0;
     }
@@ -124,7 +124,7 @@
     // declared for that dimension) ends, in seconds. Only anomalies that occur after this period
     // ends will be declared.
     // Entries may be, but are not guaranteed to be, removed after the period is finished.
-    unordered_map<HashableDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
+    unordered_map<MetricDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
 
     void flushPastBuckets(const int64_t& currBucketNum);
 
@@ -135,7 +135,7 @@
     // and remove any items with value 0.
     void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
 
-    bool isInRefractoryPeriod(const uint64_t& timestampNs, const HashableDimensionKey& key);
+    bool isInRefractoryPeriod(const uint64_t& timestampNs, const MetricDimensionKey& key);
 
     // Calculates the corresponding bucket index within the circular array.
     size_t index(int64_t bucketNum) const;
@@ -144,7 +144,7 @@
     virtual void resetStorage();
 
     // Informs the subscribers that an anomaly has occurred.
-    void informSubscribers(const HashableDimensionKey& key);
+    void informSubscribers(const MetricDimensionKey& key);
 
     FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
     FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
index 7576a38..bbee9fa 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
@@ -37,8 +37,8 @@
     if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!");
 }
 
-void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
-                                                  const uint64_t& timestampNs) {
+void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const MetricDimensionKey& dimensionKey,
+                                                          const uint64_t& timestampNs) {
     auto itr = mAlarms.find(dimensionKey);
     if (itr == mAlarms.end()) {
         return;
@@ -51,7 +51,7 @@
     }
 }
 
-void DurationAnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
+void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey,
                                 const uint64_t& timestampNs) {
 
     uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
@@ -66,7 +66,7 @@
     }
 }
 
-void DurationAnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
+void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey) {
     auto itr = mAlarms.find(dimensionKey);
     if (itr != mAlarms.end()) {
         mAlarms.erase(dimensionKey);
@@ -77,7 +77,7 @@
 }
 
 void DurationAnomalyTracker::stopAllAlarms() {
-    std::set<HashableDimensionKey> keys;
+    std::set<MetricDimensionKey> keys;
     for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
         keys.insert(itr->first);
     }
@@ -95,7 +95,7 @@
     // seldomly called. The alternative would be having AnomalyAlarms store information about the
     // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is
     // rarely ever called.
-    unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
+    unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
     for (const auto& kv : mAlarms) {
         if (firedAlarms.count(kv.second) > 0) {
             matchedAlarms.insert({kv.first, kv.second});
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
index 33e55ab..052fdf57 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -32,10 +32,10 @@
     virtual ~DurationAnomalyTracker();
 
     // Starts the alarm at the given timestamp.
-    void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime);
+    void startAlarm(const MetricDimensionKey& dimensionKey, const uint64_t& eventTime);
 
     // Stops the alarm.
-    void stopAlarm(const HashableDimensionKey& dimensionKey);
+    void stopAlarm(const MetricDimensionKey& dimensionKey);
 
     // Stop all the alarms owned by this tracker.
     void stopAllAlarms();
@@ -46,7 +46,7 @@
     }
 
     // Declares the anomaly when the alarm expired given the current timestamp.
-    void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
+    void declareAnomalyIfAlarmExpired(const MetricDimensionKey& dimensionKey,
                                       const uint64_t& timestampNs);
 
     // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker
@@ -59,7 +59,7 @@
 protected:
     // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
     // are still active.
-    std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms;
+    std::unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> mAlarms;
 
     // Anomaly alarm monitor.
     sp<AnomalyMonitor> mAnomalyMonitor;
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index ea6586c..4c20ccb 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -78,6 +78,7 @@
             return false;
         }
 
+
         bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers,
                                                      conditionIdIndexMap, stack);
 
@@ -88,8 +89,10 @@
             ALOGW("Child initialization success %lld ", (long long)child);
         }
 
+        if (allConditionTrackers[childIndex]->isSliced()) {
+            setSliced(true);
+        }
         mChildren.push_back(childIndex);
-
         mTrackerIndex.insert(childTracker->getLogTrackerIndex().begin(),
                              childTracker->getLogTrackerIndex().end());
     }
@@ -105,11 +108,15 @@
 void CombinationConditionTracker::isConditionMet(
         const ConditionKey& conditionParameters,
         const vector<sp<ConditionTracker>>& allConditions,
-        vector<ConditionState>& conditionCache) const {
+        const FieldMatcher& dimensionFields,
+        vector<ConditionState>& conditionCache,
+        std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const {
+    // So far, this is fine as there is at most one child having sliced output.
     for (const int childIndex : mChildren) {
         if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
             allConditions[childIndex]->isConditionMet(conditionParameters, allConditions,
-                                                      conditionCache);
+                                                      dimensionFields, conditionCache,
+                                                      dimensionsKeySet);
         }
     }
     conditionCache[mIndex] =
@@ -127,6 +134,7 @@
     }
 
     for (const int childIndex : mChildren) {
+        // So far, this is fine as there is at most one child having sliced output.
         if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) {
             const sp<ConditionTracker>& child = mAllConditions[childIndex];
             child->evaluateCondition(event, eventMatcherValues, mAllConditions,
@@ -159,6 +167,24 @@
     }
 }
 
+ConditionState CombinationConditionTracker::getMetConditionDimension(
+        const std::vector<sp<ConditionTracker>>& allConditions,
+        const FieldMatcher& dimensionFields,
+        std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const {
+    vector<ConditionState> conditionCache(allConditions.size(), ConditionState::kNotEvaluated);
+    // So far, this is fine as there is at most one child having sliced output.
+    for (const int childIndex : mChildren) {
+        conditionCache[childIndex] = conditionCache[childIndex] |
+            allConditions[childIndex]->getMetConditionDimension(
+                allConditions, dimensionFields, dimensionsKeySet);
+    }
+    evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
+    if (conditionCache[mIndex] == ConditionState::kTrue && dimensionsKeySet.empty()) {
+        dimensionsKeySet.insert(DEFAULT_DIMENSION_KEY);
+    }
+    return conditionCache[mIndex];
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index dfd3837..0b7f949 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -41,12 +41,20 @@
                            std::vector<ConditionState>& conditionCache,
                            std::vector<bool>& changedCache) override;
 
-    void isConditionMet(const ConditionKey& conditionParameters,
-                        const std::vector<sp<ConditionTracker>>& allConditions,
-                        std::vector<ConditionState>& conditionCache) const override;
+    void isConditionMet(
+        const ConditionKey& conditionParameters,
+        const std::vector<sp<ConditionTracker>>& allConditions,
+        const FieldMatcher& dimensionFields,
+        std::vector<ConditionState>& conditionCache,
+        std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override;
 
+    ConditionState getMetConditionDimension(
+            const std::vector<sp<ConditionTracker>>& allConditions,
+            const FieldMatcher& dimensionFields,
+            std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override;
 private:
     LogicalOperation mLogicalOperation;
+
     // Store index of the children Predicates.
     // We don't store string name of the Children, because we want to get rid of the hash map to
     // map the name to object. We don't want to store smart pointers to children, because it
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 773860f..81abbdb 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -24,6 +24,7 @@
 #include <log/logprint.h>
 #include <utils/RefBase.h>
 
+#include <unordered_set>
 #include <unordered_map>
 
 namespace android {
@@ -84,10 +85,19 @@
     // [allConditions]: all condition trackers. This is needed because the condition evaluation is
     //                  done recursively
     // [conditionCache]: the cache holding the condition evaluation values.
+    // [dimensionsKeySet]: the dimensions where the sliced condition is true. For combination
+    //                    condition, it assumes that only one child predicate is sliced.
     virtual void isConditionMet(
             const ConditionKey& conditionParameters,
             const std::vector<sp<ConditionTracker>>& allConditions,
-            std::vector<ConditionState>& conditionCache) const = 0;
+            const FieldMatcher& dimensionFields,
+            std::vector<ConditionState>& conditionCache,
+            std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const = 0;
+
+    virtual ConditionState getMetConditionDimension(
+            const std::vector<sp<ConditionTracker>>& allConditions,
+            const FieldMatcher& dimensionFields,
+            std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const = 0;
 
     // return the list of LogMatchingTracker index that this ConditionTracker uses.
     virtual const std::set<int>& getLogTrackerIndex() const {
@@ -98,6 +108,10 @@
         mSliced = mSliced | sliced;
     }
 
+    bool isSliced() const {
+        return mSliced;
+    }
+
 protected:
     const int64_t mConditionId;
 
diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp
index d99c2cc..0427700 100644
--- a/cmds/statsd/src/condition/ConditionWizard.cpp
+++ b/cmds/statsd/src/condition/ConditionWizard.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 #include "ConditionWizard.h"
+#include <unordered_set>
 
 namespace android {
 namespace os {
@@ -23,14 +24,26 @@
 using std::string;
 using std::vector;
 
-ConditionState ConditionWizard::query(const int index,
-                                      const ConditionKey& parameters) {
+ConditionState ConditionWizard::query(
+    const int index, const ConditionKey& parameters,
+    const FieldMatcher& dimensionFields,
+    std::unordered_set<HashableDimensionKey> *dimensionKeySet) {
+
     vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
 
-    mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache);
+    mAllConditions[index]->isConditionMet(
+        parameters, mAllConditions, dimensionFields, cache, *dimensionKeySet);
     return cache[index];
 }
 
+ConditionState ConditionWizard::getMetConditionDimension(
+    const int index, const FieldMatcher& dimensionFields,
+    std::unordered_set<HashableDimensionKey> *dimensionsKeySet) const {
+
+    return mAllConditions[index]->getMetConditionDimension(mAllConditions, dimensionFields,
+                                 *dimensionsKeySet);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
index 4ff5c07..b38b59f 100644
--- a/cmds/statsd/src/condition/ConditionWizard.h
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -41,7 +41,14 @@
     // the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
     virtual ConditionState query(
             const int conditionIndex,
-            const ConditionKey& conditionParameters);
+            const ConditionKey& conditionParameters,
+            const FieldMatcher& dimensionFields,
+            std::unordered_set<HashableDimensionKey> *dimensionKeySet);
+
+    virtual ConditionState getMetConditionDimension(
+            const int index,
+            const FieldMatcher& dimensionFields,
+            std::unordered_set<HashableDimensionKey> *dimensionsKeySet) const;
 
 private:
     std::vector<sp<ConditionTracker>> mAllConditions;
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index 5cfc349..25265d5 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -104,6 +104,9 @@
                                   vector<bool>& stack) {
     // SimpleConditionTracker does not have dependency on other conditions, thus we just return
     // if the initialization was successful.
+    if (mOutputDimensions.has_field() || mOutputDimensions.child_size() > 0) {
+        setSliced(true);
+    }
     return mInitialized;
 }
 
@@ -234,11 +237,12 @@
          conditionChangedCache[mIndex] == true);
 }
 
-void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
-                                               const vector<MatchingState>& eventMatcherValues,
-                                               const vector<sp<ConditionTracker>>& mAllConditions,
-                                               vector<ConditionState>& conditionCache,
-                                               vector<bool>& conditionChangedCache) {
+void SimpleConditionTracker::evaluateCondition(
+        const LogEvent& event,
+        const vector<MatchingState>& eventMatcherValues,
+        const vector<sp<ConditionTracker>>& mAllConditions,
+        vector<ConditionState>& conditionCache,
+        vector<bool>& conditionChangedCache) {
     if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
         // it has been evaluated.
         VLOG("Yes, already evaluated, %lld %d",
@@ -271,7 +275,7 @@
         if (mSliced) {
             // if the condition result is sliced. metrics won't directly get value from the
             // cache, so just set any value other than kNotEvaluated.
-            conditionCache[mIndex] = ConditionState::kUnknown;
+            conditionCache[mIndex] = mInitialValue;
         } else {
             const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
             if (itr == mSlicedConditionState.end()) {
@@ -310,10 +314,8 @@
             vector<ConditionState> dimensionalConditionCache(conditionCache.size(),
                                                              ConditionState::kNotEvaluated);
             vector<bool> dimensionalConditionChangedCache(conditionChangedCache.size(), false);
-
             handleConditionEvent(HashableDimensionKey(outputValue), matchedState == 1,
                 dimensionalConditionCache, dimensionalConditionChangedCache);
-
             OrConditionState(dimensionalConditionCache, &conditionCache);
             OrBooleanVector(dimensionalConditionChangedCache, &conditionChangedCache);
         }
@@ -323,42 +325,73 @@
 void SimpleConditionTracker::isConditionMet(
         const ConditionKey& conditionParameters,
         const vector<sp<ConditionTracker>>& allConditions,
-        vector<ConditionState>& conditionCache) const {
-    const auto pair = conditionParameters.find(mConditionId);
-
-    if (pair == conditionParameters.end() && mOutputDimensions.child_size() > 0) {
-        ALOGE("Predicate %lld output has dimension, but it's not specified in the query!",
-              (long long)mConditionId);
-        conditionCache[mIndex] = mInitialValue;
+        const FieldMatcher& dimensionFields,
+        vector<ConditionState>& conditionCache,
+        std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const {
+    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
+        // it has been evaluated.
+        VLOG("Yes, already evaluated, %lld %d",
+            (long long)mConditionId, conditionCache[mIndex]);
         return;
     }
-    std::vector<HashableDimensionKey> defaultKeys = {DEFAULT_DIMENSION_KEY};
+    const auto pair = conditionParameters.find(mConditionId);
+
+    if (pair == conditionParameters.end()) {
+        ConditionState conditionState = ConditionState::kNotEvaluated;
+        if (dimensionFields.has_field() && dimensionFields.child_size() > 0 &&
+            dimensionFields.field() == mOutputDimensions.field()) {
+            conditionState = conditionState | getMetConditionDimension(
+                allConditions, dimensionFields, dimensionsKeySet);
+        } else {
+            conditionState = conditionState | mInitialValue;
+            if (!mSliced) {
+                const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
+                if (itr != mSlicedConditionState.end()) {
+                    ConditionState sliceState =
+                        itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+                    conditionState = conditionState | sliceState;
+                }
+            }
+        }
+        conditionCache[mIndex] = conditionState;
+        return;
+    }
+    std::vector<HashableDimensionKey> defaultKeys = { DEFAULT_DIMENSION_KEY };
     const std::vector<HashableDimensionKey> &keys =
             (pair == conditionParameters.end()) ? defaultKeys : pair->second;
 
     ConditionState conditionState = ConditionState::kNotEvaluated;
-    for (const auto& key : keys) {
+    for (size_t i = 0; i < keys.size(); ++i) {
+        const HashableDimensionKey& key = keys[i];
         auto startedCountIt = mSlicedConditionState.find(key);
         if (startedCountIt != mSlicedConditionState.end()) {
-            conditionState = conditionState |
-                    (startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse);
+            ConditionState sliceState =
+                startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+            conditionState = conditionState | sliceState;
+            if (sliceState == ConditionState::kTrue && dimensionFields.has_field()) {
+                HashableDimensionKey dimensionKey;
+                if (getSubDimension(startedCountIt->first.getDimensionsValue(), dimensionFields,
+                                    dimensionKey.getMutableDimensionsValue())) {
+                    dimensionsKeySet.insert(dimensionKey);
+                }
+            }
         } else {
             // For unseen key, check whether the require dimensions are subset of sliced condition
             // output.
-            bool seenDimension = false;
+            conditionState = conditionState | mInitialValue;
             for (const auto& slice : mSlicedConditionState) {
-                if (IsSubDimension(slice.first.getDimensionsValue(),
-                                   key.getDimensionsValue())) {
-                    seenDimension = true;
-                    conditionState = conditionState |
-                        (slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse);
+                ConditionState sliceState =
+                    slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+                if (IsSubDimension(slice.first.getDimensionsValue(), key.getDimensionsValue())) {
+                    conditionState = conditionState | sliceState;
+                    if (sliceState == ConditionState::kTrue && dimensionFields.has_field()) {
+                        HashableDimensionKey dimensionKey;
+                        if (getSubDimension(slice.first.getDimensionsValue(),
+                                            dimensionFields, dimensionKey.getMutableDimensionsValue())) {
+                            dimensionsKeySet.insert(dimensionKey);
+                        }
+                    }
                 }
-                if (conditionState == ConditionState::kTrue) {
-                    break;
-                }
-            }
-            if (!seenDimension) {
-                conditionState = conditionState | mInitialValue;
             }
         }
     }
@@ -366,6 +399,38 @@
     VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]);
 }
 
+ConditionState SimpleConditionTracker::getMetConditionDimension(
+        const std::vector<sp<ConditionTracker>>& allConditions,
+        const FieldMatcher& dimensionFields,
+        std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const {
+    ConditionState conditionState = mInitialValue;
+    if (!dimensionFields.has_field() ||
+        !mOutputDimensions.has_field() ||
+        dimensionFields.field() != mOutputDimensions.field()) {
+        const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
+        if (itr != mSlicedConditionState.end()) {
+            ConditionState sliceState =
+                itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+            conditionState = conditionState | sliceState;
+        }
+        return conditionState;
+    }
+
+    for (const auto& slice : mSlicedConditionState) {
+        ConditionState sliceState =
+            slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+        DimensionsValue dimensionsValue;
+        conditionState = conditionState | sliceState;
+        HashableDimensionKey dimensionKey;
+        if (sliceState == ConditionState::kTrue &&
+            getSubDimension(slice.first.getDimensionsValue(), dimensionFields,
+                            dimensionKey.getMutableDimensionsValue())) {
+            dimensionsKeySet.insert(dimensionKey);
+        }
+    }
+    return conditionState;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index 815b445..ce9a02d 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -48,7 +48,14 @@
 
     void isConditionMet(const ConditionKey& conditionParameters,
                         const std::vector<sp<ConditionTracker>>& allConditions,
-                        std::vector<ConditionState>& conditionCache) const override;
+                        const FieldMatcher& dimensionFields,
+                        std::vector<ConditionState>& conditionCache,
+                        std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override;
+
+    ConditionState getMetConditionDimension(
+            const std::vector<sp<ConditionTracker>>& allConditions,
+            const FieldMatcher& dimensionFields,
+            std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override;
 
 private:
     const ConfigKey mConfigKey;
@@ -73,7 +80,8 @@
     void handleStopAll(std::vector<ConditionState>& conditionCache,
                        std::vector<bool>& changedCache);
 
-    void handleConditionEvent(const HashableDimensionKey& outputKey, bool matchStart,
+    void handleConditionEvent(const HashableDimensionKey& outputKey,
+                              bool matchStart,
                               std::vector<ConditionState>& conditionCache,
                               std::vector<bool>& changedCache);
 
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
index 3b2d480..0ab33cf 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -118,6 +118,9 @@
 
 void getFieldsFromFieldMatcher(const FieldMatcher& matcher, Field* rootField, Field* leafField,
                                std::vector<Field> *allFields) {
+    if (matcher.has_position()) {
+        leafField->set_position_index(0);
+    }
     if (matcher.child_size() == 0) {
         allFields->push_back(*rootField);
         return;
diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp
index 04445ca..8a2e871 100644
--- a/cmds/statsd/src/dimension.cpp
+++ b/cmds/statsd/src/dimension.cpp
@@ -253,6 +253,9 @@
 }
 
 void DimensionsValueToString(const DimensionsValue& value, std::string *flattened) {
+    if (!value.has_field()) {
+        return;
+    }
     *flattened += std::to_string(value.field());
     *flattened += ":";
     switch (value.value_case()) {
@@ -352,6 +355,46 @@
     }
 }
 
+bool getSubDimension(const DimensionsValue& dimension, const FieldMatcher& matcher,
+                     DimensionsValue* subDimension) {
+    if (!matcher.has_field()) {
+        return false;
+    }
+    if (matcher.field() != dimension.field()) {
+        return false;
+    }
+    if (matcher.child_size() <= 0) {
+        if (dimension.value_case() == DimensionsValue::ValueCase::kValueTuple ||
+            dimension.value_case() == DimensionsValue::ValueCase::VALUE_NOT_SET) {
+            return false;
+        }
+        *subDimension = dimension;
+        return true;
+    } else {
+        if (dimension.value_case() != DimensionsValue::ValueCase::kValueTuple) {
+            return false;
+        }
+        bool found_value = true;
+        auto value_tuple = dimension.value_tuple();
+        subDimension->set_field(dimension.field());
+        for (int i = 0; found_value && i < matcher.child_size(); ++i) {
+            int j = 0;
+            for (; j < value_tuple.dimensions_value_size(); ++j) {
+                if (value_tuple.dimensions_value(j).field() == matcher.child(i).field()) {
+                    break;
+                }
+            }
+            if (j < value_tuple.dimensions_value_size()) {
+                found_value &= getSubDimension(value_tuple.dimensions_value(j), matcher.child(i),
+                    subDimension->mutable_value_tuple()->add_dimensions_value());
+            } else {
+                found_value = false;
+            }
+        }
+        return found_value;
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h
index e900c5e..138c6e9 100644
--- a/cmds/statsd/src/dimension.h
+++ b/cmds/statsd/src/dimension.h
@@ -63,6 +63,9 @@
 
 // Helper function to get long value from the DimensionsValue proto.
 long getLongFromDimenValue(const DimensionsValue& dimensionValue);
+
+bool getSubDimension(const DimensionsValue& dimension, const FieldMatcher& matcher,
+                    DimensionsValue* subDimension);
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 0455f6a..ae4df3e 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -21,6 +21,7 @@
 #include "guardrail/StatsdStats.h"
 #include "stats_util.h"
 #include "stats_log_util.h"
+#include "dimension.h"
 
 #include <limits.h>
 #include <stdlib.h>
@@ -71,13 +72,15 @@
     }
 
     // TODO: use UidMap if uid->pkg_name is required
-    mDimensions = metric.dimensions_in_what();
+    mDimensionsInWhat = metric.dimensions_in_what();
+    mDimensionsInCondition = metric.dimensions_in_condition();
 
     if (metric.links().size() > 0) {
         mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
                                metric.links().end());
-        mConditionSliced = true;
     }
+    mConditionSliced = (metric.links().size() > 0)||
+        (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0);
 
     VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
          (long long)mBucketSizeNs, (long long)mStartTimeNs);
@@ -99,7 +102,10 @@
     auto count_metrics = report->mutable_count_metrics();
     for (const auto& counter : mPastBuckets) {
         CountMetricData* metricData = count_metrics->add_data();
-        *metricData->mutable_dimensions_in_what() = counter.first.getDimensionsValue();
+        *metricData->mutable_dimensions_in_what() =
+            counter.first.getDimensionKeyInWhat().getDimensionsValue();
+        *metricData->mutable_dimensions_in_condition() =
+            counter.first.getDimensionKeyInCondition().getDimensionsValue();
         for (const auto& bucket : counter.second) {
             CountBucketInfo* bucketInfo = metricData->add_bucket_info();
             bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
@@ -123,17 +129,26 @@
     VLOG("metric %lld dump report now...",(long long)mMetricId);
 
     for (const auto& counter : mPastBuckets) {
-        const HashableDimensionKey& hashableKey = counter.first;
-        VLOG("  dimension key %s", hashableKey.c_str());
+        const MetricDimensionKey& dimensionKey = counter.first;
+        VLOG("  dimension key %s", dimensionKey.c_str());
 
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension.
-        long long dimensionToken = protoOutput->start(
+        long long dimensionInWhatToken = protoOutput->start(
                 FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
-        writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
-        protoOutput->end(dimensionToken);
+        writeDimensionsValueProtoToStream(
+            dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput);
+        protoOutput->end(dimensionInWhatToken);
+
+        if (dimensionKey.hasDimensionKeyInCondition()) {
+            long long dimensionInConditionToken = protoOutput->start(
+                    FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
+            writeDimensionsValueProtoToStream(
+                dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput);
+            protoOutput->end(dimensionInConditionToken);
+        }
 
         // Then fill bucket_info (CountBucketInfo).
         for (const auto& bucket : counter.second) {
@@ -166,7 +181,7 @@
     mCondition = conditionMet;
 }
 
-bool CountMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) {
+bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
     if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) {
         return false;
     }
@@ -187,7 +202,7 @@
 }
 
 void CountMetricProducer::onMatchedLogEventInternalLocked(
-        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const size_t matcherIndex, const MetricDimensionKey& eventKey,
         const ConditionKey& conditionKey, bool condition,
         const LogEvent& event) {
     uint64_t eventTimeNs = event.GetTimestampNs();
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 061b7a3..8659d47 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -50,7 +50,7 @@
 
 protected:
     void onMatchedLogEventInternalLocked(
-            const size_t matcherIndex, const HashableDimensionKey& eventKey,
+            const size_t matcherIndex, const MetricDimensionKey& eventKey,
             const ConditionKey& conditionKey, bool condition,
             const LogEvent& event) override;
 
@@ -74,14 +74,14 @@
     void flushIfNeededLocked(const uint64_t& newEventTime);
 
     // TODO: Add a lock to mPastBuckets.
-    std::unordered_map<HashableDimensionKey, std::vector<CountBucket>> mPastBuckets;
+    std::unordered_map<MetricDimensionKey, std::vector<CountBucket>> mPastBuckets;
 
     // The current bucket.
     std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>();
 
     static const size_t kBucketSize = sizeof(CountBucket{});
 
-    bool hitGuardRailLocked(const HashableDimensionKey& newKey);
+    bool hitGuardRailLocked(const MetricDimensionKey& newKey);
 
     FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 000874c..efbdae1 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -21,6 +21,7 @@
 #include "guardrail/StatsdStats.h"
 #include "stats_util.h"
 #include "stats_log_util.h"
+#include "dimension.h"
 
 #include <limits.h>
 #include <stdlib.h>
@@ -81,13 +82,15 @@
     }
 
     // TODO: use UidMap if uid->pkg_name is required
-    mDimensions = metric.dimensions_in_what();
+    mDimensionsInWhat = metric.dimensions_in_what();
+    mDimensionsInCondition = metric.dimensions_in_condition();
 
     if (metric.links().size() > 0) {
         mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
                                metric.links().end());
-        mConditionSliced = true;
     }
+    mConditionSliced = (metric.links().size() > 0)||
+        (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0);
 
     VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
          (long long)mBucketSizeNs, (long long)mStartTimeNs);
@@ -113,15 +116,17 @@
 }
 
 unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
-        const HashableDimensionKey& eventKey) const {
+        const MetricDimensionKey& eventKey) const {
     switch (mAggregationType) {
         case DurationMetric_AggregationType_SUM:
             return make_unique<OringDurationTracker>(
-                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
+                    mDimensionsInCondition, mNested,
                     mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
         case DurationMetric_AggregationType_MAX_SPARSE:
             return make_unique<MaxDurationTracker>(
-                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
+                    mDimensionsInCondition, mNested,
                     mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
     }
 }
@@ -129,10 +134,34 @@
 void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) {
     VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
     flushIfNeededLocked(eventTime);
+
     // Now for each of the on-going event, check if the condition has changed for them.
-    for (auto& pair : mCurrentSlicedDuration) {
+    for (auto& pair : mCurrentSlicedDurationTrackerMap) {
         pair.second->onSlicedConditionMayChange(eventTime);
     }
+
+
+    std::unordered_set<HashableDimensionKey> conditionDimensionsKeySet;
+    ConditionState conditionState = mWizard->getMetConditionDimension(
+        mConditionTrackerIndex, mDimensionsInCondition, &conditionDimensionsKeySet);
+
+    bool condition = (conditionState == ConditionState::kTrue);
+    for (auto& pair : mCurrentSlicedDurationTrackerMap) {
+        conditionDimensionsKeySet.erase(pair.first.getDimensionKeyInCondition());
+    }
+    std::unordered_set<MetricDimensionKey> newKeys;
+    for (const auto& conditionDimensionsKey : conditionDimensionsKeySet) {
+        for (auto& pair : mCurrentSlicedDurationTrackerMap) {
+            auto newKey =
+                MetricDimensionKey(pair.first.getDimensionKeyInWhat(), conditionDimensionsKey);
+            if (newKeys.find(newKey) == newKeys.end()) {
+                mCurrentSlicedDurationTrackerMap[newKey] = pair.second->clone(eventTime);
+                mCurrentSlicedDurationTrackerMap[newKey]->setEventKey(newKey);
+                mCurrentSlicedDurationTrackerMap[newKey]->onSlicedConditionMayChange(eventTime);
+            }
+            newKeys.insert(newKey);
+        }
+    }
 }
 
 void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet,
@@ -142,7 +171,7 @@
     flushIfNeededLocked(eventTime);
     // TODO: need to populate the condition change time from the event which triggers the condition
     // change, instead of using current time.
-    for (auto& pair : mCurrentSlicedDuration) {
+    for (auto& pair : mCurrentSlicedDurationTrackerMap) {
         pair.second->onConditionChanged(conditionMet, eventTime);
     }
 }
@@ -155,7 +184,10 @@
     auto duration_metrics = report->mutable_duration_metrics();
     for (const auto& pair : mPastBuckets) {
         DurationMetricData* metricData = duration_metrics->add_data();
-        *metricData->mutable_dimensions_in_what() = pair.first.getDimensionsValue();
+        *metricData->mutable_dimensions_in_what() =
+            pair.first.getDimensionKeyInWhat().getDimensionsValue();
+        *metricData->mutable_dimensions_in_condition() =
+            pair.first.getDimensionKeyInCondition().getDimensionsValue();
         for (const auto& bucket : pair.second) {
             auto bucketInfo = metricData->add_bucket_info();
             bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
@@ -179,8 +211,8 @@
     VLOG("metric %lld dump report now...", (long long)mMetricId);
 
     for (const auto& pair : mPastBuckets) {
-        const HashableDimensionKey& hashableKey = pair.first;
-        VLOG("  dimension key %s", hashableKey.c_str());
+        const MetricDimensionKey& dimensionKey = pair.first;
+        VLOG("  dimension key %s", dimensionKey.c_str());
 
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
@@ -188,9 +220,18 @@
         // First fill dimension.
         long long dimensionToken = protoOutput->start(
                 FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
-        writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
+        writeDimensionsValueProtoToStream(
+            dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput);
         protoOutput->end(dimensionToken);
 
+        if (dimensionKey.hasDimensionKeyInCondition()) {
+            long long dimensionInConditionToken = protoOutput->start(
+                    FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
+            writeDimensionsValueProtoToStream(
+                dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput);
+            protoOutput->end(dimensionInConditionToken);
+        }
+
         // Then fill bucket_info (DurationBucketInfo).
         for (const auto& bucket : pair.second) {
             long long bucketInfoToken = protoOutput->start(
@@ -219,10 +260,11 @@
         return;
     }
     VLOG("flushing...........");
-    for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end();) {
+    for (auto it = mCurrentSlicedDurationTrackerMap.begin();
+            it != mCurrentSlicedDurationTrackerMap.end();) {
         if (it->second->flushIfNeeded(eventTime, &mPastBuckets)) {
             VLOG("erase bucket for key %s", it->first.c_str());
-            it = mCurrentSlicedDuration.erase(it);
+            it = mCurrentSlicedDurationTrackerMap.erase(it);
         } else {
             ++it;
         }
@@ -234,28 +276,28 @@
 }
 
 void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
-    if (mCurrentSlicedDuration.size() == 0) {
+    if (mCurrentSlicedDurationTrackerMap.size() == 0) {
         return;
     }
 
     fprintf(out, "DurationMetric %lld dimension size %lu\n", (long long)mMetricId,
-            (unsigned long)mCurrentSlicedDuration.size());
+            (unsigned long)mCurrentSlicedDurationTrackerMap.size());
     if (verbose) {
-        for (const auto& slice : mCurrentSlicedDuration) {
+        for (const auto& slice : mCurrentSlicedDurationTrackerMap) {
             fprintf(out, "\t%s\n", slice.first.c_str());
             slice.second->dumpStates(out, verbose);
         }
     }
 }
 
-bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) {
+bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
     // the key is not new, we are good.
-    if (mCurrentSlicedDuration.find(newKey) != mCurrentSlicedDuration.end()) {
+    if (mCurrentSlicedDurationTrackerMap.find(newKey) != mCurrentSlicedDurationTrackerMap.end()) {
         return false;
     }
     // 1. Report the tuple count if the tuple count > soft limit
-    if (mCurrentSlicedDuration.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
-        size_t newTupleCount = mCurrentSlicedDuration.size() + 1;
+    if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+        size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1;
         StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
@@ -268,27 +310,26 @@
 }
 
 void DurationMetricProducer::onMatchedLogEventInternalLocked(
-        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const size_t matcherIndex, const MetricDimensionKey& eventKey,
         const ConditionKey& conditionKeys, bool condition,
         const LogEvent& event) {
     flushIfNeededLocked(event.GetTimestampNs());
 
     if (matcherIndex == mStopAllIndex) {
-        for (auto& pair : mCurrentSlicedDuration) {
+        for (auto& pair : mCurrentSlicedDurationTrackerMap) {
             pair.second->noteStopAll(event.GetTimestampNs());
         }
         return;
     }
 
-
-    if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
+    if (mCurrentSlicedDurationTrackerMap.find(eventKey) == mCurrentSlicedDurationTrackerMap.end()) {
         if (hitGuardRailLocked(eventKey)) {
             return;
         }
-        mCurrentSlicedDuration[eventKey] = createDurationTracker(eventKey);
+        mCurrentSlicedDurationTrackerMap[eventKey] = createDurationTracker(eventKey);
     }
 
-    auto it = mCurrentSlicedDuration.find(eventKey);
+    auto it = mCurrentSlicedDurationTrackerMap.find(eventKey);
 
     std::vector<DimensionsValue> values;
     getDimensionKeys(event, mInternalDimensions, &values);
@@ -302,10 +343,11 @@
     } else {
         for (const DimensionsValue& value : values) {
             if (matcherIndex == mStartIndex) {
-                it->second->noteStart(HashableDimensionKey(value), condition,
-                                      event.GetTimestampNs(), conditionKeys);
+                it->second->noteStart(
+                    HashableDimensionKey(value), condition, event.GetTimestampNs(), conditionKeys);
             } else if (matcherIndex == mStopIndex) {
-                it->second->noteStop(HashableDimensionKey(value), event.GetTimestampNs(), false);
+                it->second->noteStop(
+                   HashableDimensionKey(value), event.GetTimestampNs(), false);
             }
         }
     }
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index d8cab92..152e570 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -50,7 +50,7 @@
 
 protected:
     void onMatchedLogEventInternalLocked(
-            const size_t matcherIndex, const HashableDimensionKey& eventKey,
+            const size_t matcherIndex, const MetricDimensionKey& eventKey,
             const ConditionKey& conditionKeys, bool condition,
             const LogEvent& event) override;
 
@@ -92,21 +92,21 @@
 
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
     // TODO: Add a lock to mPastBuckets.
-    std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>> mPastBuckets;
+    std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets;
 
     // The current bucket.
-    std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
-            mCurrentSlicedDuration;
+    std::unordered_map<MetricDimensionKey, std::unique_ptr<DurationTracker>>
+            mCurrentSlicedDurationTrackerMap;
 
     // Helper function to create a duration tracker given the metric aggregation type.
     std::unique_ptr<DurationTracker> createDurationTracker(
-            const HashableDimensionKey& eventKey) const;
+        const MetricDimensionKey& eventKey) const;
 
     // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
     std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
 
     // Util function to check whether the specified dimension hits the guardrail.
-    bool hitGuardRailLocked(const HashableDimensionKey& newKey);
+    bool hitGuardRailLocked(const MetricDimensionKey& newKey);
 
     static const size_t kBucketSize = sizeof(DurationBucket{});
 
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 25c86d0..820d591 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -128,7 +128,7 @@
 }
 
 void EventMetricProducer::onMatchedLogEventInternalLocked(
-        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const size_t matcherIndex, const MetricDimensionKey& eventKey,
         const ConditionKey& conditionKey, bool condition,
         const LogEvent& event) {
     if (!condition) {
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 9da0dd0..935f206 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -45,7 +45,7 @@
 
 private:
     void onMatchedLogEventInternalLocked(
-            const size_t matcherIndex, const HashableDimensionKey& eventKey,
+            const size_t matcherIndex, const MetricDimensionKey& eventKey,
             const ConditionKey& conditionKey, bool condition,
             const LogEvent& event) override;
 
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 1072c5a..d6cb189 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -82,13 +82,15 @@
     mFieldFilter = metric.gauge_fields_filter();
 
     // TODO: use UidMap if uid->pkg_name is required
-    mDimensions = metric.dimensions_in_what();
+    mDimensionsInWhat = metric.dimensions_in_what();
+    mDimensionsInCondition = metric.dimensions_in_condition();
 
     if (metric.links().size() > 0) {
         mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
                                metric.links().end());
-        mConditionSliced = true;
     }
+    mConditionSliced = (metric.links().size() > 0)||
+        (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0);
 
     // Kicks off the puller immediately.
     if (mPullTagId != -1 && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
@@ -136,18 +138,27 @@
     long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS);
 
     for (const auto& pair : mPastBuckets) {
-        const HashableDimensionKey& hashableKey = pair.first;
+        const MetricDimensionKey& dimensionKey = pair.first;
 
-        VLOG("  dimension key %s", hashableKey.c_str());
+        VLOG("  dimension key %s", dimensionKey.c_str());
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension.
         long long dimensionToken = protoOutput->start(
                 FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
-        writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
+        writeDimensionsValueProtoToStream(
+            dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput);
         protoOutput->end(dimensionToken);
 
+        if (dimensionKey.hasDimensionKeyInCondition()) {
+            long long dimensionInConditionToken = protoOutput->start(
+                    FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
+            writeDimensionsValueProtoToStream(
+                dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput);
+            protoOutput->end(dimensionInConditionToken);
+        }
+
         // Then fill bucket_info (GaugeBucketInfo).
         for (const auto& bucket : pair.second) {
             long long bucketInfoToken = protoOutput->start(
@@ -248,7 +259,7 @@
     }
 }
 
-bool GaugeMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) {
+bool GaugeMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
     if (mCurrentSlicedBucket->find(newKey) != mCurrentSlicedBucket->end()) {
         return false;
     }
@@ -268,7 +279,7 @@
 }
 
 void GaugeMetricProducer::onMatchedLogEventInternalLocked(
-        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const size_t matcherIndex, const MetricDimensionKey& eventKey,
         const ConditionKey& conditionKey, bool condition,
         const LogEvent& event) {
     if (condition == false) {
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 6c01347..86d0ccd 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -44,7 +44,7 @@
     uint64_t mBucketNum;
 };
 
-typedef std::unordered_map<HashableDimensionKey, std::vector<GaugeAtom>>
+typedef std::unordered_map<MetricDimensionKey, std::vector<GaugeAtom>>
     DimToGaugeAtomsMap;
 
 // This gauge metric producer first register the puller to automatically pull the gauge at the
@@ -64,7 +64,7 @@
 
 protected:
     void onMatchedLogEventInternalLocked(
-            const size_t matcherIndex, const HashableDimensionKey& eventKey,
+            const size_t matcherIndex, const MetricDimensionKey& eventKey,
             const ConditionKey& conditionKey, bool condition,
             const LogEvent& event) override;
 
@@ -99,7 +99,7 @@
 
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
     // TODO: Add a lock to mPastBuckets.
-    std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
+    std::unordered_map<MetricDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
 
     // The current bucket.
     std::shared_ptr<DimToGaugeAtomsMap> mCurrentSlicedBucket;
@@ -119,7 +119,7 @@
     std::shared_ptr<FieldValueMap> getGaugeFields(const LogEvent& event);
 
     // Util function to check whether the specified dimension hits the guardrail.
-    bool hitGuardRailLocked(const HashableDimensionKey& newKey);
+    bool hitGuardRailLocked(const MetricDimensionKey& newKey);
 
     static const size_t kBucketSize = sizeof(GaugeBucket{});
 
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index e74924a..85e655b 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -15,6 +15,8 @@
  */
 #include "MetricProducer.h"
 
+#include "dimension.h"
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -30,29 +32,51 @@
 
     bool condition;
     ConditionKey conditionKey;
+
+    std::unordered_set<HashableDimensionKey> dimensionKeysInCondition;
     if (mConditionSliced) {
         for (const auto& link : mConditionLinks) {
             getDimensionKeysForCondition(event, link, &conditionKey[link.condition()]);
         }
-        if (mWizard->query(mConditionTrackerIndex, conditionKey) != ConditionState::kTrue) {
-            condition = false;
-        } else {
-            condition = true;
-        }
+        auto conditionState =
+            mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
+                           &dimensionKeysInCondition);
+        condition = (conditionState == ConditionState::kTrue);
     } else {
         condition = mCondition;
     }
 
-    if (mDimensions.has_field() && mDimensions.child_size() > 0) {
-        vector<DimensionsValue> dimensionValues;
-        getDimensionKeys(event, mDimensions, &dimensionValues);
-        for (const DimensionsValue& dimensionValue : dimensionValues) {
+    vector<DimensionsValue> dimensionInWhatValues;
+    if (mDimensionsInWhat.has_field() && mDimensionsInWhat.child_size() > 0) {
+        getDimensionKeys(event, mDimensionsInWhat, &dimensionInWhatValues);
+    }
+
+    if (dimensionInWhatValues.empty() && dimensionKeysInCondition.empty()) {
+        onMatchedLogEventInternalLocked(
+            matcherIndex, DEFAULT_METRIC_DIMENSION_KEY, conditionKey, condition, event);
+    } else if (dimensionKeysInCondition.empty()) {
+        for (const DimensionsValue& whatValue : dimensionInWhatValues) {
             onMatchedLogEventInternalLocked(
-                matcherIndex, HashableDimensionKey(dimensionValue), conditionKey, condition, event);
+                matcherIndex,
+                MetricDimensionKey(HashableDimensionKey(whatValue), DEFAULT_DIMENSION_KEY),
+                conditionKey, condition, event);
+        }
+    } else if (dimensionInWhatValues.empty()) {
+        for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
+            onMatchedLogEventInternalLocked(
+                matcherIndex,
+                MetricDimensionKey(DEFAULT_DIMENSION_KEY, conditionDimensionKey),
+                conditionKey, condition, event);
         }
     } else {
-        onMatchedLogEventInternalLocked(
-            matcherIndex, DEFAULT_DIMENSION_KEY, conditionKey, condition, event);
+        for (const DimensionsValue& whatValue : dimensionInWhatValues) {
+            for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
+                onMatchedLogEventInternalLocked(
+                    matcherIndex,
+                    MetricDimensionKey(HashableDimensionKey(whatValue), conditionDimensionKey),
+                    conditionKey, condition, event);
+            }
+        }
     }
 }
 
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 6f33073..3b1498f 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -91,6 +91,7 @@
         std::lock_guard<std::mutex> lock(mMutex);
         return onDumpReportLocked(dumpTimeNs, protoOutput);
     }
+
     void onDumpReport(const uint64_t dumpTimeNs, StatsLogReport* report) {
         std::lock_guard<std::mutex> lock(mMutex);
         return onDumpReportLocked(dumpTimeNs, report);
@@ -156,7 +157,8 @@
 
     int mConditionTrackerIndex;
 
-    FieldMatcher mDimensions;  // The dimension defined in statsd_config
+    FieldMatcher mDimensionsInWhat;  // The dimensions_in_what defined in statsd_config
+    FieldMatcher mDimensionsInCondition;  // The dimensions_in_condition defined in statsd_config
 
     std::vector<MetricConditionLink> mConditionLinks;
 
@@ -178,7 +180,7 @@
      * [event]: the log event, just in case the metric needs its data, e.g., EventMetric.
      */
     virtual void onMatchedLogEventInternalLocked(
-            const size_t matcherIndex, const HashableDimensionKey& eventKey,
+            const size_t matcherIndex, const MetricDimensionKey& eventKey,
             const ConditionKey& conditionKey, bool condition,
             const LogEvent& event) = 0;
 
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 9cdbafc..d4b9102 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -143,6 +143,10 @@
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
     FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
+    FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink);
+    FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink);
+    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink);
+    FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index ae0c673..c9cc7bb 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -81,13 +81,15 @@
     }
 
     mBucketSizeNs = bucketSizeMills * 1000000;
-    mDimensions = metric.dimensions_in_what();
+    mDimensionsInWhat = metric.dimensions_in_what();
+    mDimensionsInCondition = metric.dimensions_in_condition();
 
     if (metric.links().size() > 0) {
         mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
                                metric.links().end());
-        mConditionSliced = true;
     }
+    mConditionSliced = (metric.links().size() > 0)||
+        (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0);
 
     if (!metric.has_condition() && mPullTagId != -1) {
         VLOG("Setting up periodic pulling for %d", mPullTagId);
@@ -124,7 +126,10 @@
     auto value_metrics = report->mutable_value_metrics();
     for (const auto& pair : mPastBuckets) {
         ValueMetricData* metricData = value_metrics->add_data();
-        *metricData->mutable_dimensions_in_what() = pair.first.getDimensionsValue();
+        *metricData->mutable_dimensions_in_what() =
+            pair.first.getDimensionKeyInWhat().getDimensionsValue();
+        *metricData->mutable_dimensions_in_condition() =
+            pair.first.getDimensionKeyInCondition().getDimensionsValue();
         for (const auto& bucket : pair.second) {
             ValueBucketInfo* bucketInfo = metricData->add_bucket_info();
             bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
@@ -146,16 +151,24 @@
     long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS);
 
     for (const auto& pair : mPastBuckets) {
-        const HashableDimensionKey& hashableKey = pair.first;
-        VLOG("  dimension key %s", hashableKey.c_str());
+        const MetricDimensionKey& dimensionKey = pair.first;
+        VLOG("  dimension key %s", dimensionKey.c_str());
         long long wrapperToken =
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension.
         long long dimensionToken = protoOutput->start(
             FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
-        writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
+        writeDimensionsValueProtoToStream(
+            dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput);
         protoOutput->end(dimensionToken);
+        if (dimensionKey.hasDimensionKeyInCondition()) {
+            long long dimensionInConditionToken = protoOutput->start(
+                    FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
+            writeDimensionsValueProtoToStream(
+                dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput);
+            protoOutput->end(dimensionInConditionToken);
+        }
 
         // Then fill bucket_info (ValueBucketInfo).
         for (const auto& bucket : pair.second) {
@@ -239,7 +252,7 @@
     }
 }
 
-bool ValueMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) {
+bool ValueMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
     // ===========GuardRail==============
     // 1. Report the tuple count if the tuple count > soft limit
     if (mCurrentSlicedBucket.find(newKey) != mCurrentSlicedBucket.end()) {
@@ -260,7 +273,7 @@
 }
 
 void ValueMetricProducer::onMatchedLogEventInternalLocked(
-        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const size_t matcherIndex, const MetricDimensionKey& eventKey,
         const ConditionKey& conditionKey, bool condition,
         const LogEvent& event) {
     uint64_t eventTimeNs = event.GetTimestampNs();
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 9f750cf..121ec7d 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -49,7 +49,7 @@
 
 protected:
     void onMatchedLogEventInternalLocked(
-            const size_t matcherIndex, const HashableDimensionKey& eventKey,
+            const size_t matcherIndex, const MetricDimensionKey& eventKey,
             const ConditionKey& conditionKey, bool condition,
             const LogEvent& event) override;
 
@@ -99,16 +99,16 @@
         long sum;
     } Interval;
 
-    std::unordered_map<HashableDimensionKey, Interval> mCurrentSlicedBucket;
+    std::unordered_map<MetricDimensionKey, Interval> mCurrentSlicedBucket;
 
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
     // TODO: Add a lock to mPastBuckets.
-    std::unordered_map<HashableDimensionKey, std::vector<ValueBucket>> mPastBuckets;
+    std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>> mPastBuckets;
 
     std::shared_ptr<FieldValueMap> getValueFields(const LogEvent& event);
 
     // Util function to check whether the specified dimension hits the guardrail.
-    bool hitGuardRailLocked(const HashableDimensionKey& newKey);
+    bool hitGuardRailLocked(const MetricDimensionKey& newKey);
 
     static const size_t kBucketSize = sizeof(ValueBucket{});
 
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index c2d2cea..45735a8 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -60,8 +60,9 @@
 
 class DurationTracker {
 public:
-    DurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey,
-                    sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
+    DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
+                    sp<ConditionWizard> wizard, int conditionIndex,
+                    const FieldMatcher& dimensionInCondition, bool nesting,
                     uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced,
                     const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
         : mConfigKey(key),
@@ -70,6 +71,7 @@
           mWizard(wizard),
           mConditionTrackerIndex(conditionIndex),
           mBucketSizeNs(bucketSizeNs),
+          mDimensionInCondition(dimensionInCondition),
           mNested(nesting),
           mCurrentBucketStartTimeNs(currentBucketStartNs),
           mDuration(0),
@@ -79,6 +81,8 @@
 
     virtual ~DurationTracker(){};
 
+    virtual unique_ptr<DurationTracker> clone(const uint64_t eventTime) = 0;
+
     virtual void noteStart(const HashableDimensionKey& key, bool condition,
                            const uint64_t eventTime, const ConditionKey& conditionKey) = 0;
     virtual void noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
@@ -92,7 +96,7 @@
     // events, so that the owner can safely remove the tracker.
     virtual bool flushIfNeeded(
             uint64_t timestampNs,
-            std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) = 0;
+            std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) = 0;
 
     // Predict the anomaly timestamp given the current status.
     virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
@@ -100,6 +104,10 @@
     // Dump internal states for debugging
     virtual void dumpStates(FILE* out, bool verbose) const = 0;
 
+    void setEventKey(const MetricDimensionKey& eventKey) {
+         mEventKey = eventKey;
+    }
+
 protected:
     // Starts the anomaly alarm.
     void startAnomalyAlarm(const uint64_t eventTime) {
@@ -150,7 +158,7 @@
 
     const int64_t mTrackerId;
 
-    HashableDimensionKey mEventKey;
+    MetricDimensionKey mEventKey;
 
     sp<ConditionWizard> mWizard;
 
@@ -158,6 +166,8 @@
 
     const int64_t mBucketSizeNs;
 
+    const FieldMatcher mDimensionInCondition;
+
     const bool mNested;
 
     uint64_t mCurrentBucketStartTimeNs;
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 412a0c9..db7dea4 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -25,13 +25,23 @@
 namespace statsd {
 
 MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
-                                       const HashableDimensionKey& eventKey,
-                                       sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
+                                       const MetricDimensionKey& eventKey,
+                                       sp<ConditionWizard> wizard, int conditionIndex,
+                                       const FieldMatcher& dimensionInCondition, bool nesting,
                                        uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
                                        bool conditionSliced,
                                        const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
-                      bucketSizeNs, conditionSliced, anomalyTrackers) {
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
+                      currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers) {
+}
+
+unique_ptr<DurationTracker> MaxDurationTracker::clone(const uint64_t eventTime) {
+    auto clonedTracker = make_unique<MaxDurationTracker>(*this);
+    for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end(); ++it) {
+        it->second.lastStartTime = eventTime;
+        it->second.lastDuration = 0;
+    }
+    return clonedTracker;
 }
 
 bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -44,7 +54,7 @@
     if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
         size_t newTupleCount = mInfos.size() + 1;
         StatsdStats::getInstance().noteMetricDimensionSize(
-            mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()),
+            mConfigKey, hashMetricDimensionKey(mTrackerId, mEventKey),
             newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
@@ -149,7 +159,7 @@
 }
 
 bool MaxDurationTracker::flushIfNeeded(
-        uint64_t eventTime, unordered_map<HashableDimensionKey, vector<DurationBucket>>* output) {
+        uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
     if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
         return false;
     }
@@ -236,8 +246,14 @@
         if (pair.second.state == kStopped) {
             continue;
         }
-        bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
-                            ConditionState::kTrue;
+        std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
+        ConditionState conditionState = mWizard->query(
+            mConditionTrackerIndex, pair.second.conditionKeys, mDimensionInCondition,
+            &conditionDimensionKeySet);
+        bool conditionMet = (conditionState == ConditionState::kTrue) &&
+            (!mDimensionInCondition.has_field() ||
+             conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) !=
+                conditionDimensionKeySet.end());
         VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
         noteConditionChanged(pair.first, conditionMet, timestamp);
     }
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index 661d131..4d32a06 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -29,10 +29,15 @@
 class MaxDurationTracker : public DurationTracker {
 public:
     MaxDurationTracker(const ConfigKey& key, const int64_t& id,
-                       const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
-                       int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
-                       uint64_t bucketSizeNs, bool conditionSliced,
+                       const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
+                       int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting,
+                       uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced,
                        const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
+
+    MaxDurationTracker(const MaxDurationTracker& tracker) = default;
+
+    unique_ptr<DurationTracker> clone(const uint64_t eventTime) override;
+
     void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
                    const ConditionKey& conditionKey) override;
     void noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
@@ -41,7 +46,7 @@
 
     bool flushIfNeeded(
             uint64_t timestampNs,
-            std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) override;
+            std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
 
     void onSlicedConditionMayChange(const uint64_t timestamp) override;
     void onConditionChanged(bool condition, const uint64_t timestamp) override;
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 75d7c08..0feae36 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -25,17 +25,25 @@
 using std::pair;
 
 OringDurationTracker::OringDurationTracker(
-        const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey,
-        sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+        const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
+        sp<ConditionWizard> wizard, int conditionIndex,
+        const FieldMatcher& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs,
         uint64_t bucketSizeNs, bool conditionSliced,
         const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
-                      bucketSizeNs, conditionSliced, anomalyTrackers),
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
+                      currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers),
       mStarted(),
       mPaused() {
     mLastStartTime = 0;
 }
 
+unique_ptr<DurationTracker> OringDurationTracker::clone(const uint64_t eventTime) {
+    auto clonedTracker = make_unique<OringDurationTracker>(*this);
+    clonedTracker->mLastStartTime = eventTime;
+    clonedTracker->mDuration = 0;
+    return clonedTracker;
+}
+
 bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
     // ===========GuardRail==============
     // 1. Report the tuple count if the tuple count > soft limit
@@ -45,7 +53,7 @@
     if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
         size_t newTupleCount = mConditionKeyMap.size() + 1;
         StatsdStats::getInstance().noteMetricDimensionSize(
-            mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()),
+            mConfigKey, hashMetricDimensionKey(mTrackerId, mEventKey),
             newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
@@ -76,7 +84,6 @@
     if (mConditionSliced && mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
         mConditionKeyMap[key] = conditionKey;
     }
-
     VLOG("Oring: %s start, condition %d", key.c_str(), condition);
 }
 
@@ -128,7 +135,7 @@
 }
 
 bool OringDurationTracker::flushIfNeeded(
-        uint64_t eventTime, unordered_map<HashableDimensionKey, vector<DurationBucket>>* output) {
+        uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
     if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) {
         return false;
     }
@@ -184,8 +191,14 @@
                 ++it;
                 continue;
             }
-            if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) !=
-                ConditionState::kTrue) {
+            std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
+            ConditionState conditionState =
+                mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key],
+                               mDimensionInCondition, &conditionDimensionKeySet);
+            if (conditionState != ConditionState::kTrue ||
+                (mDimensionInCondition.has_field() &&
+                 conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) ==
+                    conditionDimensionKeySet.end())) {
                 startedToPaused.push_back(*it);
                 it = mStarted.erase(it);
                 VLOG("Key %s started -> paused", key.c_str());
@@ -210,8 +223,14 @@
                 ++it;
                 continue;
             }
-            if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) ==
-                ConditionState::kTrue) {
+            std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
+            ConditionState conditionState =
+                mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key],
+                               mDimensionInCondition, &conditionDimensionKeySet);
+            if (conditionState == ConditionState::kTrue &&
+                (!mDimensionInCondition.has_field() ||
+                 conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition())
+                    != conditionDimensionKeySet.end())) {
                 pausedToStarted.push_back(*it);
                 it = mPaused.erase(it);
                 VLOG("Key %s paused -> started", key.c_str());
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 43469ca..75b5a81 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -28,11 +28,15 @@
 class OringDurationTracker : public DurationTracker {
 public:
     OringDurationTracker(const ConfigKey& key, const int64_t& id,
-                         const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
-                         int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
-                         uint64_t bucketSizeNs, bool conditionSliced,
+                         const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
+                         int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting,
+                         uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced,
                          const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
 
+    OringDurationTracker(const OringDurationTracker& tracker) = default;
+
+    unique_ptr<DurationTracker> clone(const uint64_t eventTime) override;
+
     void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
                    const ConditionKey& conditionKey) override;
     void noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
@@ -44,7 +48,7 @@
 
     bool flushIfNeeded(
             uint64_t timestampNs,
-            std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) override;
+            std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
 
     int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
                                       const uint64_t currentTimestamp) const override;
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index a41f30c..6c61400 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -54,6 +54,9 @@
 
 void writeDimensionsValueProtoToStream(const DimensionsValue& dimensionsValue,
                                        ProtoOutputStream* protoOutput) {
+    if (!dimensionsValue.has_field()) {
+        return;
+    }
     protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, dimensionsValue.field());
     switch (dimensionsValue.value_case()) {
         case DimensionsValue::ValueCase::kValueStr:
@@ -103,6 +106,9 @@
 
 void writeFieldProtoToStream(
     const Field& field, util::ProtoOutputStream* protoOutput) {
+    if (!field.has_field()) {
+        return;
+    }
     protoOutput->write(FIELD_TYPE_INT32 | FIELD_FIELD, field.field());
     if (field.has_position_index()) {
       protoOutput->write(FIELD_TYPE_INT32 | FIELD_POSITION_INDEX, field.position_index());
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 160b1f4..31f51a7 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -28,13 +28,14 @@
 namespace statsd {
 
 const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey();
+const MetricDimensionKey DEFAULT_METRIC_DIMENSION_KEY = MetricDimensionKey();
 
 // Minimum bucket size in seconds
 const long kMinBucketSizeSec = 5 * 60;
 
 typedef std::map<int64_t, std::vector<HashableDimensionKey>> ConditionKey;
 
-typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
+typedef std::unordered_map<MetricDimensionKey, int64_t> DimToValMap;
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
index f912e4b..3af684f 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -56,7 +56,7 @@
 
 void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey,
                                                   const Subscription& subscription,
-                                                  const HashableDimensionKey& dimKey) const {
+                                                  const MetricDimensionKey& dimKey) const {
     // Reminder about ids:
     //  subscription id - name of the Subscription (that ties the Alert to the broadcast)
     //  subscription rule_id - the name of the Alert (that triggers the broadcast)
@@ -92,7 +92,7 @@
 void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender,
                                              const ConfigKey& configKey,
                                              const Subscription& subscription,
-                                             const HashableDimensionKey& dimKey) const {
+                                             const MetricDimensionKey& dimKey) const {
     VLOG("SubscriberReporter::sendBroadcastLocked called.");
     if (mStatsCompanionService == nullptr) {
         ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService.");
@@ -107,8 +107,8 @@
 }
 
 StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue(
-        const HashableDimensionKey& dimKey) {
-    return protoToStatsDimensionsValue(dimKey.getDimensionsValue());
+        const MetricDimensionKey& dimKey) {
+    return protoToStatsDimensionsValue(dimKey.getDimensionKeyInWhat().getDimensionsValue());
 }
 
 StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue(
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
index 5bb458a..13fc7fd 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.h
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -80,7 +80,7 @@
      */
     void alertBroadcastSubscriber(const ConfigKey& configKey,
                                   const Subscription& subscription,
-                                  const HashableDimensionKey& dimKey) const;
+                                  const MetricDimensionKey& dimKey) const;
 
 private:
     SubscriberReporter() {};
@@ -101,7 +101,7 @@
     void sendBroadcastLocked(const sp<android::IBinder>& intentSender,
                              const ConfigKey& configKey,
                              const Subscription& subscription,
-                             const HashableDimensionKey& dimKey) const;
+                             const MetricDimensionKey& dimKey) const;
 
     /** Converts a stats_log.proto DimensionsValue to a StatsDimensionsValue. */
     static StatsDimensionsValue protoToStatsDimensionsValue(
@@ -109,7 +109,7 @@
 
     /** Converts a HashableDimensionKey to a StatsDimensionsValue. */
     static StatsDimensionsValue protoToStatsDimensionsValue(
-            const HashableDimensionKey& dimKey);
+            const MetricDimensionKey& dimKey);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index 66bfa68..a415ea1 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -33,14 +33,14 @@
 
 const ConfigKey kConfigKey(0, 12345);
 
-HashableDimensionKey getMockDimensionKey(int key, string value) {
+MetricDimensionKey getMockMetricDimensionKey(int key, string value) {
     DimensionsValue dimensionsValue;
     dimensionsValue.set_field(key);
     dimensionsValue.set_value_str(value);
-    return HashableDimensionKey(dimensionsValue);
+    return MetricDimensionKey(HashableDimensionKey(dimensionsValue), DEFAULT_DIMENSION_KEY);
 }
 
-void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list,
+void AddValueToBucket(const std::vector<std::pair<MetricDimensionKey, long>>& key_value_pair_list,
                       std::shared_ptr<DimToValMap> bucket) {
     for (auto itr = key_value_pair_list.begin(); itr != key_value_pair_list.end(); itr++) {
         (*bucket)[itr->first] += itr->second;
@@ -48,7 +48,7 @@
 }
 
 std::shared_ptr<DimToValMap> MockBucket(
-        const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list) {
+        const std::vector<std::pair<MetricDimensionKey, long>>& key_value_pair_list) {
     std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
     AddValueToBucket(key_value_pair_list, bucket);
     return bucket;
@@ -56,7 +56,7 @@
 
 // Returns the value, for the given key, in that bucket, or 0 if not present.
 int64_t getBucketValue(const std::shared_ptr<DimToValMap>& bucket,
-                       const HashableDimensionKey& key) {
+                       const MetricDimensionKey& key) {
     const auto& itr = bucket->find(key);
     if (itr != bucket->end()) {
         return itr->second;
@@ -68,14 +68,14 @@
 bool detectAnomaliesPass(AnomalyTracker& tracker,
                          const int64_t& bucketNum,
                          const std::shared_ptr<DimToValMap>& currentBucket,
-                         const std::set<const HashableDimensionKey>& trueList,
-                         const std::set<const HashableDimensionKey>& falseList) {
-    for (HashableDimensionKey key : trueList) {
+                         const std::set<const MetricDimensionKey>& trueList,
+                         const std::set<const MetricDimensionKey>& falseList) {
+    for (MetricDimensionKey key : trueList) {
         if (!tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) {
             return false;
         }
     }
-    for (HashableDimensionKey key : falseList) {
+    for (MetricDimensionKey key : falseList) {
         if (tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) {
             return false;
         }
@@ -100,7 +100,7 @@
 void checkRefractoryTimes(AnomalyTracker& tracker,
                           const int64_t& currTimestampNs,
                           const int32_t& refractoryPeriodSec,
-                          const std::unordered_map<HashableDimensionKey, int64_t>& timestamps) {
+                          const std::unordered_map<MetricDimensionKey, int64_t>& timestamps) {
     for (const auto& kv : timestamps) {
         if (kv.second < 0) {
             // Make sure that, if there is a refractory period, it is already past.
@@ -124,9 +124,9 @@
     alert.set_trigger_if_sum_gt(2);
 
     AnomalyTracker anomalyTracker(alert, kConfigKey);
-    HashableDimensionKey keyA = getMockDimensionKey(1, "a");
-    HashableDimensionKey keyB = getMockDimensionKey(1, "b");
-    HashableDimensionKey keyC = getMockDimensionKey(1, "c");
+    MetricDimensionKey keyA = getMockMetricDimensionKey(1, "a");
+    MetricDimensionKey keyB = getMockMetricDimensionKey(1, "b");
+    MetricDimensionKey keyC = getMockMetricDimensionKey(1, "c");
 
     int64_t eventTimestamp0 = 10 * NS_PER_SEC;
     int64_t eventTimestamp1 = bucketSizeNs + 11 * NS_PER_SEC;
@@ -269,11 +269,11 @@
     alert.set_trigger_if_sum_gt(2);
 
     AnomalyTracker anomalyTracker(alert, kConfigKey);
-    HashableDimensionKey keyA = getMockDimensionKey(1, "a");
-    HashableDimensionKey keyB = getMockDimensionKey(1, "b");
-    HashableDimensionKey keyC = getMockDimensionKey(1, "c");
-    HashableDimensionKey keyD = getMockDimensionKey(1, "d");
-    HashableDimensionKey keyE = getMockDimensionKey(1, "e");
+    MetricDimensionKey keyA = getMockMetricDimensionKey(1, "a");
+    MetricDimensionKey keyB = getMockMetricDimensionKey(1, "b");
+    MetricDimensionKey keyC = getMockMetricDimensionKey(1, "c");
+    MetricDimensionKey keyD = getMockMetricDimensionKey(1, "d");
+    MetricDimensionKey keyE = getMockMetricDimensionKey(1, "e");
 
     std::shared_ptr<DimToValMap> bucket9 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
     std::shared_ptr<DimToValMap> bucket16 = MockBucket({{keyB, 4}});
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 819f2be..d1b7b28 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -78,7 +78,7 @@
 std::map<int64_t, std::vector<HashableDimensionKey>> getWakeLockQueryKey(
     const Position position,
     const std::vector<int> &uids, const string& conditionName) {
-    std::map<int64_t, std::vector<HashableDimensionKey>>  outputKeyMap;
+    std::map<int64_t, std::vector<HashableDimensionKey>> outputKeyMap;
     std::vector<int> uid_indexes;
     switch(position) {
         case Position::FIRST:
@@ -265,6 +265,9 @@
 TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
     for (Position position :
             { Position::ANY, Position::FIRST, Position::LAST}) {
+        FieldMatcher dimensionInCondition;
+        std::unordered_set<HashableDimensionKey> dimensionKeys;
+
         SimplePredicate simplePredicate = getWakeLockHeldCondition(
                 true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
                 position);
@@ -307,7 +310,8 @@
         const auto queryKey = getWakeLockQueryKey(position, uids, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
 
-        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                        conditionCache, dimensionKeys);
         EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
         // another wake lock acquired by this uid
@@ -361,7 +365,8 @@
 
         // query again
         conditionCache[0] = ConditionState::kNotEvaluated;
-        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                        conditionCache, dimensionKeys);
         EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
 
     }
@@ -369,6 +374,9 @@
 }
 
 TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
+    FieldMatcher dimensionInCondition;
+    std::unordered_set<HashableDimensionKey> dimensionKeys;
+
     SimplePredicate simplePredicate = getWakeLockHeldCondition(
             true /*nesting*/, true /*default to false*/, false /*slice output by uid*/,
             Position::ANY /* position */);
@@ -410,7 +418,8 @@
     ConditionKey queryKey;
     conditionCache[0] = ConditionState::kNotEvaluated;
 
-    conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+    conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                    conditionCache, dimensionKeys);
     EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
     // another wake lock acquired by this uid
@@ -452,13 +461,17 @@
 
     // query again
     conditionCache[0] = ConditionState::kNotEvaluated;
-    conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+    dimensionKeys.clear();
+    conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                    conditionCache, dimensionKeys);
     EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
 }
 
 TEST(SimpleConditionTrackerTest, TestStopAll) {
     for (Position position :
             {Position::ANY, Position::FIRST, Position::LAST}) {
+        FieldMatcher dimensionInCondition;
+        std::unordered_set<HashableDimensionKey> dimensionKeys;
         SimplePredicate simplePredicate = getWakeLockHeldCondition(
                 true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
                 position);
@@ -502,7 +515,8 @@
         const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
 
-        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                        conditionCache, dimensionKeys);
         EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
         // another wake lock acquired by uid2
@@ -528,8 +542,9 @@
         // TEST QUERY
         const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
+        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                        conditionCache, dimensionKeys);
 
-        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
         EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
 
 
@@ -550,15 +565,15 @@
         // TEST QUERY
         const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
-
-        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                        conditionCache, dimensionKeys);
         EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
 
         // TEST QUERY
         const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName);
         conditionCache[0] = ConditionState::kNotEvaluated;
-
-        conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+        conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+                                        conditionCache, dimensionKeys);
         EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
     }
 
diff --git a/cmds/statsd/tests/dimension_test.cpp b/cmds/statsd/tests/dimension_test.cpp
new file mode 100644
index 0000000..678abae
--- /dev/null
+++ b/cmds/statsd/tests/dimension_test.cpp
@@ -0,0 +1,149 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dimension.h"
+
+#include <gtest/gtest.h>
+
+using namespace android::os::statsd;
+
+#ifdef __ANDROID__
+
+TEST(DimensionTest, subLeafNodes) {
+    DimensionsValue dimension;
+    int tagId = 100;
+    dimension.set_field(tagId);
+    auto child = dimension.mutable_value_tuple()->add_dimensions_value();
+    child->set_field(1);
+    child->set_value_int(2000);
+
+    child = dimension.mutable_value_tuple()->add_dimensions_value();
+    child->set_field(3);
+    child->set_value_str("test");
+
+    child = dimension.mutable_value_tuple()->add_dimensions_value();
+    child->set_field(4);
+    auto grandChild = child->mutable_value_tuple()->add_dimensions_value();
+    grandChild->set_field(1);
+    grandChild->set_value_float(1.3f);
+    grandChild = child->mutable_value_tuple()->add_dimensions_value();
+    grandChild->set_field(3);
+    grandChild->set_value_str("tag");
+
+    child = dimension.mutable_value_tuple()->add_dimensions_value();
+    child->set_field(6);
+    child->set_value_bool(false);
+
+    DimensionsValue sub_dimension;
+    FieldMatcher matcher;
+
+    // Tag id not matched.
+    matcher.set_field(tagId + 1);
+    EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension));
+
+    // Field not exist.
+    matcher.Clear();
+    matcher.set_field(tagId);
+    matcher.add_child()->set_field(5);
+    EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension));
+
+    // Field exists.
+    matcher.Clear();
+    matcher.set_field(tagId);
+    matcher.add_child()->set_field(3);
+    EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
+
+    // Field exists.
+    matcher.Clear();
+    sub_dimension.Clear();
+    matcher.set_field(tagId);
+    matcher.add_child()->set_field(6);
+    EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
+
+    // Field exists.
+    matcher.Clear();
+    sub_dimension.Clear();
+    matcher.set_field(tagId);
+    matcher.add_child()->set_field(1);
+    EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
+
+    // Not leaf field.
+    matcher.Clear();
+    sub_dimension.Clear();
+    matcher.set_field(tagId);
+    matcher.add_child()->set_field(4);
+    EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension));
+
+    // Grand-child leaf field not exist.
+    matcher.Clear();
+    sub_dimension.Clear();
+    matcher.set_field(tagId);
+    auto childMatcher = matcher.add_child();
+    childMatcher->set_field(4);
+    childMatcher->add_child()->set_field(2);
+    EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension));
+
+    // Grand-child leaf field.
+    matcher.Clear();
+    sub_dimension.Clear();
+    matcher.set_field(tagId);
+    childMatcher = matcher.add_child();
+    childMatcher->set_field(4);
+    childMatcher->add_child()->set_field(1);
+    EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
+
+    matcher.Clear();
+    sub_dimension.Clear();
+    matcher.set_field(tagId);
+    childMatcher = matcher.add_child();
+    childMatcher->set_field(4);
+    childMatcher->add_child()->set_field(3);
+    EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
+
+    // Multiple grand-child fields.
+    matcher.Clear();
+    sub_dimension.Clear();
+    matcher.set_field(tagId);
+    childMatcher = matcher.add_child();
+    childMatcher->set_field(4);
+    childMatcher->add_child()->set_field(3);
+    childMatcher->add_child()->set_field(1);
+    EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
+
+    // Multiple fields.
+    matcher.Clear();
+    sub_dimension.Clear();
+    matcher.set_field(tagId);
+    childMatcher = matcher.add_child();
+    childMatcher->set_field(4);
+    childMatcher->add_child()->set_field(3);
+    childMatcher->add_child()->set_field(1);
+    matcher.add_child()->set_field(3);
+    EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
+
+    // Subset of the fields not exist.
+    matcher.Clear();
+    sub_dimension.Clear();
+    matcher.set_field(tagId);
+    childMatcher = matcher.add_child();
+    childMatcher->set_field(4);
+    childMatcher->add_child()->set_field(3);
+    childMatcher->add_child()->set_field(1);
+    matcher.add_child()->set_field(2);
+    EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension));
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp
new file mode 100644
index 0000000..b5d48ef
--- /dev/null
+++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp
@@ -0,0 +1,725 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+namespace {
+
+StatsdConfig CreateCountMetricWithNoLinkConfig() {
+    StatsdConfig config;
+    auto screenBrightnessChangeAtomMatcher = CreateScreenBrightnessChangedAtomMatcher();
+    *config.add_atom_matcher() = screenBrightnessChangeAtomMatcher;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+    *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+    *config.add_predicate() = screenIsOffPredicate;
+
+    auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+    // The predicate is dimensioning by any attribution node and both by uid and tag.
+    *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
+        CreateAttributionUidAndTagDimensions(
+            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    *config.add_predicate() = holdingWakelockPredicate;
+
+    auto combinationPredicate = config.add_predicate();
+    combinationPredicate->set_id(987654);
+    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
+    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
+    addPredicateToPredicateCombination(holdingWakelockPredicate, combinationPredicate);
+
+    auto metric = config.add_count_metric();
+    metric->set_id(StringToId("ScreenBrightnessChangeMetric"));
+    metric->set_what(screenBrightnessChangeAtomMatcher.id());
+    metric->set_condition(combinationPredicate->id());
+    *metric->mutable_dimensions_in_what() = CreateDimensions(
+            android::util::SCREEN_BRIGHTNESS_CHANGED, {1 /* level */});
+    *metric->mutable_dimensions_in_condition() = CreateAttributionUidDimensions(
+            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    metric->set_bucket(ONE_MINUTE);
+    return config;
+}
+
+}  // namespace
+
+TEST(DimensionInConditionE2eTest, TestCountMetricNoLink) {
+    ConfigKey cfgKey;
+    auto config = CreateCountMetricWithNoLinkConfig();
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs =
+        TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+    std::vector<AttributionNode> attributions1 =
+        {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+         CreateAttribution(222, "GMSCoreModule2")};
+
+    std::vector<AttributionNode> attributions2 =
+        {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+         CreateAttribution(555, "GMSCoreModule2")};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateScreenStateChangedEvent(
+        android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10));
+    events.push_back(CreateScreenStateChangedEvent(
+        android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100));
+    events.push_back(CreateScreenStateChangedEvent(
+        android::view::DISPLAY_STATE_ON, bucketStartTimeNs + bucketSizeNs + 1));
+    events.push_back(CreateScreenStateChangedEvent(
+        android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 2 * bucketSizeNs - 10));
+
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions1, "wl1", bucketStartTimeNs + 200));
+    events.push_back(CreateReleaseWakelockEvent(
+        attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1));
+
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 100));
+    events.push_back(CreateReleaseWakelockEvent(
+        attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 50));
+
+    events.push_back(CreateScreenBrightnessChangedEvent(
+        123, bucketStartTimeNs + 11));
+    events.push_back(CreateScreenBrightnessChangedEvent(
+        123, bucketStartTimeNs + 101));
+    events.push_back(CreateScreenBrightnessChangedEvent(
+        123, bucketStartTimeNs + 201));
+    events.push_back(CreateScreenBrightnessChangedEvent(
+        456, bucketStartTimeNs + 203));
+    events.push_back(CreateScreenBrightnessChangedEvent(
+        456, bucketStartTimeNs + bucketSizeNs - 99));
+    events.push_back(CreateScreenBrightnessChangedEvent(
+        456, bucketStartTimeNs + bucketSizeNs - 2));
+    events.push_back(CreateScreenBrightnessChangedEvent(
+        789, bucketStartTimeNs + bucketSizeNs - 1));
+    events.push_back(CreateScreenBrightnessChangedEvent(
+        456, bucketStartTimeNs + bucketSizeNs + 2));
+    events.push_back(CreateScreenBrightnessChangedEvent(
+        789, bucketStartTimeNs + 2 * bucketSizeNs - 11));
+    events.push_back(CreateScreenBrightnessChangedEvent(
+        789, bucketStartTimeNs + 2 * bucketSizeNs - 9));
+    events.push_back(CreateScreenBrightnessChangedEvent(
+        789, bucketStartTimeNs + 2 * bucketSizeNs - 1));
+
+    sortLogEventsByTimestamp(&events);
+
+    for (const auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+
+    EXPECT_EQ(reports.reports_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+
+    EXPECT_EQ(countMetrics.data_size(), 7);
+    auto data = countMetrics.data(0);
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs );
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123);
+    EXPECT_FALSE(data.dimensions_in_condition().has_field());
+
+    data = countMetrics.data(1);
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123);
+    ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111);
+
+    data = countMetrics.data(2);
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 3);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456);
+    ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111);
+
+    data = countMetrics.data(3);
+    EXPECT_EQ(data.bucket_info_size(), 2);
+    EXPECT_EQ(data.bucket_info(0).count(), 2);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).count(), 1);
+    EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456);
+    ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 333);
+
+    data = countMetrics.data(4);
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 2);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789);
+    EXPECT_FALSE(data.dimensions_in_condition().has_field());
+
+    data = countMetrics.data(5);
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789);
+    ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111);
+
+    data = countMetrics.data(6);
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789);
+    ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 333);
+}
+
+namespace {
+
+StatsdConfig CreateCountMetricWithLinkConfig() {
+    StatsdConfig config;
+    auto appCrashMatcher = CreateProcessCrashAtomMatcher();
+    *config.add_atom_matcher() = appCrashMatcher;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+
+    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+    auto isSyncingPredicate = CreateIsSyncingPredicate();
+    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+    *syncDimension = CreateAttributionUidAndTagDimensions(
+        android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+    syncDimension->add_child()->set_field(2 /* name field*/);
+
+    *config.add_predicate() = screenIsOffPredicate;
+    *config.add_predicate() = isSyncingPredicate;
+    auto combinationPredicate = config.add_predicate();
+    combinationPredicate->set_id(987654);
+    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
+    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
+    addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
+
+    auto metric = config.add_count_metric();
+    metric->set_bucket(ONE_MINUTE);
+    metric->set_id(StringToId("AppCrashMetric"));
+    metric->set_what(appCrashMatcher.id());
+    metric->set_condition(combinationPredicate->id());
+    *metric->mutable_dimensions_in_what() = CreateDimensions(
+            android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1 /* uid */});
+    *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
+            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+
+    // Links between crash atom and condition of app is in syncing.
+    auto links = metric->add_links();
+    links->set_condition(isSyncingPredicate.id());
+    auto dimensionWhat = links->mutable_fields_in_what();
+    dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    dimensionWhat->add_child()->set_field(1);  // uid field.
+    *links->mutable_fields_in_condition() = CreateAttributionUidDimensions(
+            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+    return config;
+}
+
+}  // namespace
+
+TEST(DimensionInConditionE2eTest, TestCountMetricWithLink) {
+    ConfigKey cfgKey;
+    auto config = CreateCountMetricWithLinkConfig();
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs =
+        TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+    std::vector<AttributionNode> attributions1 =
+        {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+         CreateAttribution(222, "GMSCoreModule2")};
+
+    std::vector<AttributionNode> attributions2 =
+        {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+         CreateAttribution(555, "GMSCoreModule2")};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 11));
+    events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 101));
+    events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 101));
+
+    events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 201));
+    events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 211));
+    events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 211));
+
+    events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 401));
+    events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 401));
+    events.push_back(CreateAppCrashEvent(555, bucketStartTimeNs + 401));
+
+    events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + bucketSizeNs + 301));
+    events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + bucketSizeNs + 301));
+
+    events.push_back(CreateAppCrashEvent(777, bucketStartTimeNs + bucketSizeNs + 701));
+
+    events.push_back(CreateScreenStateChangedEvent(
+        android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10));
+    events.push_back(CreateScreenStateChangedEvent(
+        android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100));
+    events.push_back(CreateScreenStateChangedEvent(
+        android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202));
+    events.push_back(CreateScreenStateChangedEvent(
+        android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 700));
+
+    events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200));
+    events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+        bucketStartTimeNs + bucketSizeNs + 300));
+
+    events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400));
+    events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
+        bucketStartTimeNs + bucketSizeNs - 1));
+
+    events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 400));
+    events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
+        bucketStartTimeNs + bucketSizeNs + 600));
+
+    sortLogEventsByTimestamp(&events);
+
+    for (const auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+
+    EXPECT_EQ(reports.reports_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+
+    EXPECT_EQ(countMetrics.data_size(), 5);
+    auto data = countMetrics.data(0);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
+    EXPECT_FALSE(data.dimensions_in_condition().has_field());
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+
+    data = countMetrics.data(1);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1");
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 2);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+
+    data = countMetrics.data(2);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 222);
+    EXPECT_FALSE(data.dimensions_in_condition().has_field());
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 2);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+
+    data = countMetrics.data(3);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2");
+    EXPECT_EQ(data.bucket_info_size(), 2);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).count(), 1);
+    EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+
+    data = countMetrics.data(4);
+    EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 777);
+    EXPECT_FALSE(data.dimensions_in_condition().has_field());
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+}
+
+namespace {
+
+StatsdConfig CreateDurationMetricConfigNoLink(DurationMetric::AggregationType aggregationType) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+    *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+
+    auto inBatterySaverModePredicate = CreateBatterySaverModePredicate();
+
+    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+    auto isSyncingPredicate = CreateIsSyncingPredicate();
+    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+    *syncDimension = CreateAttributionUidAndTagDimensions(
+        android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+    syncDimension->add_child()->set_field(2 /* name field */);
+
+    *config.add_predicate() = inBatterySaverModePredicate;
+    *config.add_predicate() = screenIsOffPredicate;
+    *config.add_predicate() = isSyncingPredicate;
+    auto combinationPredicate = config.add_predicate();
+    combinationPredicate->set_id(987654);
+    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
+    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
+    addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
+
+    auto metric = config.add_duration_metric();
+    metric->set_bucket(ONE_MINUTE);
+    metric->set_id(StringToId("BatterySaverModeDurationMetric"));
+    metric->set_what(inBatterySaverModePredicate.id());
+    metric->set_condition(combinationPredicate->id());
+    *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
+            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+    return config;
+}
+
+}  // namespace
+
+
+TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink) {
+    for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
+        ConfigKey cfgKey;
+        auto config = CreateDurationMetricConfigNoLink(aggregationType);
+        int64_t bucketStartTimeNs = 10000000000;
+        int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+        auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+        EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+        EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+        std::vector<AttributionNode> attributions1 =
+            {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+             CreateAttribution(222, "GMSCoreModule2")};
+
+        std::vector<AttributionNode> attributions2 =
+            {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+             CreateAttribution(555, "GMSCoreModule2")};
+
+        std::vector<std::unique_ptr<LogEvent>> events;
+
+        events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 1));
+        events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 101));
+        events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 110));
+
+        events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 201));
+        events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 500));
+
+        events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 600));
+        events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 850));
+
+        events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + bucketSizeNs + 870));
+        events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 900));
+
+        events.push_back(CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10));
+        events.push_back(CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100));
+        events.push_back(CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202));
+        events.push_back(CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 800));
+
+        events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200));
+        events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+            bucketStartTimeNs + bucketSizeNs + 300));
+
+        events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400));
+        events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
+            bucketStartTimeNs + bucketSizeNs - 1));
+
+        events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401));
+        events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
+            bucketStartTimeNs + bucketSizeNs + 700));
+
+        sortLogEventsByTimestamp(&events);
+
+        for (const auto& event : events) {
+            processor->OnLogEvent(event.get());
+        }
+
+        ConfigMetricsReportList reports;
+        processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+
+        EXPECT_EQ(reports.reports_size(), 1);
+        EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+        StatsLogReport::DurationMetricDataWrapper metrics;
+        sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics);
+
+        EXPECT_EQ(metrics.data_size(), 3);
+        auto data = metrics.data(0);
+        EXPECT_FALSE(data.dimensions_in_what().has_field());
+        EXPECT_FALSE(data.dimensions_in_condition().has_field());
+        EXPECT_EQ(data.bucket_info_size(), 2);
+        EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9);
+        EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+        EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+        EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30);
+        EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+        EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+
+        data = metrics.data(1);
+        EXPECT_FALSE(data.dimensions_in_what().has_field());
+        ValidateAttributionUidAndTagDimension(
+            data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1");
+        EXPECT_EQ(data.bucket_info_size(), 2);
+        EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600);
+        EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+        EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+        EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300);
+        EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+        EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+
+        data = metrics.data(2);
+        EXPECT_FALSE(data.dimensions_in_what().has_field());
+        ValidateAttributionUidAndTagDimension(
+            data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2");
+        EXPECT_EQ(data.bucket_info_size(), 2);
+        EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600);
+        EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+        EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+        EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
+        EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+        EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    }
+}
+
+namespace {
+
+StatsdConfig CreateDurationMetricConfigWithLink(DurationMetric::AggregationType aggregationType) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
+    *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+    *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+
+    auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+    auto isSyncingPredicate = CreateIsSyncingPredicate();
+    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+    *syncDimension = CreateAttributionUidAndTagDimensions(
+        android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+    syncDimension->add_child()->set_field(2 /* name field */);
+
+    auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
+    *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
+        CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
+
+    *config.add_predicate() = screenIsOffPredicate;
+    *config.add_predicate() = isSyncingPredicate;
+    *config.add_predicate() = isInBackgroundPredicate;
+    auto combinationPredicate = config.add_predicate();
+    combinationPredicate->set_id(987654);
+    combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
+    addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
+    addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
+
+    auto metric = config.add_duration_metric();
+    metric->set_bucket(ONE_MINUTE);
+    metric->set_id(StringToId("AppInBackgroundMetric"));
+    metric->set_what(isInBackgroundPredicate.id());
+    metric->set_condition(combinationPredicate->id());
+    *metric->mutable_dimensions_in_what() = CreateDimensions(
+        android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
+    *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
+            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+
+    // Links between crash atom and condition of app is in syncing.
+    auto links = metric->add_links();
+    links->set_condition(isSyncingPredicate.id());
+    auto dimensionWhat = links->mutable_fields_in_what();
+    dimensionWhat->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+    dimensionWhat->add_child()->set_field(1);  // uid field.
+    *links->mutable_fields_in_condition() = CreateAttributionUidDimensions(
+            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+    return config;
+}
+
+}  // namespace
+
+TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink) {
+    for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
+        ConfigKey cfgKey;
+        auto config = CreateDurationMetricConfigWithLink(aggregationType);
+        int64_t bucketStartTimeNs = 10000000000;
+        int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+        auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+        EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+        EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+        std::vector<AttributionNode> attributions1 =
+            {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+             CreateAttribution(222, "GMSCoreModule2")};
+
+        std::vector<AttributionNode> attributions2 =
+            {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+             CreateAttribution(555, "GMSCoreModule2")};
+
+        std::vector<std::unique_ptr<LogEvent>> events;
+
+        events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 101));
+        events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + 110));
+
+        events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 201));
+        events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + bucketSizeNs + 100));
+
+        events.push_back(CreateMoveToBackgroundEvent(333, bucketStartTimeNs + 399));
+        events.push_back(CreateMoveToForegroundEvent(333, bucketStartTimeNs + bucketSizeNs + 800));
+
+        events.push_back(CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10));
+        events.push_back(CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100));
+        events.push_back(CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202));
+        events.push_back(CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 801));
+
+        events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200));
+        events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+            bucketStartTimeNs + bucketSizeNs + 300));
+
+        events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400));
+        events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
+            bucketStartTimeNs + bucketSizeNs - 1));
+
+        events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401));
+        events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
+            bucketStartTimeNs + bucketSizeNs + 700));
+
+        sortLogEventsByTimestamp(&events);
+
+        for (const auto& event : events) {
+            processor->OnLogEvent(event.get());
+        }
+
+        ConfigMetricsReportList reports;
+        processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+
+        EXPECT_EQ(reports.reports_size(), 1);
+        EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+        StatsLogReport::DurationMetricDataWrapper metrics;
+        sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics);
+
+        EXPECT_EQ(metrics.data_size(), 3);
+        auto data = metrics.data(0);
+        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
+        EXPECT_FALSE(data.dimensions_in_condition().has_field());
+        EXPECT_EQ(data.bucket_info_size(), 1);
+        EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9);
+        EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+        EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+
+        data = metrics.data(1);
+        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
+        ValidateAttributionUidAndTagDimension(
+            data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1");
+        EXPECT_EQ(data.bucket_info_size(), 2);
+        EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 201);
+        EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+        EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+        EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
+        EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+        EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+
+        data = metrics.data(2);
+        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
+        EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333);
+        ValidateAttributionUidAndTagDimension(
+            data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2");
+        EXPECT_EQ(data.bucket_info_size(), 2);
+        EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 401);
+        EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+        EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+        EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
+        EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+        EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    }
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
index 4504a95..233031c 100644
--- a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
@@ -44,9 +44,10 @@
     auto screenIsOffPredicate = CreateScreenIsOffPredicate();
 
     auto isSyncingPredicate = CreateIsSyncingPredicate();
-    *isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions() =
-        CreateDimensions(
-            android::util::SYNC_STATE_CHANGED, {1 /* uid field */, 2 /* name field*/});
+    auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+    *syncDimension = CreateAttributionUidDimensions(
+        android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+    syncDimension->add_child()->set_field(2 /* name field*/);
 
     auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
     *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
@@ -78,9 +79,8 @@
     auto dimensionWhat = links->mutable_fields_in_what();
     dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
     dimensionWhat->add_child()->set_field(1);  // uid field.
-    auto dimensionCondition = links->mutable_fields_in_condition();
-    dimensionCondition->set_field(android::util::SYNC_STATE_CHANGED);
-    dimensionCondition->add_child()->set_field(1);  // uid field.
+    *links->mutable_fields_in_condition() = CreateAttributionUidDimensions(
+            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
 
     // Links between crash atom and condition of app is in background.
     links = countMetric->add_links();
@@ -88,7 +88,7 @@
     dimensionWhat = links->mutable_fields_in_what();
     dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
     dimensionWhat->add_child()->set_field(1);  // uid field.
-    dimensionCondition = links->mutable_fields_in_condition();
+    auto dimensionCondition = links->mutable_fields_in_condition();
     dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
     dimensionCondition->add_child()->set_field(1);  // uid field.
     return config;
@@ -132,12 +132,14 @@
         CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
                                       bucketStartTimeNs + 2 * bucketSizeNs - 100);
 
+    std::vector<AttributionNode> attributions =
+        {CreateAttribution(appUid, "App1"), CreateAttribution(appUid + 1, "GMSCoreModule1")};
     auto syncOnEvent1 =
-        CreateSyncStartEvent(appUid, "ReadEmail", bucketStartTimeNs + 50);
+        CreateSyncStartEvent(attributions, "ReadEmail", bucketStartTimeNs + 50);
     auto syncOffEvent1 =
-        CreateSyncEndEvent(appUid, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300);
+        CreateSyncEndEvent(attributions, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300);
     auto syncOnEvent2 =
-        CreateSyncStartEvent(appUid, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000);
+        CreateSyncStartEvent(attributions, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000);
 
     auto moveToBackgroundEvent1 =
         CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15);
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 4ad2097..50b3532 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -67,9 +67,9 @@
     // Flushes.
     countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
-    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
-    const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+    const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
     EXPECT_EQ(1UL, buckets.size());
     EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
@@ -80,10 +80,10 @@
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     countProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
-    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
-    EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY].size());
-    const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY][1];
+    EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1];
     EXPECT_EQ(bucket2StartTimeNs, bucketInfo2.mBucketStartNs);
     EXPECT_EQ(bucket2StartTimeNs + bucketSizeNs, bucketInfo2.mBucketEndNs);
     EXPECT_EQ(1LL, bucketInfo2.mCount);
@@ -91,9 +91,9 @@
     // nothing happens in bucket 3. we should not record anything for bucket 3.
     countProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
-    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
-    const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+    const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
     EXPECT_EQ(2UL, buckets3.size());
 }
 
@@ -124,10 +124,10 @@
 
     countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
-    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
     {
-        const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+        const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
         EXPECT_EQ(1UL, buckets.size());
         const auto& bucketInfo = buckets[0];
         EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
@@ -167,9 +167,9 @@
         {getMockedDimensionKey(conditionTagId, 2, "222")};
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
+    EXPECT_CALL(*wizard, query(_, key1, _, _)).WillOnce(Return(ConditionState::kFalse));
 
-    EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue));
+    EXPECT_CALL(*wizard, query(_, key2, _, _)).WillOnce(Return(ConditionState::kTrue));
 
     CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard,
                                       bucketStartTimeNs);
@@ -181,9 +181,9 @@
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
     countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
-    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+    EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
-    const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+    const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
     EXPECT_EQ(1UL, buckets.size());
     const auto& bucketInfo = buckets[0];
     EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
@@ -229,13 +229,13 @@
 
     EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second);
-    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
 
     // One event in bucket #2. No alarm as bucket #0 is trashed out.
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second);
-    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
 
     // Two events in bucket #3.
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
@@ -244,13 +244,13 @@
     EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
     EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second);
     // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6
-    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
             event5.GetTimestampNs() / 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_DIMENSION_KEY),
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
             event7.GetTimestampNs() / NS_PER_SEC + refPeriodSec);
 }
 
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index a59f1fe..c9fe252 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -62,9 +62,9 @@
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
     durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
     EXPECT_EQ(1UL, durationProducer.mPastBuckets.size());
-    EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+    EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 durationProducer.mPastBuckets.end());
-    const auto& buckets = durationProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+    const auto& buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
     EXPECT_EQ(2UL, buckets.size());
     EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
@@ -107,9 +107,9 @@
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event4);
     durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
     EXPECT_EQ(1UL, durationProducer.mPastBuckets.size());
-    EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+    EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
                 durationProducer.mPastBuckets.end());
-    const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+    const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
     EXPECT_EQ(1UL, buckets2.size());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs);
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index da00cae..3deab37 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -114,9 +114,9 @@
     key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")};
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
+    EXPECT_CALL(*wizard, query(_, key1, _, _)).WillOnce(Return(ConditionState::kFalse));
 
-    EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue));
+    EXPECT_CALL(*wizard, query(_, key2, _, _)).WillOnce(Return(ConditionState::kTrue));
 
     EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs);
 
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 4533ac6..58be5b0 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -218,7 +218,7 @@
     EXPECT_EQ(13L,
         gaugeProducer.mCurrentSlicedBucket->begin()->
             second.front().mFields->begin()->second.value_int());
-    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
 
     std::shared_ptr<LogEvent> event2 =
             std::make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 20);
@@ -231,7 +231,7 @@
     EXPECT_EQ(15L,
         gaugeProducer.mCurrentSlicedBucket->begin()->
             second.front().mFields->begin()->second.value_int());
-    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
             event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
 
     std::shared_ptr<LogEvent> event3 =
@@ -245,7 +245,7 @@
     EXPECT_EQ(26L,
         gaugeProducer.mCurrentSlicedBucket->begin()->
             second.front().mFields->begin()->second.value_int());
-    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
             event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
 
     // The event4 does not have the gauge field. Thus the current bucket value is 0.
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 0772b0d40..203f028 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -41,22 +41,24 @@
 
 const int TagId = 1;
 
-const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1");
-const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
-const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
-const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
 
 TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
+    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
+    const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
+    const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
+
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
-                               bucketSizeNs, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
+                               false, bucketStartTimeNs, bucketSizeNs, false, {});
 
     tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
     // Event starts again. This would not change anything as it already starts.
@@ -75,16 +77,22 @@
 }
 
 TEST(MaxDurationTrackerTest, TestStopAll) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
+    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
+    const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
+    const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
+
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
-                               bucketSizeNs, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
+                               false, bucketStartTimeNs, bucketSizeNs, false, {});
 
     tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
 
@@ -105,21 +113,26 @@
 }
 
 TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
+    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
+    const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
+    const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
-                               bucketSizeNs, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
+                               false, bucketStartTimeNs, bucketSizeNs, false, {});
 
     // The event starts.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
 
-    // Starts again. Does not change anything.
+    // Starts again. Does not DEFAULT_DIMENSION_KEY anything.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1,
                       ConditionKey());
 
@@ -135,16 +148,21 @@
 }
 
 TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
+    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
+    const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
+    const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
-                               bucketSizeNs, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
+                               true, bucketStartTimeNs, bucketSizeNs, false, {});
 
     // 2 starts
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -160,7 +178,8 @@
     EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
 
     // real stop now.
-    tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
+    tracker.noteStop(DEFAULT_DIMENSION_KEY,
+                     bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
     tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets);
 
     EXPECT_EQ(3u, buckets[eventKey].size());
@@ -170,16 +189,20 @@
 }
 
 TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
+    const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
+    const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
+
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey conditionKey1;
-    HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 2, "maps");
+    MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps");
     conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
 
-    EXPECT_CALL(*wizard, query(_, conditionKey1))  // #4
+    EXPECT_CALL(*wizard, query(_, conditionKey1, _, _))  // #4
             .WillOnce(Return(ConditionState::kFalse));
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
@@ -187,8 +210,8 @@
     int64_t durationTimeNs = 2 * 1000;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
-                               bucketSizeNs, true, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
+                               false, bucketStartTimeNs, bucketSizeNs, true, {});
     EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
 
     tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
@@ -204,6 +227,10 @@
 }
 
 TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
+    const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
+    const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
+    FieldMatcher dimensionInCondition;
     int64_t metricId = 1;
     Alert alert;
     alert.set_id(101);
@@ -213,7 +240,7 @@
     const int32_t refPeriodSec = 1;
     alert.set_refractory_period_secs(refPeriodSec);
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
@@ -221,8 +248,8 @@
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
     sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
-                               bucketSizeNs, false, {anomalyTracker});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
+                               true, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
 
     tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStop(key1, eventStartTimeNs + 10, false);
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 6b8893e..80e16a1 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -38,24 +38,26 @@
 const ConfigKey kConfigKey(0, 12345);
 const int TagId = 1;
 const int64_t metricId = 123;
-const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event");
-
-const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")};
-const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
-const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
 
 TEST(OringDurationTrackerTest, TestDurationOverlap) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
+
+    const std::vector<HashableDimensionKey> kConditionKey1 =
+        {getMockedDimensionKey(TagId, 1, "maps")};
+    const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+    const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
-                                 bucketStartTimeNs, bucketSizeNs, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
+                                 false, bucketStartTimeNs, bucketSizeNs, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -71,16 +73,23 @@
 }
 
 TEST(OringDurationTrackerTest, TestDurationNested) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
+
+    const std::vector<HashableDimensionKey> kConditionKey1 =
+        {getMockedDimensionKey(TagId, 1, "maps")};
+    const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+    const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
-                                 bucketSizeNs, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
+                                 true, bucketStartTimeNs, bucketSizeNs, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
@@ -95,16 +104,23 @@
 }
 
 TEST(OringDurationTrackerTest, TestStopAll) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
+
+    const std::vector<HashableDimensionKey> kConditionKey1 =
+        {getMockedDimensionKey(TagId, 1, "maps")};
+    const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+    const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
-                                 bucketSizeNs, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
+                                 true, bucketStartTimeNs, bucketSizeNs, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
@@ -118,17 +134,24 @@
 }
 
 TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
+
+    const std::vector<HashableDimensionKey> kConditionKey1 =
+        {getMockedDimensionKey(TagId, 1, "maps")};
+    const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+    const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
-                                 bucketSizeNs, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
+                                 true, bucketStartTimeNs, bucketSizeNs, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -150,23 +173,30 @@
 }
 
 TEST(OringDurationTrackerTest, TestDurationConditionChange) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
+
+    const std::vector<HashableDimensionKey> kConditionKey1 =
+        {getMockedDimensionKey(TagId, 1, "maps")};
+    const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+    const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
 
-    EXPECT_CALL(*wizard, query(_, key1))  // #4
+    EXPECT_CALL(*wizard, query(_, key1, _, _))  // #4
             .WillOnce(Return(ConditionState::kFalse));
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
-                                 bucketStartTimeNs, bucketSizeNs, true, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
+                                 false, bucketStartTimeNs, bucketSizeNs, true, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
 
@@ -181,25 +211,32 @@
 }
 
 TEST(OringDurationTrackerTest, TestDurationConditionChange2) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
+
+    const std::vector<HashableDimensionKey> kConditionKey1 =
+        {getMockedDimensionKey(TagId, 1, "maps")};
+    const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+    const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
 
-    EXPECT_CALL(*wizard, query(_, key1))
+    EXPECT_CALL(*wizard, query(_, key1, _, _))
             .Times(2)
             .WillOnce(Return(ConditionState::kFalse))
             .WillOnce(Return(ConditionState::kTrue));
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
-                                 bucketStartTimeNs, bucketSizeNs, true, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
+                                 false, bucketStartTimeNs, bucketSizeNs, true, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     // condition to false; record duration 5n
@@ -216,22 +253,29 @@
 }
 
 TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
+
+    const std::vector<HashableDimensionKey> kConditionKey1 =
+        {getMockedDimensionKey(TagId, 1, "maps")};
+    const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+    const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
+    FieldMatcher dimensionInCondition;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     ConditionKey key1;
     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
 
-    EXPECT_CALL(*wizard, query(_, key1))  // #4
+    EXPECT_CALL(*wizard, query(_, key1, _, _))  // #4
             .WillOnce(Return(ConditionState::kFalse));
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
-                                 bucketSizeNs, true, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
+                                 true, bucketStartTimeNs, bucketSizeNs, true, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
@@ -249,6 +293,13 @@
 }
 
 TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
+
+    const std::vector<HashableDimensionKey> kConditionKey1 =
+        {getMockedDimensionKey(TagId, 1, "maps")};
+    const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+    const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
+    FieldMatcher dimensionInCondition;
     Alert alert;
     alert.set_id(101);
     alert.set_metric_id(1);
@@ -256,7 +307,7 @@
     alert.set_num_buckets(2);
     alert.set_refractory_period_secs(1);
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
@@ -264,8 +315,8 @@
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
     sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
-                                 bucketSizeNs, true, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
+                                 true, bucketStartTimeNs, bucketSizeNs, true, {anomalyTracker});
 
     // Nothing in the past bucket.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
@@ -310,6 +361,12 @@
 }
 
 TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
+
+    const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")};
+    const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+    const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
+    FieldMatcher dimensionInCondition;
     Alert alert;
     alert.set_id(101);
     alert.set_metric_id(1);
@@ -318,7 +375,7 @@
     const int32_t refPeriodSec = 45;
     alert.set_refractory_period_secs(refPeriodSec);
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
     uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
@@ -326,8 +383,8 @@
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
     sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
-                                 bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
+                                 true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
@@ -352,6 +409,13 @@
 }
 
 TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) {
+    const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
+
+    const std::vector<HashableDimensionKey> kConditionKey1 =
+        {getMockedDimensionKey(TagId, 1, "maps")};
+    const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+    const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
+    FieldMatcher dimensionInCondition;
     Alert alert;
     alert.set_id(101);
     alert.set_metric_id(1);
@@ -360,7 +424,7 @@
     const int32_t refPeriodSec = 45;
     alert.set_refractory_period_secs(refPeriodSec);
 
-    unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+    unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     ConditionKey conkey;
     conkey[StringToId("APP_BACKGROUND")] = kConditionKey1;
@@ -369,8 +433,9 @@
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
     sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
-                                 bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
+                                 true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false,
+                                 {anomalyTracker});
 
     tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
     EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index fff3dbf..55c078d 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -299,26 +299,26 @@
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
     // Value sum == 30 <= 130.
-    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
 
     // One event in bucket #2. No alarm as bucket #0 is trashed out.
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
     // Value sum == 130 <= 130.
-    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
 
     // Three events in bucket #3.
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
     // Anomaly at event 4 since Value sum == 131 > 130!
-    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
             event4->GetTimestampNs() / 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_DIMENSION_KEY),
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
             event4->GetTimestampNs() / 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_DIMENSION_KEY),
+    EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
             event6->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
 }
 
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.cpp b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
index fc7245c..ab9345a 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.cpp
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
@@ -26,6 +26,13 @@
     return HashableDimensionKey(dimensionsValue);
 }
 
+MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, string value) {
+    DimensionsValue dimensionsValue;
+    dimensionsValue.set_field(tagId);
+    dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field(key);
+    dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)->set_value_str(value);
+    return MetricDimensionKey(HashableDimensionKey(dimensionsValue), DEFAULT_DIMENSION_KEY);
+}
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index 23e86f9..0a97456 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -25,10 +25,12 @@
 
 class MockConditionWizard : public ConditionWizard {
 public:
-    MOCK_METHOD2(
+    MOCK_METHOD4(
             query,
             ConditionState(const int conditionIndex,
-                           const ConditionKey& conditionParameters));
+                           const ConditionKey& conditionParameters,
+                           const FieldMatcher& dimensionFields,
+                           std::unordered_set<HashableDimensionKey> *dimensionKeySet));
 };
 
 class MockStatsPullerManager : public StatsPullerManager {
@@ -39,6 +41,7 @@
 };
 
 HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value);
+MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, std::string value);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 9f4582d..13055cb 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <gtest/gtest.h>
 #include "statsd_test_util.h"
 
 namespace android {
@@ -27,6 +26,22 @@
     return atom_matcher;
 }
 
+AtomMatcher CreateScreenBrightnessChangedAtomMatcher() {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId("ScreenBrightnessChanged"));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(android::util::SCREEN_BRIGHTNESS_CHANGED);
+    return atom_matcher;
+}
+
+AtomMatcher CreateUidProcessStateChangedAtomMatcher() {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId("UidProcessStateChanged"));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(android::util::UID_PROCESS_STATE_CHANGED);
+    return atom_matcher;
+}
+
 AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name,
                                                   WakelockStateChanged::State state) {
     AtomMatcher atom_matcher;
@@ -47,6 +62,30 @@
     return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE);
 }
 
+AtomMatcher CreateBatterySaverModeStateChangedAtomMatcher(
+    const string& name, BatterySaverModeStateChanged::State state) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(android::util::BATTERY_SAVER_MODE_STATE_CHANGED);
+    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+    field_value_matcher->set_field(1);  // State field.
+    field_value_matcher->set_eq_int(state);
+    return atom_matcher;
+}
+
+AtomMatcher CreateBatterySaverModeStartAtomMatcher() {
+    return CreateBatterySaverModeStateChangedAtomMatcher(
+        "BatterySaverModeStart", BatterySaverModeStateChanged::ON);
+}
+
+
+AtomMatcher CreateBatterySaverModeStopAtomMatcher() {
+    return CreateBatterySaverModeStateChangedAtomMatcher(
+        "BatterySaverModeStop", BatterySaverModeStateChanged::OFF);
+}
+
+
 AtomMatcher CreateScreenStateChangedAtomMatcher(
     const string& name, android::view::DisplayStateEnum state) {
     AtomMatcher atom_matcher;
@@ -59,6 +98,7 @@
     return atom_matcher;
 }
 
+
 AtomMatcher CreateScreenTurnedOnAtomMatcher() {
     return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn",
             android::view::DisplayStateEnum::DISPLAY_STATE_ON);
@@ -128,6 +168,13 @@
         "ProcessCrashed", ProcessLifeCycleStateChanged::PROCESS_CRASHED);
 }
 
+Predicate CreateBatterySaverModePredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("BatterySaverIsOn"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("BatterySaverModeStart"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("BatterySaverModeStop"));
+    return predicate;
+}
 
 Predicate CreateScreenIsOnPredicate() {
     Predicate predicate;
@@ -218,6 +265,31 @@
     return event;
 }
 
+std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs) {
+    auto event = std::make_unique<LogEvent>(
+        android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs);
+    EXPECT_TRUE(event->write(BatterySaverModeStateChanged::ON));
+    event->init();
+    return event;
+}
+
+std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs) {
+    auto event = std::make_unique<LogEvent>(
+        android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs);
+    EXPECT_TRUE(event->write(BatterySaverModeStateChanged::OFF));
+    event->init();
+    return event;
+}
+
+std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
+    int level, uint64_t timestampNs) {
+    auto event = std::make_unique<LogEvent>(android::util::SCREEN_BRIGHTNESS_CHANGED, timestampNs);
+    EXPECT_TRUE(event->write(level));
+    event->init();
+    return event;
+
+}
+
 std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(
     const std::vector<AttributionNode>& attributions, const string& wakelockName,
     const WakelockStateChanged::State state, uint64_t timestampNs) {
@@ -267,9 +339,10 @@
 }
 
 std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(
-    const int uid, const string& name, const SyncStateChanged::State state, uint64_t timestampNs) {
+    const std::vector<AttributionNode>& attributions,
+    const string& name, const SyncStateChanged::State state, uint64_t timestampNs) {
     auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs);
-    event->write(uid);
+    event->write(attributions);
     event->write(name);
     event->write(state);
     event->init();
@@ -277,13 +350,13 @@
 }
 
 std::unique_ptr<LogEvent> CreateSyncStartEvent(
-    const int uid, const string& name, uint64_t timestampNs){
-    return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::ON, timestampNs);
+    const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs){
+    return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::ON, timestampNs);
 }
 
 std::unique_ptr<LogEvent> CreateSyncEndEvent(
-    const int uid, const string& name, uint64_t timestampNs) {
-    return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::OFF, timestampNs);
+    const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs) {
+    return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::OFF, timestampNs);
 }
 
 std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent(
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index ff8fef0c..6638893 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -14,6 +14,7 @@
 
 #pragma once
 
+#include <gtest/gtest.h>
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "statslog.h"
 #include "src/logd/LogEvent.h"
@@ -26,6 +27,18 @@
 // Create AtomMatcher proto to simply match a specific atom type.
 AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
 
+// Create AtomMatcher proto for screen brightness state changed.
+AtomMatcher CreateScreenBrightnessChangedAtomMatcher();
+
+// Create AtomMatcher proto for starting battery save mode.
+AtomMatcher CreateBatterySaverModeStartAtomMatcher();
+
+// Create AtomMatcher proto for stopping battery save mode.
+AtomMatcher CreateBatterySaverModeStopAtomMatcher();
+
+// Create AtomMatcher proto for process state changed.
+AtomMatcher CreateUidProcessStateChangedAtomMatcher();
+
 // Create AtomMatcher proto for acquiring wakelock.
 AtomMatcher CreateAcquireWakelockAtomMatcher();
 
@@ -59,6 +72,9 @@
 // Create Predicate proto for screen is off.
 Predicate CreateScreenIsOffPredicate();
 
+// Create Predicate proto for battery saver mode.
+Predicate CreateBatterySaverModePredicate();
+
 // Create Predicate proto for holding wakelock.
 Predicate CreateHoldingWakelockPredicate();
 
@@ -86,6 +102,15 @@
 std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
     const android::view::DisplayStateEnum state, uint64_t timestampNs);
 
+// Create log event for screen brightness state changed.
+std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
+   int level, uint64_t timestampNs);
+
+// Create log event when battery saver starts.
+std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs);
+// Create log event when battery saver stops.
+std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs);
+
 // Create log event for app moving to background.
 std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs);
 
@@ -94,11 +119,11 @@
 
 // Create log event when the app sync starts.
 std::unique_ptr<LogEvent> CreateSyncStartEvent(
-    const int uid, const string& name, uint64_t timestampNs);
+    const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs);
 
 // Create log event when the app sync ends.
 std::unique_ptr<LogEvent> CreateSyncEndEvent(
-    const int uid, const string& name, uint64_t timestampNs);
+    const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs);
 
 // Create log event when the app sync ends.
 std::unique_ptr<LogEvent> CreateAppCrashEvent(
@@ -136,9 +161,12 @@
 
 template <typename T>
 void sortMetricDataByDimensionsValue(const T& metricData, T* sortedMetricData) {
-    std::map<HashableDimensionKey, int> dimensionIndexMap;
+    std::map<MetricDimensionKey, int> dimensionIndexMap;
     for (int i = 0; i < metricData.data_size(); ++i) {
-        dimensionIndexMap.insert(std::make_pair(metricData.data(i).dimensions_in_what(), i));
+        dimensionIndexMap.insert(std::make_pair(
+            MetricDimensionKey(HashableDimensionKey(metricData.data(i).dimensions_in_what()),
+            HashableDimensionKey(metricData.data(i).dimensions_in_condition())),
+            i));
     }
     for (const auto& itr : dimensionIndexMap) {
         *sortedMetricData->add_data() = metricData.data(itr.second);
diff --git a/core/java/android/service/settings/suggestions/Suggestion.java b/core/java/android/service/settings/suggestions/Suggestion.java
index 11e1e67..e97f963a 100644
--- a/core/java/android/service/settings/suggestions/Suggestion.java
+++ b/core/java/android/service/settings/suggestions/Suggestion.java
@@ -40,6 +40,7 @@
      */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
             FLAG_HAS_BUTTON,
+            FLAG_ICON_TINTABLE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Flags {
@@ -49,6 +50,10 @@
      * Flag for suggestion type with a single button
      */
     public static final int FLAG_HAS_BUTTON = 1 << 0;
+    /**
+     * @hide
+     */
+    public static final int FLAG_ICON_TINTABLE = 1 << 1;
 
     private final String mId;
     private final CharSequence mTitle;
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
index c88cf5c..ba56d59 100644
--- a/core/jni/android/graphics/AnimatedImageDrawable.cpp
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -26,10 +26,11 @@
 #include <SkPictureRecorder.h>
 #include <hwui/AnimatedImageDrawable.h>
 #include <hwui/Canvas.h>
+#include <utils/Looper.h>
 
 using namespace android;
 
-static jmethodID gAnimatedImageDrawable_postOnAnimationEndMethodID;
+static jmethodID gAnimatedImageDrawable_onAnimationEndMethodID;
 
 // Note: jpostProcess holds a handle to the ImageDecoder.
 static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
@@ -123,9 +124,9 @@
     return drawable->start();
 }
 
-static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+static jboolean AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
-    drawable->stop();
+    return drawable->stop();
 }
 
 // Java's LOOP_INFINITE relies on this being the same.
@@ -137,33 +138,63 @@
     drawable->setRepetitionCount(loopCount);
 }
 
-class JniAnimationEndListener : public OnAnimationEndListener {
+class InvokeListener : public MessageHandler {
 public:
-    JniAnimationEndListener(JNIEnv* env, jobject javaObject) {
+    InvokeListener(JNIEnv* env, jobject javaObject) {
         LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJvm) != JNI_OK);
-        mJavaObject = env->NewGlobalRef(javaObject);
+        // Hold a weak reference to break a cycle that would prevent GC.
+        mWeakRef = env->NewWeakGlobalRef(javaObject);
     }
 
-    ~JniAnimationEndListener() override {
+    ~InvokeListener() override {
         auto* env = get_env_or_die(mJvm);
-        env->DeleteGlobalRef(mJavaObject);
+        env->DeleteWeakGlobalRef(mWeakRef);
     }
 
-    void onAnimationEnd() override {
+    virtual void handleMessage(const Message&) override {
         auto* env = get_env_or_die(mJvm);
-        env->CallVoidMethod(mJavaObject, gAnimatedImageDrawable_postOnAnimationEndMethodID);
+        jobject localRef = env->NewLocalRef(mWeakRef);
+        if (localRef) {
+            env->CallVoidMethod(localRef, gAnimatedImageDrawable_onAnimationEndMethodID);
+        }
     }
 
 private:
     JavaVM* mJvm;
-    jobject mJavaObject;
+    jweak mWeakRef;
+};
+
+class JniAnimationEndListener : public OnAnimationEndListener {
+public:
+    JniAnimationEndListener(sp<Looper>&& looper, JNIEnv* env, jobject javaObject) {
+        mListener = new InvokeListener(env, javaObject);
+        mLooper = std::move(looper);
+    }
+
+    void onAnimationEnd() override { mLooper->sendMessage(mListener, 0); }
+
+private:
+    sp<InvokeListener> mListener;
+    sp<Looper> mLooper;
 };
 
 static void AnimatedImageDrawable_nSetOnAnimationEndListener(JNIEnv* env, jobject /*clazz*/,
                                                              jlong nativePtr, jobject jdrawable) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
-    drawable->setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener>(
-                new JniAnimationEndListener(env, jdrawable)));
+    if (!jdrawable) {
+        drawable->setOnAnimationEndListener(nullptr);
+    } else {
+        sp<Looper> looper = Looper::getForThread();
+        if (!looper.get()) {
+            doThrowISE(env,
+                       "Must set AnimatedImageDrawable's AnimationCallback on a thread with a "
+                       "looper!");
+            return;
+        }
+
+        drawable->setOnAnimationEndListener(
+                std::make_unique<JniAnimationEndListener>(std::move(looper), env, jdrawable));
+    }
 }
 
 static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
@@ -186,7 +217,7 @@
     { "nSetColorFilter",     "(JJ)V",                                                        (void*) AnimatedImageDrawable_nSetColorFilter },
     { "nIsRunning",          "(J)Z",                                                         (void*) AnimatedImageDrawable_nIsRunning },
     { "nStart",              "(J)Z",                                                         (void*) AnimatedImageDrawable_nStart },
-    { "nStop",               "(J)V",                                                         (void*) AnimatedImageDrawable_nStop },
+    { "nStop",               "(J)Z",                                                         (void*) AnimatedImageDrawable_nStop },
     { "nSetLoopCount",       "(JI)V",                                                        (void*) AnimatedImageDrawable_nSetLoopCount },
     { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener },
     { "nNativeByteSize",     "(J)J",                                                         (void*) AnimatedImageDrawable_nNativeByteSize },
@@ -195,7 +226,7 @@
 
 int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
     jclass animatedImageDrawable_class = FindClassOrDie(env, "android/graphics/drawable/AnimatedImageDrawable");
-    gAnimatedImageDrawable_postOnAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "postOnAnimationEnd", "()V");
+    gAnimatedImageDrawable_onAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "onAnimationEnd", "()V");
 
     return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable",
             gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods));
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index afbc579..61a22c1 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1262,6 +1262,18 @@
     return VolumeShaperHelper::convertStateToJobject(env, gVolumeShaperFields, state);
 }
 
+static int android_media_AudioTrack_setPresentation(
+                                JNIEnv *env,  jobject thiz, jint presentationId, jint programId) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "AudioTrack not initialized");
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+
+    return (jint)lpTrack->selectPresentation((int)presentationId, (int)programId);
+}
+
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
 static const JNINativeMethod gMethods[] = {
@@ -1333,6 +1345,7 @@
     {"native_getVolumeShaperState",
             "(I)Landroid/media/VolumeShaper$State;",
                                         (void *)android_media_AudioTrack_get_volume_shaper_state},
+    {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation},
 };
 
 
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index bd49b87..27c8fda 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -348,7 +348,9 @@
         if (mState == null) {
             throw new IllegalStateException("called stop on empty AnimatedImageDrawable");
         }
-        nStop(mState.mNativePtr);
+        if (nStop(mState.mNativePtr)) {
+            postOnAnimationEnd();
+        }
     }
 
     // Animatable2 overrides
@@ -365,21 +367,31 @@
             nSetOnAnimationEndListener(mState.mNativePtr, this);
         }
 
-        mAnimationCallbacks.add(callback);
+        if (!mAnimationCallbacks.contains(callback)) {
+            mAnimationCallbacks.add(callback);
+        }
     }
 
     @Override
     public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
-        if (callback == null || mAnimationCallbacks == null) {
+        if (callback == null || mAnimationCallbacks == null
+                || !mAnimationCallbacks.remove(callback)) {
             return false;
         }
 
-        return mAnimationCallbacks.remove(callback);
+        if (mAnimationCallbacks.isEmpty()) {
+            clearAnimationCallbacks();
+        }
+
+        return true;
     }
 
     @Override
     public void clearAnimationCallbacks() {
-        mAnimationCallbacks = null;
+        if (mAnimationCallbacks != null) {
+            mAnimationCallbacks = null;
+            nSetOnAnimationEndListener(mState.mNativePtr, null);
+        }
     }
 
     private void postOnAnimationStart() {
@@ -413,6 +425,21 @@
         return mHandler;
     }
 
+    /**
+     *  Called by JNI.
+     *
+     *  The JNI code has already posted this to the thread that created the
+     *  callback, so no need to post.
+     */
+    @SuppressWarnings("unused")
+    private void onAnimationEnd() {
+        if (mAnimationCallbacks != null) {
+            for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
+                callback.onAnimationEnd(this);
+            }
+        }
+    }
+
 
     private static native long nCreate(long nativeImageDecoder,
             @Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
@@ -432,7 +459,7 @@
     @FastNative
     private static native boolean nStart(long nativePtr);
     @FastNative
-    private static native void nStop(long nativePtr);
+    private static native boolean nStop(long nativePtr);
     @FastNative
     private static native void nSetLoopCount(long nativePtr, int loopCount);
     // Pass the drawable down to native so it can call onAnimationEnd.
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 5356d3b..2bded9b 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -48,8 +48,10 @@
     return true;
 }
 
-void AnimatedImageDrawable::stop() {
+bool AnimatedImageDrawable::stop() {
+    bool wasRunning = mRunning;
     mRunning = false;
+    return wasRunning;
 }
 
 bool AnimatedImageDrawable::isRunning() {
@@ -180,7 +182,6 @@
     if (finalFrame) {
         if (mEndListener) {
             mEndListener->onAnimationEnd();
-            mEndListener = nullptr;
         }
     }
 }
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index 9d84ed5..2fd6f40 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -68,7 +68,9 @@
     // Returns true if the animation was started; false otherwise (e.g. it was
     // already running)
     bool start();
-    void stop();
+    // Returns true if the animation was stopped; false otherwise (e.g. it was
+    // already stopped)
+    bool stop();
     bool isRunning();
     void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); }
 
diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
new file mode 100644
index 0000000..4652c18
--- /dev/null
+++ b/media/java/android/media/AudioPresentation.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+
+/**
+ * The AudioPresentation class encapsulates the information that describes an audio presentation
+ * which is available in next generation audio content.
+ *
+ * Used by {@link MediaExtractor} {@link MediaExtractor#getAudioPresentations(int)} and
+ * {@link AudioTrack} {@link AudioTrack#setPresentation(AudioPresentation)} to query available
+ * presentations and to select one.
+ *
+ * A list of available audio presentations in a media source can be queried using
+ * {@link MediaExtractor#getAudioPresentations(int)}. This list can be presented to a user for
+ * selection.
+ * An AudioPresentation can be passed to an offloaded audio decoder via
+ * {@link AudioTrack#setPresentation(AudioPresentation)} to request decoding of the selected
+ * presentation. An audio stream may contain multiple presentations that differ by language,
+ * accessibility, end point mastering and dialogue enhancement. An audio presentation may also have
+ * a set of description labels in different languages to help the user to make an informed
+ * selection.
+ */
+public final class AudioPresentation {
+    private final int mPresentationId;
+    private final int mProgramId;
+    private final Map<String, String> mLabels;
+    private final String mLanguage;
+
+    /** @hide */
+    @IntDef(
+        value = {
+            MASTERING_NOT_INDICATED,
+            MASTERED_FOR_STEREO,
+            MASTERED_FOR_SURROUND,
+            MASTERED_FOR_3D,
+            MASTERED_FOR_HEADPHONE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MasteringIndicationType {}
+
+    private final @MasteringIndicationType int mMasteringIndication;
+    private final boolean mAudioDescriptionAvailable;
+    private final boolean mSpokenSubtitlesAvailable;
+    private final boolean mDialogueEnhancementAvailable;
+
+    /**
+     * No preferred reproduction channel layout.
+     */
+    public static final int MASTERING_NOT_INDICATED         = 0;
+    /**
+     * Stereo speaker layout.
+     */
+    public static final int MASTERED_FOR_STEREO             = 1;
+    /**
+     * Two-dimensional (e.g. 5.1) speaker layout.
+     */
+    public static final int MASTERED_FOR_SURROUND           = 2;
+    /**
+     * Three-dimensional (e.g. 5.1.2) speaker layout.
+     */
+    public static final int MASTERED_FOR_3D                 = 3;
+    /**
+     * Prerendered for headphone playback.
+     */
+    public static final int MASTERED_FOR_HEADPHONE          = 4;
+
+    AudioPresentation(int presentationId,
+                        int programId,
+                        Map<String, String> labels,
+                        String language,
+                        @MasteringIndicationType int masteringIndication,
+                        boolean audioDescriptionAvailable,
+                        boolean spokenSubtitlesAvailable,
+                        boolean dialogueEnhancementAvailable) {
+        this.mPresentationId = presentationId;
+        this.mProgramId = programId;
+        this.mLanguage = language;
+        this.mMasteringIndication = masteringIndication;
+        this.mAudioDescriptionAvailable = audioDescriptionAvailable;
+        this.mSpokenSubtitlesAvailable = spokenSubtitlesAvailable;
+        this.mDialogueEnhancementAvailable = dialogueEnhancementAvailable;
+
+        this.mLabels = new HashMap<String, String>(labels);
+    }
+
+    /**
+     * The framework uses this presentation id to select an audio presentation rendered by a
+     * decoder. Presentation id is typically sequential, but does not have to be.
+     * @hide
+     */
+    public int getPresentationId() {
+        return mPresentationId;
+    }
+
+    /**
+     * The framework uses this program id to select an audio presentation rendered by a decoder.
+     * Program id can be used to further uniquely identify the presentation to a decoder.
+     * @hide
+     */
+    public int getProgramId() {
+        return mProgramId;
+    }
+
+    /**
+     * @return a map of available text labels for this presentation. Each label is indexed by its
+     * locale corresponding to the language code as specified by ISO 639-2 [42]. Either ISO 639-2/B
+     * or ISO 639-2/T could be used.
+     */
+    public Map<Locale, String> getLabels() {
+        Map<Locale, String> localeLabels = new HashMap<>();
+        for (Map.Entry<String, String> entry : mLabels.entrySet()) {
+            localeLabels.put(new Locale(entry.getKey()), entry.getValue());
+        }
+        return localeLabels;
+    }
+
+    /**
+     * @return the locale corresponding to audio presentation's ISO 639-1/639-2 language code.
+     */
+    public Locale getLocale() {
+        return new Locale(mLanguage);
+    }
+
+    /**
+     * @return the mastering indication of the audio presentation.
+     * See {@link #MASTERING_NOT_INDICATED}, {@link #MASTERED_FOR_STEREO},
+     * {@link #MASTERED_FOR_SURROUND}, {@link #MASTERED_FOR_3D}, {@link #MASTERED_FOR_HEADPHONE}
+     */
+    @MasteringIndicationType
+    public int getMasteringIndication() {
+        return mMasteringIndication;
+    }
+
+    /**
+     * Indicates whether an audio description for the visually impaired is available.
+     * @return {@code true} if audio description is available.
+     */
+    public boolean hasAudioDescription() {
+        return mAudioDescriptionAvailable;
+    }
+
+    /**
+     * Indicates whether spoken subtitles for the visually impaired are available.
+     * @return {@code true} if spoken subtitles are available.
+     */
+    public boolean hasSpokenSubtitles() {
+        return mSpokenSubtitlesAvailable;
+    }
+
+    /**
+     * Indicates whether dialogue enhancement is available.
+     * @return {@code true} if dialogue enhancement is available.
+     */
+    public boolean hasDialogueEnhancement() {
+        return mDialogueEnhancementAvailable;
+    }
+}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 4e9ce8e..8e822a5 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2008,6 +2008,25 @@
     }
 
     /**
+     * Sets the audio presentation.
+     * If the audio presentation is invalid then {@link #ERROR_BAD_VALUE} will be returned.
+     * If a multi-stream decoder (MSD) is not present, or the format does not support
+     * multiple presentations, then {@link #ERROR_INVALID_OPERATION} will be returned.
+     * @param presentation see {@link AudioPresentation}. In particular, id should be set.
+     * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+     *    {@link #ERROR_INVALID_OPERATION}
+     * @throws IllegalArgumentException if the audio presentation is null.
+     * @throws IllegalStateException if track is not initialized.
+     */
+    public int setPresentation(@NonNull AudioPresentation presentation) {
+        if (presentation == null) {
+            throw new IllegalArgumentException("audio presentation is null");
+        }
+        return native_setPresentation(presentation.getPresentationId(),
+                presentation.getProgramId());
+    }
+
+    /**
      * Sets the initialization state of the instance. This method was originally intended to be used
      * in an AudioTrack subclass constructor to set a subclass-specific post-initialization state.
      * However, subclasses of AudioTrack are no longer recommended, so this method is obsolete.
@@ -3245,6 +3264,7 @@
             @NonNull VolumeShaper.Operation operation);
 
     private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id);
+    private native final int native_setPresentation(int presentationId, int programId);
 
     //---------------------------------------------------------
     // Utility methods
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 174d6a3..4919eeb 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -22,6 +22,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
+import android.media.AudioPresentation;
 import android.media.MediaCodec;
 import android.media.MediaFormat;
 import android.media.MediaHTTPService;
@@ -40,6 +41,7 @@
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
@@ -396,6 +398,17 @@
     }
 
     /**
+     * Get the list of available audio presentations for the track.
+     * @param trackIndex index of the track.
+     * @return a list of available audio presentations for a given valid audio track index.
+     * The list will be empty if the source does not contain any audio presentations.
+     */
+    @NonNull
+    public List<AudioPresentation> getAudioPresentations(int trackIndex) {
+        return new ArrayList<AudioPresentation>();
+    }
+
+    /**
      * Get the PSSH info if present.
      * @return a map of uuid-to-bytes, with the uuid specifying
      * the crypto scheme, and the bytes being the data specific to that scheme.
diff --git a/media/jni/android_media_AudioPresentation.h b/media/jni/android_media_AudioPresentation.h
new file mode 100644
index 0000000..71b8dac
--- /dev/null
+++ b/media/jni/android_media_AudioPresentation.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_MEDIA_AUDIO_PRESENTATION_H_
+#define _ANDROID_MEDIA_AUDIO_PRESENTATION_H_
+
+#include "jni.h"
+
+#include <media/AudioPresentationInfo.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include <nativehelper/ScopedLocalRef.h>
+
+namespace android {
+
+struct JAudioPresentationInfo {
+    struct fields_t {
+        jclass      clazz;
+        jmethodID   constructID;
+
+        // list parameters
+        jclass listclazz;
+        jmethodID listConstructId;
+        jmethodID listAddId;
+
+        void init(JNIEnv *env) {
+            jclass lclazz = env->FindClass("android/media/AudioPresentation");
+            if (lclazz == NULL) {
+                return;
+            }
+
+            clazz = (jclass)env->NewGlobalRef(lclazz);
+            if (clazz == NULL) {
+                return;
+            }
+
+            constructID = env->GetMethodID(clazz, "<init>",
+                                "(IILjava/util/Map;Ljava/lang/String;IZZZ)V");
+            env->DeleteLocalRef(lclazz);
+
+            // list objects
+            jclass llistclazz = env->FindClass("java/util/ArrayList");
+            CHECK(llistclazz != NULL);
+            listclazz = static_cast<jclass>(env->NewGlobalRef(llistclazz));
+            CHECK(listclazz != NULL);
+            listConstructId = env->GetMethodID(listclazz, "<init>", "()V");
+            CHECK(listConstructId != NULL);
+            listAddId = env->GetMethodID(listclazz, "add", "(Ljava/lang/Object;)Z");
+            CHECK(listAddId != NULL);
+            env->DeleteLocalRef(llistclazz);
+        }
+
+        void exit(JNIEnv *env) {
+            env->DeleteGlobalRef(clazz);
+            clazz = NULL;
+            env->DeleteGlobalRef(listclazz);
+            listclazz = NULL;
+        }
+    };
+
+    static status_t ConvertMessageToMap(JNIEnv *env, const sp<AMessage> &msg, jobject *map) {
+        ScopedLocalRef<jclass> hashMapClazz(env, env->FindClass("java/util/HashMap"));
+
+        if (hashMapClazz.get() == NULL) {
+            return -EINVAL;
+        }
+        jmethodID hashMapConstructID =
+            env->GetMethodID(hashMapClazz.get(), "<init>", "()V");
+
+        if (hashMapConstructID == NULL) {
+            return -EINVAL;
+        }
+        jmethodID hashMapPutID =
+            env->GetMethodID(
+                    hashMapClazz.get(),
+                    "put",
+                    "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+
+        if (hashMapPutID == NULL) {
+            return -EINVAL;
+        }
+
+        jobject hashMap = env->NewObject(hashMapClazz.get(), hashMapConstructID);
+
+        for (size_t i = 0; i < msg->countEntries(); ++i) {
+            AMessage::Type valueType;
+            const char *key = msg->getEntryNameAt(i, &valueType);
+
+            if (!strncmp(key, "android._", 9)) {
+                // don't expose private keys (starting with android._)
+                continue;
+            }
+
+            jobject valueObj = NULL;
+
+            AString val;
+            CHECK(msg->findString(key, &val));
+
+            valueObj = env->NewStringUTF(val.c_str());
+
+            if (valueObj != NULL) {
+                jstring keyObj = env->NewStringUTF(key);
+
+                env->CallObjectMethod(hashMap, hashMapPutID, keyObj, valueObj);
+
+                env->DeleteLocalRef(keyObj); keyObj = NULL;
+                env->DeleteLocalRef(valueObj); valueObj = NULL;
+            }
+        }
+
+        *map = hashMap;
+
+        return OK;
+    }
+
+    jobject asJobject(JNIEnv *env, const fields_t& fields, const AudioPresentationInfo &info) {
+        jobject list = env->NewObject(fields.listclazz, fields.listConstructId);
+
+        for (size_t i = 0; i < info.countPresentations(); ++i) {
+            const sp<AudioPresentation> &ap = info.getPresentation(i);
+            jobject jLabelObject;
+
+            sp<AMessage> labelMessage = new AMessage();
+            for (size_t i = 0; i < ap->mLabels.size(); ++i) {
+                labelMessage->setString(ap->mLabels.keyAt(i).string(),
+                                        ap->mLabels.valueAt(i).string());
+            }
+            if (ConvertMessageToMap(env, labelMessage, &jLabelObject) != OK) {
+                return NULL;
+            }
+            jstring jLanguage = env->NewStringUTF(ap->mLanguage.string());
+
+            jobject jValueObj = env->NewObject(fields.clazz, fields.constructID,
+                                static_cast<jint>(ap->mPresentationId),
+                                static_cast<jint>(ap->mProgramId),
+                                jLabelObject,
+                                jLanguage,
+                                static_cast<jint>(ap->mMasteringIndication),
+                                static_cast<jboolean>((ap->mAudioDescriptionAvailable == 1) ?
+                                    1 : 0),
+                                static_cast<jboolean>((ap->mSpokenSubtitlesAvailable == 1) ?
+                                    1 : 0),
+                                static_cast<jboolean>((ap->mDialogueEnhancementAvailable == 1) ?
+                                    1 : 0));
+            if (jValueObj == NULL) {
+                env->DeleteLocalRef(jLanguage); jLanguage = NULL;
+                return NULL;
+            }
+
+            env->CallBooleanMethod(list, fields.listAddId, jValueObj);
+            env->DeleteLocalRef(jValueObj); jValueObj = NULL;
+            env->DeleteLocalRef(jLanguage); jLanguage = NULL;
+        }
+        return list;
+    }
+};
+}  // namespace android
+
+#endif  // _ANDROID_MEDIA_AUDIO_PRESENTATION_H_
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
index 7983896..c23f226 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -17,21 +17,20 @@
 package com.android.settingslib.core.instrumentation;
 
 import android.app.Activity;
-import android.content.Context;
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
 import android.content.Intent;
 
 import android.os.SystemClock;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnPause;
-import com.android.settingslib.core.lifecycle.events.OnResume;
 
 import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;
 
 /**
  * Logs visibility change of a fragment.
  */
-public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPause {
+public class VisibilityLoggerMixin implements LifecycleObserver {
 
     private static final String TAG = "VisibilityLoggerMixin";
 
@@ -55,7 +54,7 @@
         mMetricsFeature = metricsFeature;
     }
 
-    @Override
+    @OnLifecycleEvent(Event.ON_RESUME)
     public void onResume() {
         mVisibleTimestamp = SystemClock.elapsedRealtime();
         if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
@@ -63,7 +62,7 @@
         }
     }
 
-    @Override
+    @OnLifecycleEvent(Event.ON_PAUSE)
     public void onPause() {
         mVisibleTimestamp = 0;
         if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
index a264886..1ab6afe 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
@@ -18,6 +18,7 @@
 import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;
 
 import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
@@ -30,6 +31,8 @@
 import android.content.Context;
 import android.content.Intent;
 
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.TestConfig;
@@ -39,6 +42,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.android.controller.ActivityController;
 import org.robolectric.annotation.Config;
 
 
@@ -110,6 +115,30 @@
                 .hidden(nullable(Context.class), anyInt());
     }
 
+    @Test
+    public void activityShouldBecomeVisibleAndHide() {
+        ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class);
+        TestActivity testActivity = ac.get();
+        MockitoAnnotations.initMocks(testActivity);
+        ac.create().start().resume();
+        verify(testActivity.mMetricsFeatureProvider, times(1)).visible(any(), anyInt(), anyInt());
+        ac.pause().stop().destroy();
+        verify(testActivity.mMetricsFeatureProvider, times(1)).hidden(any(), anyInt());
+    }
+
+    public static class TestActivity extends FragmentActivity {
+        @Mock
+        MetricsFeatureProvider mMetricsFeatureProvider;
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            VisibilityLoggerMixin mixin = new VisibilityLoggerMixin(
+                    TestInstrumentable.TEST_METRIC, mMetricsFeatureProvider);
+            getLifecycle().addObserver(mixin);
+            super.onCreate(savedInstanceState);
+        }
+    }
+
     private final class TestInstrumentable implements Instrumentable {
 
         public static final int TEST_METRIC = 12345;
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 997fe6d..100c2aa 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -25,16 +25,22 @@
     android:baselineAligned="false"
     android:clickable="false"
     android:clipChildren="false"
-    android:clipToPadding="false"
-    android:paddingTop="0dp"
-    android:gravity="center_vertical"
-    android:orientation="horizontal">
+    android:clipToPadding="false">
+
+    <View
+        android:id="@+id/qs_footer_divider"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:layout_gravity="top"
+        android:background="?android:attr/dividerHorizontal"/>
 
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:layout_marginTop="1dp"
         android:layout_marginStart="8dp"
         android:layout_marginEnd="8dp"
+        android:layout_gravity="center_vertical"
         android:gravity="end" >
 
         <LinearLayout
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 76baee4..9c87e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -75,6 +75,7 @@
     private boolean mListening;
 
     private boolean mShowEmergencyCallsOnly;
+    private View mDivider;
     protected MultiUserSwitch mMultiUserSwitch;
     private ImageView mMultiUserAvatar;
 
@@ -93,8 +94,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        Resources res = getResources();
-
+        mDivider = findViewById(R.id.qs_footer_divider);
         mEdit = findViewById(android.R.id.edit);
         mEdit.setOnClickListener(view ->
                 Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
@@ -162,6 +162,7 @@
     @Nullable
     private TouchAnimator createSettingsAlphaAnimator() {
         return new TouchAnimator.Builder()
+                .addFloat(mDivider, "alpha", 0, 1)
                 .addFloat(mCarrierText, "alpha", 0, 1)
                 .addFloat(mActionsContainer, "alpha", 0, 1)
                 .build();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 3dfb913..3ebeb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -379,7 +379,7 @@
         // Because space is usually constrained in the auto use-case, there should not be a
         // pinned notification when the shade has been expanded. Ensure this by removing all heads-
         // up notifications.
-        mHeadsUpManager.releaseAllImmediately();
+        mHeadsUpManager.removeAllHeadsUpEntries();
         super.animateExpandNotificationsPanel();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
deleted file mode 100644
index c45c538..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ /dev/null
@@ -1,455 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Resources;
-import android.support.v4.util.ArraySet;
-import android.util.Log;
-import android.util.Pools;
-import android.view.View;
-import android.view.ViewTreeObserver;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dumpable;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.Stack;
-
-/**
- * A implementation of HeadsUpManager for phone and car.
- */
-public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
-       ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback,
-       OnHeadsUpChangedListener {
-    private static final String TAG = "HeadsUpManagerPhone";
-    private static final boolean DEBUG = false;
-
-    private final View mStatusBarWindowView;
-    private final int mStatusBarHeight;
-    private final NotificationGroupManager mGroupManager;
-    private final StatusBar mBar;
-    private final VisualStabilityManager mVisualStabilityManager;
-
-    private boolean mReleaseOnExpandFinish;
-    private boolean mTrackingHeadsUp;
-    private HashSet<String> mSwipedOutKeys = new HashSet<>();
-    private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
-    private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
-            = new ArraySet<>();
-    private boolean mIsExpanded;
-    private int[] mTmpTwoArray = new int[2];
-    private boolean mHeadsUpGoingAway;
-    private boolean mWaitingOnCollapseWhenGoingAway;
-    private boolean mIsObserving;
-    private int mStatusBarState;
-
-    private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
-        private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
-
-        @Override
-        public HeadsUpEntryPhone acquire() {
-            if (!mPoolObjects.isEmpty()) {
-                return mPoolObjects.pop();
-            }
-            return new HeadsUpEntryPhone();
-        }
-
-        @Override
-        public boolean release(@NonNull HeadsUpEntryPhone instance) {
-            instance.reset();
-            mPoolObjects.push(instance);
-            return true;
-        }
-    };
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  Constructor:
-
-    public HeadsUpManagerPhone(@NonNull final Context context, @NonNull View statusBarWindowView,
-            @NonNull NotificationGroupManager groupManager, @NonNull StatusBar bar,
-            @NonNull VisualStabilityManager visualStabilityManager) {
-        super(context);
-
-        mStatusBarWindowView = statusBarWindowView;
-        mGroupManager = groupManager;
-        mBar = bar;
-        mVisualStabilityManager = visualStabilityManager;
-
-        Resources resources = mContext.getResources();
-        mStatusBarHeight = resources.getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_height);
-
-        addListener(new OnHeadsUpChangedListener() {
-            @Override
-            public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) {
-                if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged");
-                updateTouchableRegionListener();
-            }
-        });
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  Public methods:
-
-    /**
-     * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
-     * that a user might have consciously clicked on it.
-     *
-     * @param key the key of the touched notification
-     * @return whether the touch is invalid and should be discarded
-     */
-    public boolean shouldSwallowClick(@NonNull String key) {
-        HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
-        if (entry != null && mClock.currentTimeMillis() < entry.postTime) {
-            return true;
-        }
-        return false;
-    }
-
-    public void onExpandingFinished() {
-        if (mReleaseOnExpandFinish) {
-            releaseAllImmediately();
-            mReleaseOnExpandFinish = false;
-        } else {
-            for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
-                if (isHeadsUp(entry.key)) {
-                    // Maybe the heads-up was removed already
-                    removeHeadsUpEntry(entry);
-                }
-            }
-        }
-        mEntriesToRemoveAfterExpand.clear();
-    }
-
-    /**
-     * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
-     * from the list even after a Heads Up Notification is gone.
-     */
-    public void setTrackingHeadsUp(boolean trackingHeadsUp) {
-        mTrackingHeadsUp = trackingHeadsUp;
-    }
-
-    /**
-     * Notify that the status bar panel gets expanded or collapsed.
-     *
-     * @param isExpanded True to notify expanded, false to notify collapsed.
-     */
-    public void setIsPanelExpanded(boolean isExpanded) {
-        if (isExpanded != mIsExpanded) {
-            mIsExpanded = isExpanded;
-            if (isExpanded) {
-                // make sure our state is sane
-                mWaitingOnCollapseWhenGoingAway = false;
-                mHeadsUpGoingAway = false;
-                updateTouchableRegionListener();
-            }
-        }
-    }
-
-    /**
-     * Set the current state of the statusbar.
-     */
-    public void setStatusBarState(int statusBarState) {
-        mStatusBarState = statusBarState;
-    }
-
-    /**
-     * Set that we are exiting the headsUp pinned mode, but some notifications might still be
-     * animating out. This is used to keep the touchable regions in a sane state.
-     */
-    public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
-        if (headsUpGoingAway != mHeadsUpGoingAway) {
-            mHeadsUpGoingAway = headsUpGoingAway;
-            if (!headsUpGoingAway) {
-                waitForStatusBarLayout();
-            }
-            updateTouchableRegionListener();
-        }
-    }
-
-    /**
-     * Notifies that a remote input textbox in notification gets active or inactive.
-     * @param entry The entry of the target notification.
-     * @param remoteInputActive True to notify active, False to notify inactive.
-     */
-    public void setRemoteInputActive(
-            @NonNull NotificationData.Entry entry, boolean remoteInputActive) {
-        HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key);
-        if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
-            headsUpEntry.remoteInputActive = remoteInputActive;
-            if (remoteInputActive) {
-                headsUpEntry.removeAutoRemovalCallbacks();
-            } else {
-                headsUpEntry.updateEntry(false /* updatePostTime */);
-            }
-        }
-    }
-
-    @VisibleForTesting
-    public void removeMinimumDisplayTimeForTesting() {
-        mMinimumDisplayTime = 1;
-        mHeadsUpNotificationDecay = 1;
-        mTouchAcceptanceDelay = 1;
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  HeadsUpManager public methods overrides:
-
-    @Override
-    public boolean isTrackingHeadsUp() {
-        return mTrackingHeadsUp;
-    }
-
-    @Override
-    public void snooze() {
-        super.snooze();
-        mReleaseOnExpandFinish = true;
-    }
-
-    /**
-     * React to the removal of the notification in the heads up.
-     *
-     * @return true if the notification was removed and false if it still needs to be kept around
-     * for a bit since it wasn't shown long enough
-     */
-    @Override
-    public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
-        if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
-            return super.removeNotification(key, ignoreEarliestRemovalTime);
-        } else {
-            HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key);
-            entry.removeAsSoonAsPossible();
-            return false;
-        }
-    }
-
-    public void addSwipedOutNotification(@NonNull String key) {
-        mSwipedOutKeys.add(key);
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  Dumpable overrides:
-
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("HeadsUpManagerPhone state:");
-        dumpInternal(fd, pw, args);
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  ViewTreeObserver.OnComputeInternalInsetsListener overrides:
-
-    /**
-     * Overridden from TreeObserver.
-     */
-    @Override
-    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
-        if (mIsExpanded || mBar.isBouncerShowing()) {
-            // The touchable region is always the full area when expanded
-            return;
-        }
-        if (hasPinnedHeadsUp()) {
-            ExpandableNotificationRow topEntry = getTopEntry().row;
-            if (topEntry.isChildInGroup()) {
-                final ExpandableNotificationRow groupSummary
-                        = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
-                if (groupSummary != null) {
-                    topEntry = groupSummary;
-                }
-            }
-            topEntry.getLocationOnScreen(mTmpTwoArray);
-            int minX = mTmpTwoArray[0];
-            int maxX = mTmpTwoArray[0] + topEntry.getWidth();
-            int maxY = topEntry.getIntrinsicHeight();
-
-            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-            info.touchableRegion.set(minX, 0, maxX, maxY);
-        } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
-            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-            info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
-        }
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  VisualStabilityManager.Callback overrides:
-
-    @Override
-    public void onReorderingAllowed() {
-        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
-        for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
-            if (isHeadsUp(entry.key)) {
-                // Maybe the heads-up was removed already
-                removeHeadsUpEntry(entry);
-            }
-        }
-        mEntriesToRemoveWhenReorderingAllowed.clear();
-        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  HeadsUpManager utility (protected) methods overrides:
-
-    @Override
-    protected HeadsUpEntry createHeadsUpEntry() {
-        return mEntryPool.acquire();
-    }
-
-    @Override
-    protected void releaseHeadsUpEntry(HeadsUpEntry entry) {
-        mEntryPool.release((HeadsUpEntryPhone) entry);
-    }
-
-    @Override
-    protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
-          return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded
-                  || super.shouldHeadsUpBecomePinned(entry);
-    }
-
-    @Override
-    protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
-        super.dumpInternal(fd, pw, args);
-        pw.print("  mStatusBarState="); pw.println(mStatusBarState);
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  Protected utility methods:
-
-    @Nullable
-    protected HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
-        return (HeadsUpEntryPhone) getHeadsUpEntry(key);
-    }
-
-    @Nullable
-    protected HeadsUpEntryPhone getTopHeadsUpEntryPhone() {
-        return (HeadsUpEntryPhone) getTopHeadsUpEntry();
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  Private utility methods:
-
-    private boolean wasShownLongEnough(@NonNull String key) {
-        if (mSwipedOutKeys.contains(key)) {
-            // We always instantly dismiss views being manually swiped out.
-            mSwipedOutKeys.remove(key);
-            return true;
-        }
-
-        HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key);
-        HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
-        if (headsUpEntry != topEntry) {
-            return true;
-        }
-        return headsUpEntry.wasShownLongEnough();
-    }
-
-    /**
-     * We need to wait on the whole panel to collapse, before we can remove the touchable region
-     * listener.
-     */
-    private void waitForStatusBarLayout() {
-        mWaitingOnCollapseWhenGoingAway = true;
-        mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
-            @Override
-            public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                    int oldLeft,
-                    int oldTop, int oldRight, int oldBottom) {
-                if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
-                    mStatusBarWindowView.removeOnLayoutChangeListener(this);
-                    mWaitingOnCollapseWhenGoingAway = false;
-                    updateTouchableRegionListener();
-                }
-            }
-        });
-    }
-
-    private void updateTouchableRegionListener() {
-        boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway
-                || mWaitingOnCollapseWhenGoingAway;
-        if (shouldObserve == mIsObserving) {
-            return;
-        }
-        if (shouldObserve) {
-            mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
-            mStatusBarWindowView.requestLayout();
-        } else {
-            mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
-        }
-        mIsObserving = shouldObserve;
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  HeadsUpEntryPhone:
-
-    protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry {
-        public void setEntry(@NonNull final NotificationData.Entry entry) {
-           Runnable removeHeadsUpRunnable = () -> {
-                if (!mVisualStabilityManager.isReorderingAllowed()) {
-                    mEntriesToRemoveWhenReorderingAllowed.add(entry);
-                    mVisualStabilityManager.addReorderingAllowedCallback(
-                            HeadsUpManagerPhone.this);
-                } else if (!mTrackingHeadsUp) {
-                    removeHeadsUpEntry(entry);
-                } else {
-                    mEntriesToRemoveAfterExpand.add(entry);
-                }
-            };
-
-            super.setEntry(entry, removeHeadsUpRunnable);
-        }
-
-        public boolean wasShownLongEnough() {
-            return earliestRemovaltime < mClock.currentTimeMillis();
-        }
-
-        @Override
-        public void updateEntry(boolean updatePostTime) {
-            super.updateEntry(updatePostTime);
-
-            if (mEntriesToRemoveAfterExpand.contains(entry)) {
-                mEntriesToRemoveAfterExpand.remove(entry);
-            }
-            if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
-                mEntriesToRemoveWhenReorderingAllowed.remove(entry);
-            }
-        }
-
-        @Override
-        public void expanded(boolean expanded) {
-            if (this.expanded == expanded) {
-                return;
-            }
-
-            this.expanded = expanded;
-            if (expanded) {
-                removeAutoRemovalCallbacks();
-            } else {
-                updateEntry(false /* updatePostTime */);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 2bfdefe..c85571c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -23,7 +23,7 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 /**
@@ -31,7 +31,7 @@
  */
 public class HeadsUpTouchHelper implements Gefingerpoken {
 
-    private HeadsUpManagerPhone mHeadsUpManager;
+    private HeadsUpManager mHeadsUpManager;
     private NotificationStackScrollLayout mStackScroller;
     private int mTrackingPointer;
     private float mTouchSlop;
@@ -43,7 +43,7 @@
     private NotificationPanelView mPanel;
     private ExpandableNotificationRow mPickedChild;
 
-    public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
+    public HeadsUpTouchHelper(HeadsUpManager headsUpManager,
             NotificationStackScrollLayout stackScroller,
             NotificationPanelView notificationPanelView) {
         mHeadsUpManager = headsUpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 52d005c..cd2e77a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -68,12 +68,14 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 import java.util.List;
+import java.util.Collection;
 
 public class NotificationPanelView extends PanelView implements
         ExpandableView.OnHeightChangedListener,
@@ -1569,7 +1571,7 @@
     private void updatePanelExpanded() {
         boolean isExpanded = !isFullyCollapsed();
         if (mPanelExpanded != isExpanded) {
-            mHeadsUpManager.setIsPanelExpanded(isExpanded);
+            mHeadsUpManager.setIsExpanded(isExpanded);
             mStatusBar.setPanelExpanded(isExpanded);
             mPanelExpanded = isExpanded;
         }
@@ -2336,7 +2338,7 @@
     }
 
     @Override
-    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
+    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
         super.setHeadsUpManager(headsUpManager);
         mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller,
                 this);
@@ -2628,8 +2630,8 @@
         }
     }
 
-    public void setPulsing(boolean pulsing) {
-        mKeyguardStatusView.setPulsing(pulsing);
+    public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
+        mKeyguardStatusView.setPulsing(pulsing != null);
         positionClockAndNotifications();
         mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1]
                 + mKeyguardStatusView.getClockBottom());
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 6daabed..2b7e474 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -50,7 +50,7 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -75,7 +75,7 @@
     }
 
     protected StatusBar mStatusBar;
-    protected HeadsUpManagerPhone mHeadsUpManager;
+    protected HeadsUpManager mHeadsUpManager;
 
     private float mPeekHeight;
     private float mHintDistance;
@@ -1252,7 +1252,7 @@
      */
     protected abstract int getClearAllHeight();
 
-    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
+    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
         mHeadsUpManager = headsUpManager;
     }
 
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 3777a6c..1bf719a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -208,7 +208,6 @@
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -220,7 +219,6 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
@@ -811,14 +809,15 @@
                 .commit();
         mIconController = Dependency.get(StatusBarIconController.class);
 
-        mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this,
-                mVisualStabilityManager);
+        mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager);
+        mHeadsUpManager.setBar(this);
         mHeadsUpManager.addListener(this);
         mHeadsUpManager.addListener(mNotificationPanel);
         mHeadsUpManager.addListener(mGroupManager);
         mHeadsUpManager.addListener(mVisualStabilityManager);
         mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
+        mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
         putComponent(HeadsUpManager.class, mHeadsUpManager);
 
         mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
@@ -1349,8 +1348,7 @@
 
     @Override
     public void onPerformRemoveNotification(StatusBarNotification n) {
-        if (mStackScroller.hasPulsingNotifications() &&
-                    !mHeadsUpManager.hasHeadsUpNotifications()) {
+        if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
             // We were showing a pulse for a notification, but no notifications are pulsing anymore.
             // Finish the pulse.
             mDozeScrimController.pulseOutNow();
@@ -2099,8 +2097,9 @@
     }
 
     public void maybeEscalateHeadsUp() {
-        mHeadsUpManager.getAllEntries().forEach(entry -> {
-            final StatusBarNotification sbn = entry.notification;
+        Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries();
+        for (HeadsUpManager.HeadsUpEntry entry : entries) {
+            final StatusBarNotification sbn = entry.entry.notification;
             final Notification notification = sbn.getNotification();
             if (notification.fullScreenIntent != null) {
                 if (DEBUG) {
@@ -2110,11 +2109,11 @@
                     EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
                             sbn.getKey());
                     notification.fullScreenIntent.send();
-                    entry.notifyFullScreenIntentLaunched();
+                    entry.entry.notifyFullScreenIntentLaunched();
                 } catch (PendingIntent.CanceledException e) {
                 }
             }
-        });
+        }
         mHeadsUpManager.releaseAllImmediately();
     }
 
@@ -4659,22 +4658,24 @@
                 @Override
                 public void onPulseStarted() {
                     callback.onPulseStarted();
-                    if (mHeadsUpManager.hasHeadsUpNotifications()) {
+                    Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries =
+                            mHeadsUpManager.getAllEntries();
+                    if (!pulsingEntries.isEmpty()) {
                         // Only pulse the stack scroller if there's actually something to show.
                         // Otherwise just show the always-on screen.
-                        setPulsing(true);
+                        setPulsing(pulsingEntries);
                     }
                 }
 
                 @Override
                 public void onPulseFinished() {
                     callback.onPulseFinished();
-                    setPulsing(false);
+                    setPulsing(null);
                 }
 
-                private void setPulsing(boolean pulsing) {
+                private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
                     mNotificationPanel.setPulsing(pulsing);
-                    mVisualStabilityManager.setPulsing(pulsing);
+                    mVisualStabilityManager.setPulsing(pulsing != null);
                     mIgnoreTouchWhilePulsing = false;
                 }
             }, reason);
@@ -4822,7 +4823,7 @@
 
 
     // for heads up notifications
-    protected HeadsUpManagerPhone mHeadsUpManager;
+    protected HeadsUpManager mHeadsUpManager;
 
     private AboveShelfObserver mAboveShelfObserver;
 
@@ -4925,7 +4926,7 @@
                 // Release the HUN notification to the shade.
 
                 if (isPresenterFullyCollapsed()) {
-                    HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
+                    HeadsUpManager.setIsClickedNotification(row, true);
                 }
                 //
                 // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index a2b896d..53dfb24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -16,68 +16,118 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.os.SystemClock;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.ArrayMap;
+import android.os.SystemClock;
 import android.provider.Settings;
+import android.support.v4.util.ArraySet;
+import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.Iterator;
-import java.util.stream.Stream;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Stack;
 
 /**
  * A manager which handles heads up notifications which is a special mode where
  * they simply peek from the top of the screen.
  */
-public class HeadsUpManager {
+public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener,
+        VisualStabilityManager.Callback {
     private static final String TAG = "HeadsUpManager";
     private static final boolean DEBUG = false;
     private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
+    private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
 
-    protected final Clock mClock = new Clock();
-    protected final Context mContext;
-    protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
-    protected final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final int mHeadsUpNotificationDecay;
+    private final int mMinimumDisplayTime;
 
-    protected int mHeadsUpNotificationDecay;
-    protected int mMinimumDisplayTime;
-    protected int mTouchAcceptanceDelay;
-    protected int mSnoozeLengthMs;
-    protected boolean mHasPinnedNotification;
-    protected int mUser;
-
-    private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
+    private final int mTouchAcceptanceDelay;
     private final ArrayMap<String, Long> mSnoozedPackages;
-    private final ContentObserver mSettingsObserver;
+    private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
+    private final int mDefaultSnoozeLengthMs;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
 
-    public HeadsUpManager(@NonNull final Context context) {
+        private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
+
+        @Override
+        public HeadsUpEntry acquire() {
+            if (!mPoolObjects.isEmpty()) {
+                return mPoolObjects.pop();
+            }
+            return new HeadsUpEntry();
+        }
+
+        @Override
+        public boolean release(HeadsUpEntry instance) {
+            instance.reset();
+            mPoolObjects.push(instance);
+            return true;
+        }
+    };
+
+    private final View mStatusBarWindowView;
+    private final int mStatusBarHeight;
+    private final Context mContext;
+    private final NotificationGroupManager mGroupManager;
+    private StatusBar mBar;
+    private int mSnoozeLengthMs;
+    private ContentObserver mSettingsObserver;
+    private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
+    private HashSet<String> mSwipedOutKeys = new HashSet<>();
+    private int mUser;
+    private Clock mClock;
+    private boolean mReleaseOnExpandFinish;
+    private boolean mTrackingHeadsUp;
+    private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
+    private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
+            = new ArraySet<>();
+    private boolean mIsExpanded;
+    private boolean mHasPinnedNotification;
+    private int[] mTmpTwoArray = new int[2];
+    private boolean mHeadsUpGoingAway;
+    private boolean mWaitingOnCollapseWhenGoingAway;
+    private boolean mIsObserving;
+    private boolean mRemoteInputActive;
+    private float mExpandedHeight;
+    private VisualStabilityManager mVisualStabilityManager;
+    private int mStatusBarState;
+
+    public HeadsUpManager(final Context context, View statusBarWindowView,
+                          NotificationGroupManager groupManager) {
         mContext = context;
-        Resources resources = context.getResources();
-        mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
-        mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
+        Resources resources = mContext.getResources();
         mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
         mSnoozedPackages = new ArrayMap<>();
-        int defaultSnoozeLengthMs =
-                resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
+        mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
+        mSnoozeLengthMs = mDefaultSnoozeLengthMs;
+        mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
+        mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
+        mClock = new Clock();
 
         mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
-                SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs);
+                SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
         mSettingsObserver = new ContentObserver(mHandler) {
             @Override
             public void onChange(boolean selfChange) {
@@ -92,26 +142,47 @@
         context.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
                 mSettingsObserver);
+        mStatusBarWindowView = statusBarWindowView;
+        mGroupManager = groupManager;
+        mStatusBarHeight = resources.getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
     }
 
-    /**
-     * Adds an OnHeadUpChangedListener to observe events.
-     */
-    public void addListener(@NonNull OnHeadsUpChangedListener listener) {
+    private void updateTouchableRegionListener() {
+        boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway
+                || mWaitingOnCollapseWhenGoingAway;
+        if (shouldObserve == mIsObserving) {
+            return;
+        }
+        if (shouldObserve) {
+            mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+            mStatusBarWindowView.requestLayout();
+        } else {
+            mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+        }
+        mIsObserving = shouldObserve;
+    }
+
+    public void setBar(StatusBar bar) {
+        mBar = bar;
+    }
+
+    public void addListener(OnHeadsUpChangedListener listener) {
         mListeners.add(listener);
     }
 
-    /**
-     * Removes the OnHeadUpChangedListener from the observer list.
-     */
-    public void removeListener(@NonNull OnHeadsUpChangedListener listener) {
+    public void removeListener(OnHeadsUpChangedListener listener) {
         mListeners.remove(listener);
     }
 
+    public StatusBar getBar() {
+        return mBar;
+    }
+
     /**
      * Called when posting a new notification to the heads up.
      */
-    public void showNotification(@NonNull NotificationData.Entry headsUp) {
+    public void showNotification(NotificationData.Entry headsUp) {
         if (DEBUG) Log.v(TAG, "showNotification");
         addHeadsUpEntry(headsUp);
         updateNotification(headsUp, true);
@@ -121,7 +192,7 @@
     /**
      * Called when updating or posting a notification to the heads up.
      */
-    public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) {
+    public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
         if (DEBUG) Log.v(TAG, "updateNotification");
 
         headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
@@ -133,13 +204,14 @@
                 // with the groupmanager
                 return;
             }
-            headsUpEntry.updateEntry(true /* updatePostTime */);
+            headsUpEntry.updateEntry();
             setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
         }
     }
 
-    private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) {
-        HeadsUpEntry headsUpEntry = createHeadsUpEntry();
+    private void addHeadsUpEntry(NotificationData.Entry entry) {
+        HeadsUpEntry headsUpEntry = mEntryPool.acquire();
+
         // This will also add the entry to the sortedList
         headsUpEntry.setEntry(entry);
         mHeadsUpEntries.put(entry.key, headsUpEntry);
@@ -151,17 +223,16 @@
         entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
     }
 
-    protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) {
-        return hasFullScreenIntent(entry);
+    private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
+        return mStatusBarState != StatusBarState.KEYGUARD
+                && !mIsExpanded || hasFullScreenIntent(entry);
     }
 
-    protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) {
+    private boolean hasFullScreenIntent(NotificationData.Entry entry) {
         return entry.notification.getNotification().fullScreenIntent != null;
     }
 
-    protected void setEntryPinned(
-            @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) {
-        if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned);
+    private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) {
         ExpandableNotificationRow row = headsUpEntry.entry.row;
         if (row.isPinned() != isPinned) {
             row.setPinned(isPinned);
@@ -176,35 +247,33 @@
         }
     }
 
-    protected void removeHeadsUpEntry(NotificationData.Entry entry) {
+    private void removeHeadsUpEntry(NotificationData.Entry entry) {
         HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
-        onHeadsUpEntryRemoved(remove);
-        releaseHeadsUpEntry(remove);
-    }
-
-    protected void onHeadsUpEntryRemoved(HeadsUpEntry remove) {
-        NotificationData.Entry entry = remove.entry;
         entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
         entry.row.setHeadsUp(false);
         setEntryPinned(remove, false /* isPinned */);
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpStateChanged(entry, false);
         }
+        mEntryPool.release(remove);
     }
 
-    protected void updatePinnedMode() {
+    public void removeAllHeadsUpEntries() {
+        for (String key : mHeadsUpEntries.keySet()) {
+            removeHeadsUpEntry(mHeadsUpEntries.get(key).entry);
+        }
+    }
+
+    private void updatePinnedMode() {
         boolean hasPinnedNotification = hasPinnedNotificationInternal();
         if (hasPinnedNotification == mHasPinnedNotification) {
             return;
         }
-        if (DEBUG) {
-            Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " +
-                       hasPinnedNotification);
-        }
         mHasPinnedNotification = hasPinnedNotification;
         if (mHasPinnedNotification) {
             MetricsLogger.count(mContext, "note_peek", 1);
         }
+        updateTouchableRegionListener();
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
         }
@@ -216,36 +285,47 @@
      * @return true if the notification was removed and false if it still needs to be kept around
      * for a bit since it wasn't shown long enough
      */
-    public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
-        if (DEBUG) Log.v(TAG, "removeNotification");
-        releaseImmediately(key);
-        return true;
+    public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) {
+        if (DEBUG) Log.v(TAG, "remove");
+        if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
+            releaseImmediately(key);
+            return true;
+        } else {
+            getHeadsUpEntry(key).removeAsSoonAsPossible();
+            return false;
+        }
     }
 
-    /**
-     * Returns if the given notification is in the Heads Up Notification list or not.
-     */
+    private boolean wasShownLongEnough(String key) {
+        HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+        HeadsUpEntry topEntry = getTopEntry();
+        if (mSwipedOutKeys.contains(key)) {
+            // We always instantly dismiss views being manually swiped out.
+            mSwipedOutKeys.remove(key);
+            return true;
+        }
+        if (headsUpEntry != topEntry) {
+            return true;
+        }
+        return headsUpEntry.wasShownLongEnough();
+    }
+
     public boolean isHeadsUp(String key) {
         return mHeadsUpEntries.containsKey(key);
     }
 
     /**
-     * Pushes any current Heads Up notification down into the shade.
+     * Push any current Heads Up notification down into the shade.
      */
     public void releaseAllImmediately() {
         if (DEBUG) Log.v(TAG, "releaseAllImmediately");
-        Iterator<HeadsUpEntry> iterator = mHeadsUpEntries.values().iterator();
-        while (iterator.hasNext()) {
-            HeadsUpEntry entry = iterator.next();
-            iterator.remove();
-            onHeadsUpEntryRemoved(entry);
+        ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet());
+        for (String key : keys) {
+            releaseImmediately(key);
         }
     }
 
-    /**
-     * Pushes the given Heads Up notification down into the shade.
-     */
-    public void releaseImmediately(@NonNull String key) {
+    public void releaseImmediately(String key) {
         HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
         if (headsUpEntry == null) {
             return;
@@ -254,14 +334,11 @@
         removeHeadsUpEntry(shadeEntry);
     }
 
-    /**
-     * Returns if the given notification is snoozed or not.
-     */
-    public boolean isSnoozed(@NonNull String packageName) {
+    public boolean isSnoozed(String packageName) {
         final String key = snoozeKey(packageName, mUser);
         Long snoozedUntil = mSnoozedPackages.get(key);
         if (snoozedUntil != null) {
-            if (snoozedUntil > mClock.currentTimeMillis()) {
+            if (snoozedUntil > SystemClock.elapsedRealtime()) {
                 if (DEBUG) Log.v(TAG, key + " snoozed");
                 return true;
             }
@@ -270,61 +347,33 @@
         return false;
     }
 
-    /**
-     * Snoozes all current Heads Up Notifications.
-     */
     public void snooze() {
         for (String key : mHeadsUpEntries.keySet()) {
             HeadsUpEntry entry = mHeadsUpEntries.get(key);
             String packageName = entry.entry.notification.getPackageName();
             mSnoozedPackages.put(snoozeKey(packageName, mUser),
-                    mClock.currentTimeMillis() + mSnoozeLengthMs);
+                    SystemClock.elapsedRealtime() + mSnoozeLengthMs);
         }
+        mReleaseOnExpandFinish = true;
     }
 
-    private static String snoozeKey(@NonNull String packageName, int user) {
+    private static String snoozeKey(String packageName, int user) {
         return user + "," + packageName;
     }
 
-    protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
+    private HeadsUpEntry getHeadsUpEntry(String key) {
         return mHeadsUpEntries.get(key);
     }
 
-    /**
-     * Returns the entry of given Heads Up Notification.
-     *
-     * @param key Key of heads up notification
-     */
-    public NotificationData.Entry getEntry(@NonNull String key) {
-        HeadsUpEntry entry = mHeadsUpEntries.get(key);
-        return entry != null ? entry.entry : null;
+    public NotificationData.Entry getEntry(String key) {
+        return mHeadsUpEntries.get(key).entry;
     }
 
-    /**
-     * Returns the stream of all current Heads Up Notifications.
-     */
-    @NonNull
-    public Stream<NotificationData.Entry> getAllEntries() {
-        return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry);
+    public Collection<HeadsUpEntry> getAllEntries() {
+        return mHeadsUpEntries.values();
     }
 
-    /**
-     * Returns the top Heads Up Notification, which appeares to show at first.
-     */
-    @Nullable
-    public NotificationData.Entry getTopEntry() {
-        HeadsUpEntry topEntry = getTopHeadsUpEntry();
-        return (topEntry != null) ? topEntry.entry : null;
-    }
-
-    /**
-     * Returns if any heads up notification is available or not.
-     */
-    public boolean hasHeadsUpNotifications() {
-        return !mHeadsUpEntries.isEmpty();
-    }
-
-    protected HeadsUpEntry getTopHeadsUpEntry() {
+    public HeadsUpEntry getTopEntry() {
         if (mHeadsUpEntries.isEmpty()) {
             return null;
         }
@@ -338,21 +387,56 @@
     }
 
     /**
-     * Sets the current user.
+     * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
+     * that a user might have consciously clicked on it.
+     *
+     * @param key the key of the touched notification
+     * @return whether the touch is invalid and should be discarded
      */
+    public boolean shouldSwallowClick(String key) {
+        HeadsUpEntry entry = mHeadsUpEntries.get(key);
+        if (entry != null && mClock.currentTimeMillis() < entry.postTime) {
+            return true;
+        }
+        return false;
+    }
+
+    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+        if (mIsExpanded || mBar.isBouncerShowing()) {
+            // The touchable region is always the full area when expanded
+            return;
+        }
+        if (mHasPinnedNotification) {
+            ExpandableNotificationRow topEntry = getTopEntry().entry.row;
+            if (topEntry.isChildInGroup()) {
+                final ExpandableNotificationRow groupSummary
+                        = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
+                if (groupSummary != null) {
+                    topEntry = groupSummary;
+                }
+            }
+            topEntry.getLocationOnScreen(mTmpTwoArray);
+            int minX = mTmpTwoArray[0];
+            int maxX = mTmpTwoArray[0] + topEntry.getWidth();
+            int maxY = topEntry.getIntrinsicHeight();
+
+            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+            info.touchableRegion.set(minX, 0, maxX, maxY);
+        } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
+            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+            info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
+        }
+    }
+
     public void setUser(int user) {
         mUser = user;
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("HeadsUpManager state:");
-        dumpInternal(fd, pw, args);
-    }
-
-    protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.print("  mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
         pw.print("  mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
-        pw.print("  now="); pw.println(mClock.currentTimeMillis());
+        pw.print("  now="); pw.println(SystemClock.elapsedRealtime());
         pw.print("  mUser="); pw.println(mUser);
         for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
             pw.print("  HeadsUpEntry="); pw.println(entry.entry);
@@ -365,9 +449,6 @@
         }
     }
 
-    /**
-     * Returns if there are any pinned Heads Up Notifications or not.
-     */
     public boolean hasPinnedHeadsUp() {
         return mHasPinnedNotification;
     }
@@ -383,8 +464,14 @@
     }
 
     /**
-     * Unpins all pinned Heads Up Notifications.
+     * Notifies that a notification was swiped out and will be removed.
+     *
+     * @param key the notification key
      */
+    public void addSwipedOutNotification(String key) {
+        mSwipedOutKeys.add(key);
+    }
+
     public void unpinAll() {
         for (String key : mHeadsUpEntries.keySet()) {
             HeadsUpEntry entry = mHeadsUpEntries.get(key);
@@ -394,13 +481,60 @@
         }
     }
 
-    /**
-     * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
-     * well.
-     */
+    public void onExpandingFinished() {
+        if (mReleaseOnExpandFinish) {
+            releaseAllImmediately();
+            mReleaseOnExpandFinish = false;
+        } else {
+            for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
+                if (isHeadsUp(entry.key)) {
+                    // Maybe the heads-up was removed already
+                    removeHeadsUpEntry(entry);
+                }
+            }
+        }
+        mEntriesToRemoveAfterExpand.clear();
+    }
+
+    public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+        mTrackingHeadsUp = trackingHeadsUp;
+    }
+
     public boolean isTrackingHeadsUp() {
-        // Might be implemented in subclass.
-        return false;
+        return mTrackingHeadsUp;
+    }
+
+    public void setIsExpanded(boolean isExpanded) {
+        if (isExpanded != mIsExpanded) {
+            mIsExpanded = isExpanded;
+            if (isExpanded) {
+                // make sure our state is sane
+                mWaitingOnCollapseWhenGoingAway = false;
+                mHeadsUpGoingAway = false;
+                updateTouchableRegionListener();
+            }
+        }
+    }
+
+    /**
+     * @return the height of the top heads up notification when pinned. This is different from the
+     *         intrinsic height, which also includes whether the notification is system expanded and
+     *         is mainly used when dragging down from a heads up notification.
+     */
+    public int getTopHeadsUpPinnedHeight() {
+        HeadsUpEntry topEntry = getTopEntry();
+        if (topEntry == null || topEntry.entry == null) {
+            return 0;
+        }
+        ExpandableNotificationRow row = topEntry.entry.row;
+        if (row.isChildInGroup()) {
+            final ExpandableNotificationRow groupSummary
+                    = mGroupManager.getGroupSummary(row.getStatusBarNotification());
+            if (groupSummary != null) {
+                row = groupSummary;
+            }
+        }
+        return row.getPinnedHeadsUpHeight();
     }
 
     /**
@@ -419,67 +553,147 @@
     }
 
     /**
-     * Sets an entry to be expanded and therefore stick in the heads up area if it's pinned
-     * until it's collapsed again.
+     * Set that we are exiting the headsUp pinned mode, but some notifications might still be
+     * animating out. This is used to keep the touchable regions in a sane state.
      */
+    public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
+        if (headsUpGoingAway != mHeadsUpGoingAway) {
+            mHeadsUpGoingAway = headsUpGoingAway;
+            if (!headsUpGoingAway) {
+                waitForStatusBarLayout();
+            }
+            updateTouchableRegionListener();
+        }
+    }
+
+    /**
+     * We need to wait on the whole panel to collapse, before we can remove the touchable region
+     * listener.
+     */
+    private void waitForStatusBarLayout() {
+        mWaitingOnCollapseWhenGoingAway = true;
+        mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                    int oldLeft,
+                    int oldTop, int oldRight, int oldBottom) {
+                if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
+                    mStatusBarWindowView.removeOnLayoutChangeListener(this);
+                    mWaitingOnCollapseWhenGoingAway = false;
+                    updateTouchableRegionListener();
+                }
+            }
+        });
+    }
+
+    public static void setIsClickedNotification(View child, boolean clicked) {
+        child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
+    }
+
+    public static boolean isClickedHeadsUpNotification(View child) {
+        Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION);
+        return clicked != null && clicked;
+    }
+
+    public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
+        if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
+            headsUpEntry.remoteInputActive = remoteInputActive;
+            if (remoteInputActive) {
+                headsUpEntry.removeAutoRemovalCallbacks();
+            } else {
+                headsUpEntry.updateEntry(false /* updatePostTime */);
+            }
+        }
+    }
 
     /**
      * Set an entry to be expanded and therefore stick in the heads up area if it's pinned
      * until it's collapsed again.
      */
-    public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) {
-        HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
-        if (headsUpEntry != null && entry.row.isPinned()) {
-            headsUpEntry.expanded(expanded);
+    public void setExpanded(NotificationData.Entry entry, boolean expanded) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
+        if (headsUpEntry != null && headsUpEntry.expanded != expanded && entry.row.isPinned()) {
+            headsUpEntry.expanded = expanded;
+            if (expanded) {
+                headsUpEntry.removeAutoRemovalCallbacks();
+            } else {
+                headsUpEntry.updateEntry(false /* updatePostTime */);
+            }
         }
     }
 
-    @NonNull
-    protected HeadsUpEntry createHeadsUpEntry() {
-        return new HeadsUpEntry();
+    @Override
+    public void onReorderingAllowed() {
+        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
+        for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
+            if (isHeadsUp(entry.key)) {
+                // Maybe the heads-up was removed already
+                removeHeadsUpEntry(entry);
+            }
+        }
+        mEntriesToRemoveWhenReorderingAllowed.clear();
+        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
     }
 
-    protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) {
-        // Do nothing for HeadsUpEntry.
+    public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) {
+        mVisualStabilityManager = visualStabilityManager;
+    }
+
+    public void setStatusBarState(int statusBarState) {
+        mStatusBarState = statusBarState;
     }
 
     /**
      * This represents a notification and how long it is in a heads up mode. It also manages its
      * lifecycle automatically when created.
      */
-    protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
-        @Nullable public NotificationData.Entry entry;
+    public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+        public NotificationData.Entry entry;
         public long postTime;
-        public boolean remoteInputActive;
         public long earliestRemovaltime;
+        private Runnable mRemoveHeadsUpRunnable;
+        public boolean remoteInputActive;
         public boolean expanded;
 
-        private Runnable mRemoveHeadsUpRunnable;
-
-        public void setEntry(@Nullable final NotificationData.Entry entry) {
-            setEntry(entry, null);
-        }
-
-        public void setEntry(@Nullable final NotificationData.Entry entry,
-                @Nullable Runnable removeHeadsUpRunnable) {
+        public void setEntry(final NotificationData.Entry entry) {
             this.entry = entry;
-            this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable;
 
             // The actual post time will be just after the heads-up really slided in
             postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay;
-            updateEntry(true /* updatePostTime */);
+            mRemoveHeadsUpRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    if (!mVisualStabilityManager.isReorderingAllowed()) {
+                        mEntriesToRemoveWhenReorderingAllowed.add(entry);
+                        mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this);
+                    } else if (!mTrackingHeadsUp) {
+                        removeHeadsUpEntry(entry);
+                    } else {
+                        mEntriesToRemoveAfterExpand.add(entry);
+                    }
+                }
+            };
+            updateEntry();
+        }
+
+        public void updateEntry() {
+            updateEntry(true);
         }
 
         public void updateEntry(boolean updatePostTime) {
-            if (DEBUG) Log.v(TAG, "updateEntry");
-
             long currentTime = mClock.currentTimeMillis();
             earliestRemovaltime = currentTime + mMinimumDisplayTime;
             if (updatePostTime) {
                 postTime = Math.max(postTime, currentTime);
             }
             removeAutoRemovalCallbacks();
-
+            if (mEntriesToRemoveAfterExpand.contains(entry)) {
+                mEntriesToRemoveAfterExpand.remove(entry);
+            }
+            if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
+                mEntriesToRemoveWhenReorderingAllowed.remove(entry);
+            }
             if (!isSticky()) {
                 long finishTime = postTime + mHeadsUpNotificationDecay;
                 long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
@@ -493,7 +707,7 @@
         }
 
         @Override
-        public int compareTo(@NonNull HeadsUpEntry o) {
+        public int compareTo(HeadsUpEntry o) {
             boolean isPinned = entry.row.isPinned();
             boolean otherPinned = o.entry.row.isPinned();
             if (isPinned && !otherPinned) {
@@ -520,29 +734,26 @@
                             : -1;
         }
 
-        public void expanded(boolean expanded) {
-            this.expanded = expanded;
-        }
-
-        public void reset() {
-            entry = null;
-            expanded = false;
-            remoteInputActive = false;
-            removeAutoRemovalCallbacks();
-            mRemoveHeadsUpRunnable = null;
-        }
-
         public void removeAutoRemovalCallbacks() {
-            if (mRemoveHeadsUpRunnable != null)
-                mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
+            mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
+        }
+
+        public boolean wasShownLongEnough() {
+            return earliestRemovaltime < mClock.currentTimeMillis();
         }
 
         public void removeAsSoonAsPossible() {
-            if (mRemoveHeadsUpRunnable != null) {
-                removeAutoRemovalCallbacks();
-                mHandler.postDelayed(mRemoveHeadsUpRunnable,
-                        earliestRemovaltime - mClock.currentTimeMillis());
-            }
+            removeAutoRemovalCallbacks();
+            mHandler.postDelayed(mRemoveHeadsUpRunnable,
+                    earliestRemovaltime - mClock.currentTimeMillis());
+        }
+
+        public void reset() {
+            removeAutoRemovalCallbacks();
+            entry = null;
+            mRemoveHeadsUpRunnable = null;
+            expanded = false;
+            remoteInputActive = false;
         }
     }
 
@@ -551,4 +762,5 @@
             return SystemClock.elapsedRealtime();
         }
     }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
deleted file mode 100644
index 1e3c123c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.view.View;
-
-import com.android.systemui.R;
-
-/**
- * A class of utility static methods for heads up notifications.
- */
-public final class HeadsUpUtil {
-    private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
-
-    /**
-     * Set the given view as clicked or not-clicked.
-     * @param view The view to be set the flag to.
-     * @param clicked True to set as clicked. False to not-clicked.
-     */
-    public static void setIsClickedHeadsUpNotification(View view, boolean clicked) {
-        view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
-    }
-
-    /**
-     * Check if the given view has the flag of "clicked notification"
-     * @param view The view to be checked.
-     * @return True if the view has clicked. False othrewise.
-     */
-    public static boolean isClickedHeadsUpNotification(View view) {
-        Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION);
-        return clicked != null && clicked;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index d7a810e..424858a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -64,7 +64,7 @@
     private boolean mPanelTracking;
     private boolean mExpansionChanging;
     private boolean mPanelFullWidth;
-    private boolean mPulsing;
+    private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
     private boolean mUnlockHintRunning;
     private boolean mQsCustomizerShowing;
     private int mIntrinsicPadding;
@@ -315,18 +315,23 @@
     }
 
     public boolean hasPulsingNotifications() {
-        return mPulsing;
+        return mPulsing != null;
     }
 
-    public void setPulsing(boolean hasPulsing) {
+    public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> hasPulsing) {
         mPulsing = hasPulsing;
     }
 
     public boolean isPulsing(NotificationData.Entry entry) {
-        if (!mPulsing || mHeadsUpManager == null) {
+        if (mPulsing == null) {
             return false;
         }
-        return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry));
+        for (HeadsUpManager.HeadsUpEntry e : mPulsing) {
+            if (e.entry == entry) {
+                return true;
+            }
+        }
+        return false;
     }
 
     public boolean isPanelTracking() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 1b55a5b..c114a6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -92,11 +92,10 @@
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.policy.HeadsUpUtil;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 
 import android.support.v4.graphics.ColorUtils;
@@ -289,7 +288,7 @@
     private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
     private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
             = new HashSet<>();
-    private HeadsUpManagerPhone mHeadsUpManager;
+    private HeadsUpManager mHeadsUpManager;
     private boolean mTrackingHeadsUp;
     private ScrimController mScrimController;
     private boolean mForceNoOverlappingRendering;
@@ -359,7 +358,7 @@
         }
     };
     private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
-    private boolean mPulsing;
+    private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
     private boolean mDrawBackgroundAsSrc;
     private boolean mFadingOut;
     private boolean mParentNotFullyVisible;
@@ -691,7 +690,7 @@
     }
 
     private void updateAlgorithmHeightAndPadding() {
-        if (mPulsing) {
+        if (mPulsing != null) {
             mTopPadding = mClockBottom;
         } else {
             mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding;
@@ -921,27 +920,6 @@
     }
 
     /**
-     * @return the height of the top heads up notification when pinned. This is different from the
-     *         intrinsic height, which also includes whether the notification is system expanded and
-     *         is mainly used when dragging down from a heads up notification.
-     */
-    private int getTopHeadsUpPinnedHeight() {
-        NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
-        if (topEntry == null) {
-            return 0;
-        }
-        ExpandableNotificationRow row = topEntry.row;
-        if (row.isChildInGroup()) {
-            final ExpandableNotificationRow groupSummary
-                    = mGroupManager.getGroupSummary(row.getStatusBarNotification());
-            if (groupSummary != null) {
-                row = groupSummary;
-            }
-        }
-        return row.getPinnedHeadsUpHeight();
-    }
-
-    /**
      * @return the position from where the appear transition ends when expanding.
      *         Measured in absolute height.
      */
@@ -952,7 +930,7 @@
             int minNotificationsForShelf = 1;
             if (mTrackingHeadsUp
                     || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) {
-                appearPosition = getTopHeadsUpPinnedHeight();
+                appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight();
                 minNotificationsForShelf = 2;
             } else {
                 appearPosition = 0;
@@ -1220,9 +1198,9 @@
                 if (slidingChild instanceof ExpandableNotificationRow) {
                     ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
                     if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
-                            && mHeadsUpManager.getTopEntry().row != row
+                            && mHeadsUpManager.getTopEntry().entry.row != row
                             && mGroupManager.getGroupSummary(
-                                mHeadsUpManager.getTopEntry().row.getStatusBarNotification())
+                                mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
                                 != row) {
                         continue;
                     }
@@ -2142,7 +2120,7 @@
 
     @Override
     public boolean hasPulsingNotifications() {
-        return mPulsing;
+        return mPulsing != null;
     }
 
     private void updateScrollability() {
@@ -2775,7 +2753,7 @@
     }
 
     private boolean isClickedHeadsUp(View child) {
-        return HeadsUpUtil.isClickedHeadsUpNotification(child);
+        return HeadsUpManager.isClickedHeadsUpNotification(child);
     }
 
     /**
@@ -4280,7 +4258,7 @@
         mAnimationFinishedRunnables.add(runnable);
     }
 
-    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
+    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
         mHeadsUpManager = headsUpManager;
         mAmbientState.setHeadsUpManager(headsUpManager);
     }
@@ -4348,8 +4326,8 @@
         return mIsExpanded;
     }
 
-    public void setPulsing(boolean pulsing, int clockBottom) {
-        if (!mPulsing && !pulsing) {
+    public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing, int clockBottom) {
+        if (mPulsing == null && pulsing == null) {
             return;
         }
         mPulsing = pulsing;
@@ -4488,7 +4466,7 @@
         pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
                         + " alpha:%f scrollY:%d]",
                 this.getClass().getSimpleName(),
-                mPulsing ? "T":"f",
+                mPulsing != null ?"T":"f",
                 mAmbientState.isQsCustomizerShowing() ? "T":"f",
                 getVisibility() == View.VISIBLE ? "visible"
                         : getVisibility() == View.GONE ? "gone"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 04a7bd7..682b849 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -30,7 +30,7 @@
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.policy.HeadsUpUtil;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 /**
  * A state of a view. This can be used to apply a set of view properties to a view with
@@ -582,7 +582,7 @@
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                HeadsUpUtil.setIsClickedHeadsUpNotification(child, false);
+                HeadsUpManager.setIsClickedNotification(child, false);
                 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
                 child.setTag(TAG_START_TRANSLATION_Y, null);
                 child.setTag(TAG_END_TRANSLATION_Y, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index f3c1171..6e7477f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -32,7 +32,6 @@
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.NotificationInflaterTest;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -52,7 +51,7 @@
     public NotificationTestHelper(Context context) {
         mContext = context;
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null);
+        mHeadsUpManager = new HeadsUpManager(mContext, null, mGroupManager);
     }
 
     public ExpandableNotificationRow createRow() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
deleted file mode 100644
index 28f9417..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.view.View;
-import android.service.notification.StatusBarNotification;
-import android.support.test.filters.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.assertFalse;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class HeadsUpManagerPhoneTest extends SysuiTestCase {
-    @Rule public MockitoRule rule = MockitoJUnit.rule();
-
-    private static final String TEST_PACKAGE_NAME = "test";
-    private static final int TEST_UID = 0;
-
-    private HeadsUpManagerPhone mHeadsUpManager;
-
-    private NotificationData.Entry mEntry;
-    private StatusBarNotification mSbn;
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-
-    @Mock private NotificationGroupManager mGroupManager;
-    @Mock private View mStatusBarWindowView;
-    @Mock private StatusBar mBar;
-    @Mock private ExpandableNotificationRow mRow;
-    @Mock private VisualStabilityManager mVSManager;
-
-    @Before
-    public void setUp() {
-        when(mVSManager.isReorderingAllowed()).thenReturn(true);
-
-        mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarWindowView, mGroupManager, mBar, mVSManager);
-
-        Notification.Builder n = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
-             0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0);
-
-        mEntry = new NotificationData.Entry(mSbn);
-        mEntry.row = mRow;
-        mEntry.expandedIcon = mock(StatusBarIconView.class);
-    }
-
-    @Test
-    public void testBasicOperations() {
-        // Check the initial state.
-        assertNull(mHeadsUpManager.getEntry(mEntry.key));
-        assertNull(mHeadsUpManager.getTopEntry());
-        assertEquals(0, mHeadsUpManager.getAllEntries().count());
-        assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
-
-        // Add a notification.
-        mHeadsUpManager.showNotification(mEntry);
-
-        assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
-        assertEquals(mEntry, mHeadsUpManager.getTopEntry());
-        assertEquals(1, mHeadsUpManager.getAllEntries().count());
-        assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
-
-        // Update the notification.
-        mHeadsUpManager.updateNotification(mEntry, false);
-
-        assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
-        assertEquals(mEntry, mHeadsUpManager.getTopEntry());
-        assertEquals(1, mHeadsUpManager.getAllEntries().count());
-        assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
-
-        // Remove but defer, since the notification is visible on display.
-        mHeadsUpManager.removeNotification(mEntry.key, false);
-
-        assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
-        assertEquals(mEntry, mHeadsUpManager.getTopEntry());
-        assertEquals(1, mHeadsUpManager.getAllEntries().count());
-        assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 31442af..bdf9b1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -86,8 +86,8 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -110,7 +110,7 @@
     @Mock private UnlockMethodCache mUnlockMethodCache;
     @Mock private KeyguardIndicationController mKeyguardIndicationController;
     @Mock private NotificationStackScrollLayout mStackScroller;
-    @Mock private HeadsUpManagerPhone mHeadsUpManager;
+    @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private SystemServicesProxy mSystemServicesProxy;
     @Mock private NotificationPanelView mNotificationPanelView;
     @Mock private IStatusBarService mBarService;
@@ -588,7 +588,7 @@
     static class TestableStatusBar extends StatusBar {
         public TestableStatusBar(StatusBarKeyguardViewManager man,
                 UnlockMethodCache unlock, KeyguardIndicationController key,
-                NotificationStackScrollLayout stack, HeadsUpManagerPhone hum,
+                NotificationStackScrollLayout stack, HeadsUpManager hum,
                 PowerManager pm, NotificationPanelView panelView,
                 IStatusBarService barService, NotificationListener notificationListener,
                 NotificationLogger notificationLogger,
@@ -650,7 +650,7 @@
         public void setUpForTest(NotificationPresenter presenter,
                 NotificationListContainer listContainer,
                 Callback callback,
-                HeadsUpManagerPhone headsUpManager,
+                HeadsUpManager headsUpManager,
                 NotificationData notificationData) {
             super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager);
             mNotificationData = notificationData;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
index c18ed73..f7bb065 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
@@ -44,11 +44,13 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@Ignore
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -87,7 +89,7 @@
     public void tearDown() throws Exception {
         TestableLooper.get(this).processAllMessages();
     }
-
+/*
     @Test
     public void testClickMediaRouterItemConnectsMedia() {
         mDialog.show();
@@ -137,7 +139,7 @@
                 .getText().toString().contains("Phone"));
         mDialog.dismiss();
     }
-
+*/
     @Test
     public void testNoMediaScanIfInCall() {
         mDialog.setIsInCall(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 4888fb2..43d60e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -55,6 +56,7 @@
 
 import java.util.function.Predicate;
 
+@Ignore
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -111,7 +113,7 @@
                     + " failed test", condition.test(view));
         }
     }
-
+/*
     @Test
     public void testContentDescriptions() {
         mDialog.show(SHOW_REASON_UNKNOWN);
@@ -218,4 +220,5 @@
         verify(mController, times(1)).setRingerMode(RINGER_MODE_NORMAL, false);
         verify(mController, times(1)).setStreamVolume(STREAM_RING, 0);
     }
+    */
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java
deleted file mode 100644
index 4ab2063..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.net.Uri;
-import android.provider.Settings;
-import android.service.notification.Condition;
-import android.service.notification.ZenModeConfig;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.test.FlakyTest;
-import android.view.LayoutInflater;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.policy.ZenModeController;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Ignore
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ZenModePanelTest extends SysuiTestCase {
-
-    ZenModePanel mPanel;
-    ZenModeController mController;
-    Uri mForeverId;
-
-    @Before
-    public void setup() throws Exception {
-        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
-        mPanel = (ZenModePanel) layoutInflater.inflate(com.android.systemui.R.layout.zen_mode_panel,
-                null);
-        mController = mock(ZenModeController.class);
-        mForeverId = Condition.newId(mContext).appendPath("forever").build();
-
-        mPanel.init(mController);
-    }
-
-    private void assertProperConditionTagTypes(boolean hasAlarm) {
-        final int N = mPanel.getVisibleConditions();
-        assertEquals(hasAlarm ? 3 : 2, N);
-
-        assertEquals(mForeverId, mPanel.getConditionTagAt(0).condition.id);
-        assertTrue(ZenModeConfig.isValidCountdownConditionId(
-                mPanel.getConditionTagAt(1).condition.id));
-        assertFalse(ZenModeConfig.isValidCountdownToAlarmConditionId(
-                mPanel.getConditionTagAt(1).condition.id));
-        if (hasAlarm) {
-            assertTrue(ZenModeConfig.isValidCountdownToAlarmConditionId(
-                    mPanel.getConditionTagAt(2).condition.id));
-        }
-    }
-
-    @Test
-    public void testHandleUpdateConditions_foreverSelected_alarmExists() {
-         Condition forever = new Condition(mForeverId, "", Condition.STATE_TRUE);
-
-        when(mController.getNextAlarm()).thenReturn(System.currentTimeMillis() + 1000);
-
-        mPanel.handleUpdateConditions(forever);
-        assertProperConditionTagTypes(true);
-        assertTrue(mPanel.getConditionTagAt(0).rb.isChecked());
-    }
-
-    @Test
-    public void testHandleUpdateConditions_foreverSelected_noAlarm() {
-        Uri foreverId = Condition.newId(mContext).appendPath("forever").build();
-        Condition forever = new Condition(foreverId, "", Condition.STATE_TRUE);
-
-        when(mController.getNextAlarm()).thenReturn((long) 0);
-
-        mPanel.handleUpdateConditions(forever);
-        assertProperConditionTagTypes(false);
-        assertEquals(foreverId, mPanel.getConditionTagAt(0).condition.id);
-    }
-
-    @Test
-    public void testHandleUpdateConditions_countdownSelected_alarmExists() {
-        Uri foreverId = Condition.newId(mContext).appendPath("forever").build();
-
-        Condition countdown = new Condition(ZenModeConfig.toCountdownConditionId(
-                System.currentTimeMillis() + (3 * 60 * 60 * 1000) + 4000, false),
-                "", Condition.STATE_TRUE);
-
-        when(mController.getNextAlarm()).thenReturn(System.currentTimeMillis() + 1000);
-
-        mPanel.handleUpdateConditions(countdown);
-        assertProperConditionTagTypes(true);
-        assertTrue(mPanel.getConditionTagAt(1).rb.isChecked());
-    }
-
-    @Test
-    public void testHandleUpdateConditions_countdownSelected_noAlarm() {
-        Uri foreverId = Condition.newId(mContext).appendPath("forever").build();
-
-        Condition countdown = new Condition(ZenModeConfig.toCountdownConditionId(
-                System.currentTimeMillis() + (3 * 60 * 60 * 1000) + 4000, false),
-                "", Condition.STATE_TRUE);
-
-        when(mController.getNextAlarm()).thenReturn((long) 0);
-
-        mPanel.handleUpdateConditions(countdown);
-        assertProperConditionTagTypes(false);
-        assertTrue(mPanel.getConditionTagAt(1).rb.isChecked());
-    }
-
-    @Test
-    public void testHandleUpdateConditions_nextAlarmSelected() {
-        Uri foreverId = Condition.newId(mContext).appendPath("forever").build();
-
-        Condition alarm = new Condition(ZenModeConfig.toCountdownConditionId(
-                System.currentTimeMillis() + 1000, true),
-                "", Condition.STATE_TRUE);
-        when(mController.getNextAlarm()).thenReturn(System.currentTimeMillis() + 9000);
-
-        mPanel.handleUpdateConditions(alarm);
-
-        assertProperConditionTagTypes(true);
-        assertEquals(alarm, mPanel.getConditionTagAt(2).condition);
-        assertTrue(mPanel.getConditionTagAt(2).rb.isChecked());
-    }
-
-    @Test
-    public void testHandleUpdateConditions_foreverSelected_alarmConditionDoesNotChangeIfAttached() {
-        Uri foreverId = Condition.newId(mContext).appendPath("forever").build();
-        Condition forever = new Condition(foreverId, "", Condition.STATE_TRUE);
-
-        Condition alarm = new Condition(ZenModeConfig.toCountdownConditionId(
-                System.currentTimeMillis() + 9000, true),
-                "", Condition.STATE_TRUE);
-        when(mController.getNextAlarm()).thenReturn(System.currentTimeMillis() + 1000);
-
-        mPanel.handleUpdateConditions(alarm);
-        mPanel.setAttached(true);
-        mPanel.handleUpdateConditions(forever);
-
-        assertProperConditionTagTypes(true);
-        assertEquals(alarm, mPanel.getConditionTagAt(2).condition);
-        assertTrue(mPanel.getConditionTagAt(0).rb.isChecked());
-    }
-
-    @Test
-    public void testHandleUpdateConditions_foreverSelected_timeConditionDoesNotChangeIfAttached() {
-        Uri foreverId = Condition.newId(mContext).appendPath("forever").build();
-        Condition forever = new Condition(foreverId, "", Condition.STATE_TRUE);
-
-        Condition countdown = new Condition(ZenModeConfig.toCountdownConditionId(
-                System.currentTimeMillis() + (3 * 60 * 60 * 1000) + 4000, false),
-                "", Condition.STATE_TRUE);
-        when(mController.getNextAlarm()).thenReturn((long) 0);
-
-        mPanel.handleUpdateConditions(countdown);
-        mPanel.setAttached(true);
-        mPanel.handleUpdateConditions(forever);
-
-        assertProperConditionTagTypes(false);
-        assertEquals(countdown, mPanel.getConditionTagAt(1).condition);
-        assertTrue(mPanel.getConditionTagAt(0).rb.isChecked());
-    }
-
-    @Test
-    @UiThreadTest
-    public void testHandleUpdateManualRule_stillSelectedAfterModeChange() {
-        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
-
-        Condition alarm = new Condition(ZenModeConfig.toCountdownConditionId(
-                System.currentTimeMillis() + 1000, true),
-                "", Condition.STATE_TRUE);
-
-        rule.condition = alarm;
-        rule.conditionId = alarm.id;
-        rule.enabled = true;
-        rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-
-        mPanel.handleUpdateManualRule(rule);
-
-        assertProperConditionTagTypes(true);
-        assertEquals(alarm, mPanel.getConditionTagAt(2).condition);
-        assertTrue(mPanel.getConditionTagAt(2).rb.isChecked());
-
-        assertEquals(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
-                mPanel.getSelectedZen(Settings.Global.ZEN_MODE_OFF));
-
-        rule.zenMode = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
-
-        mPanel.handleUpdateManualRule(rule);
-
-        assertProperConditionTagTypes(true);
-        assertEquals(alarm, mPanel.getConditionTagAt(2).condition);
-        assertTrue(mPanel.getConditionTagAt(2).rb.isChecked());
-
-        assertEquals(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS,
-                mPanel.getSelectedZen(Settings.Global.ZEN_MODE_OFF));
-    }
-}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 6816fd1..b27f1ec 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -479,6 +479,7 @@
         mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
         mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
         mTemporaryScreenBrightness = -1;
+        mPendingScreenBrightnessSetting = -1;
         mTemporaryAutoBrightnessAdjustment = Float.NaN;
     }
 
@@ -1476,6 +1477,7 @@
             mPendingScreenBrightnessSetting = -1;
             return false;
         }
+        mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting;
         mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
         mPendingScreenBrightnessSetting = -1;
         return true;
@@ -1484,7 +1486,8 @@
     private void notifyBrightnessChanged(int brightness, boolean userInitiated,
             boolean hadUserDataPoint) {
         final float brightnessInNits = convertToNits(brightness);
-        if (brightnessInNits >= 0.0f && mAutomaticBrightnessController != null) {
+        if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+                && mAutomaticBrightnessController != null) {
             // We only want to track changes on devices that can actually map the display backlight
             // values into a physical brightness unit since the value provided by the API is in
             // nits and not using the arbitrary backlight units.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index d96671c..79fd496 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -28,7 +28,7 @@
  * Helper for creating the recoverable key database.
  */
 class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
-    private static final int DATABASE_VERSION = 1;
+    private static final int DATABASE_VERSION = 2;
     private static final String DATABASE_NAME = "recoverablekeystore.db";
 
     private static final String SQL_CREATE_KEYS_ENTRY =
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 7674b5e..bd3f302d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -662,7 +662,7 @@
             mWallpaperController.updateWallpaperVisibility();
         }
 
-        w.handleWindowMovedIfNeeded(mPendingTransaction);
+        w.handleWindowMovedIfNeeded();
 
         final WindowStateAnimator winAnimator = w.mWinAnimator;
 
@@ -3599,8 +3599,6 @@
     }
 
     private final class AboveAppWindowContainers extends NonAppWindowContainers {
-        private final Dimmer mDimmer = new Dimmer(this);
-        private final Rect mTmpDimBoundsRect = new Rect();
         AboveAppWindowContainers(String name, WindowManagerService service) {
             super(name, service);
         }
@@ -3632,22 +3630,6 @@
                 imeContainer.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE);
             }
         }
-
-        @Override
-        Dimmer getDimmer() {
-            return mDimmer;
-        }
-
-        @Override
-        void prepareSurfaces() {
-            mDimmer.resetDimStates();
-            super.prepareSurfaces();
-            getBounds(mTmpDimBoundsRect);
-
-            if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
-                scheduleAnimation();
-            }
-        }
     }
 
     /**
@@ -3679,6 +3661,9 @@
         };
 
         private final String mName;
+        private final Dimmer mDimmer = new Dimmer(this);
+        private final Rect mTmpDimBoundsRect = new Rect();
+
         NonAppWindowContainers(String name, WindowManagerService service) {
             super(service);
             mName = name;
@@ -3722,6 +3707,22 @@
         String getName() {
             return mName;
         }
+
+        @Override
+        Dimmer getDimmer() {
+            return mDimmer;
+        }
+
+        @Override
+        void prepareSurfaces() {
+            mDimmer.resetDimStates();
+            super.prepareSurfaces();
+            getBounds(mTmpDimBoundsRect);
+
+            if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+                scheduleAnimation();
+            }
+        }
     }
 
     private class NonMagnifiableWindowContainers extends NonAppWindowContainers {
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 8269a3b..5bc739e 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -92,14 +92,21 @@
             onAnimationFinished();
             return;
         }
-        mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS);
-        try {
-            mRemoteAnimationAdapter.getRunner().onAnimationStart(createAnimations(),
-                    mFinishedCallback);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to start remote animation", e);
-            onAnimationFinished();
-        }
+
+        // Scale the timeout with the animator scale the controlling app is using.
+        mHandler.postDelayed(mTimeoutRunnable,
+                (long) (TIMEOUT_MS * mService.getCurrentAnimatorScale()));
+
+        final RemoteAnimationTarget[] animations = createAnimations();
+        mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
+            try {
+                mRemoteAnimationAdapter.getRunner().onAnimationStart(animations,
+                        mFinishedCallback);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to start remote animation", e);
+                onAnimationFinished();
+            }
+        });
     }
 
     private RemoteAnimationTarget[] createAnimations() {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 68c8995..4b7cea7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -599,6 +599,8 @@
                     "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
         }
 
+        mService.mAnimator.executeAfterPrepareSurfacesRunnables();
+
         final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
 
         // If we are ready to perform an app transition, check through all of the app tokens to be
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index cec13ab..b0d42f2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -92,6 +92,7 @@
      * executed and the corresponding transaction is closed and applied.
      */
     private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>();
+    private boolean mInExecuteAfterPrepareSurfacesRunnables;
 
     WindowAnimator(final WindowManagerService service) {
         mService = service;
@@ -438,7 +439,13 @@
         scheduleAnimation();
     }
 
-    private void executeAfterPrepareSurfacesRunnables() {
+    void executeAfterPrepareSurfacesRunnables() {
+
+        // Don't even think about to start recursing!
+        if (mInExecuteAfterPrepareSurfacesRunnables) {
+            return;
+        }
+        mInExecuteAfterPrepareSurfacesRunnables = true;
 
         // Traverse in order they were added.
         final int size = mAfterPrepareSurfacesRunnables.size();
@@ -446,5 +453,6 @@
             mAfterPrepareSurfacesRunnables.get(i).run();
         }
         mAfterPrepareSurfacesRunnables.clear();
+        mInExecuteAfterPrepareSurfacesRunnables = false;
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 36e3612..a9f2e03 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1758,7 +1758,7 @@
      * listeners and optionally animate it. Simply checking a change of position is not enough,
      * because being move due to dock divider is not a trigger for animation.
      */
-    void handleWindowMovedIfNeeded(Transaction t) {
+    void handleWindowMovedIfNeeded() {
         if (!hasMoved()) {
             return;
         }
@@ -1776,7 +1776,7 @@
                 && !isDragResizing() && !adjustedForMinimizedDockOrIme
                 && getWindowConfiguration().hasMovementAnimations()
                 && !mWinAnimator.mLastHidden) {
-            startMoveAnimation(t, left, top);
+            startMoveAnimation(left, top);
         }
 
         //TODO (multidisplay): Accessibility supported only for the default display.
@@ -4360,7 +4360,7 @@
         commitPendingTransaction();
     }
 
-    private void startMoveAnimation(Transaction t, int left, int top) {
+    private void startMoveAnimation(int left, int top) {
         if (DEBUG_ANIM) Slog.v(TAG, "Setting move animation on " + this);
         final Point oldPosition = new Point();
         final Point newPosition = new Point();
@@ -4369,7 +4369,7 @@
         final AnimationAdapter adapter = new LocalAnimationAdapter(
                 new MoveAnimationSpec(oldPosition.x, oldPosition.y, newPosition.x, newPosition.y),
                 mService.mSurfaceAnimationRunner);
-        startAnimation(t, adapter);
+        startAnimation(getPendingTransaction(), adapter);
     }
 
     private void startAnimation(Transaction t, AnimationAdapter adapter) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index f860195..26a7313 100644
--- a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -19,12 +19,12 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.platform.test.annotations.Postsubmit;
 import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -137,6 +137,32 @@
     }
 
     @Test
+    public void testTimeout_scaled() throws Exception {
+        sWm.setAnimationScale(2, 5.0f);
+        try{
+            final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+            final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
+                    new Point(50, 100), new Rect(50, 100, 150, 150));
+            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            mController.goodToGo();
+
+            mClock.fastForward(2500);
+            mHandler.timeAdvance();
+
+            verify(mMockRunner, never()).onAnimationCancelled();
+
+            mClock.fastForward(10000);
+            mHandler.timeAdvance();
+
+            verify(mMockRunner).onAnimationCancelled();
+            verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+        } finally {
+            sWm.setAnimationScale(2, 1.0f);
+        }
+
+    }
+
+    @Test
     public void testZeroAnimations() throws Exception {
         mController.goodToGo();
         verifyZeroInteractions(mMockRunner);