Merge "Merge "Fix sync initialization on secondary users" am: 99f99a52e7 am: ad4c0c4add am: e66f28c6ee"
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 05c68e1..f10b2cf 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -162,45 +162,23 @@
return 0;
}
-bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
- const DimToValMap& currentBucket) {
- if (currentBucketNum > mMostRecentBucketNum + 1) {
- addPastBucket(nullptr, currentBucketNum - 1);
- }
- for (auto itr = currentBucket.begin(); itr != currentBucket.end(); itr++) {
- if (itr->second + getSumOverPastBuckets(itr->first) > mAlert.trigger_if_sum_gt()) {
- return true;
- }
- }
- // In theory, we also need to check the dimsions not in the current bucket. In single-thread
- // mode, usually we could avoid the following loops.
- for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
- if (itr->second > mAlert.trigger_if_sum_gt()) {
- return true;
- }
- }
- return false;
-}
-
bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
const int64_t& currentBucketValue) {
if (currentBucketNum > mMostRecentBucketNum + 1) {
+ // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this.
addPastBucket(key, 0, currentBucketNum - 1);
}
return mAlert.has_trigger_if_sum_gt()
&& getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
}
-void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs) {
- // TODO: This should also take in the const HashableDimensionKey& key, to pass
- // more details to incidentd and to make mRefractoryPeriodEndsSec key-specific.
+void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key) {
// TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
- if (isInRefractoryPeriod(timestampNs)) {
+ if (isInRefractoryPeriod(timestampNs, key)) {
VLOG("Skipping anomaly declaration since within refractory period");
return;
}
- // TODO(guardrail): Consider guarding against too short refractory periods.
- mLastAnomalyTimestampNs = timestampNs;
+ mRefractoryPeriodEndsSec[key] = (timestampNs / NS_PER_SEC) + mAlert.refractory_period_secs();
// TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
// if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); }
@@ -208,7 +186,7 @@
if (!mSubscriptions.empty()) {
if (mAlert.has_id()) {
ALOGI("An anomaly (%llu) has occurred! Informing subscribers.",mAlert.id());
- informSubscribers();
+ informSubscribers(key);
} else {
ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers.");
}
@@ -218,6 +196,7 @@
StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
+ // TODO: This should also take in the const HashableDimensionKey& key?
android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
mConfigKey.GetId(), mAlert.id());
}
@@ -227,24 +206,24 @@
const HashableDimensionKey& key,
const int64_t& currentBucketValue) {
if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
- declareAnomaly(timestampNs);
+ declareAnomaly(timestampNs, key);
}
}
-void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
- const int64_t& currBucketNum,
- const DimToValMap& currentBucket) {
- if (detectAnomaly(currBucketNum, currentBucket)) {
- declareAnomaly(timestampNs);
+bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs,
+ const HashableDimensionKey& key) {
+ const auto& it = mRefractoryPeriodEndsSec.find(key);
+ if (it != mRefractoryPeriodEndsSec.end()) {
+ if ((timestampNs / NS_PER_SEC) <= it->second) {
+ return true;
+ } else {
+ mRefractoryPeriodEndsSec.erase(key);
+ }
}
+ return false;
}
-bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) const {
- return mLastAnomalyTimestampNs >= 0 &&
- timestampNs - mLastAnomalyTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC;
-}
-
-void AnomalyTracker::informSubscribers() {
+void AnomalyTracker::informSubscribers(const HashableDimensionKey& 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 2d5ab86..472c02c 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -52,17 +52,14 @@
const int64_t& bucketNum);
// Returns true if detected anomaly for the existing buckets on one or more dimension keys.
- bool detectAnomaly(const int64_t& currBucketNum, const DimToValMap& currentBucket);
bool detectAnomaly(const int64_t& currBucketNum, const HashableDimensionKey& key,
const int64_t& currentBucketValue);
// Informs incidentd about the detected alert.
- void declareAnomaly(const uint64_t& timestampNs);
+ void declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key);
// Detects the alert and informs the incidentd when applicable.
void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
- const DimToValMap& currentBucket);
- void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
const HashableDimensionKey& key,
const int64_t& currentBucketValue);
@@ -82,9 +79,11 @@
return mAlert.trigger_if_sum_gt();
}
- // Helper function to return the timestamp of the last detected anomaly.
- inline int64_t getLastAnomalyTimestampNs() const {
- return mLastAnomalyTimestampNs;
+ // 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 {
+ const auto& it = mRefractoryPeriodEndsSec.find(key);
+ return it != mRefractoryPeriodEndsSec.end() ? it->second : 0;
}
inline int getNumOfPastBuckets() const {
@@ -121,8 +120,11 @@
// The bucket number of the last added bucket.
int64_t mMostRecentBucketNum = -1;
- // The timestamp when the last anomaly was declared.
- int64_t mLastAnomalyTimestampNs = -1;
+ // Map from each dimension to the timestamp that its refractory period (if this anomaly was
+ // declared for that dimension) ends, in seconds. Only anomalies that occur after this period
+ // ends will be declared.
+ // Entries may be, but are not guaranteed to be, removed after the period is finished.
+ unordered_map<HashableDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
void flushPastBuckets(const int64_t& currBucketNum);
@@ -133,7 +135,7 @@
// and remove any items with value 0.
void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
- bool isInRefractoryPeriod(const uint64_t& timestampNs) const;
+ bool isInRefractoryPeriod(const uint64_t& timestampNs, const HashableDimensionKey& key);
// Calculates the corresponding bucket index within the circular array.
size_t index(int64_t bucketNum) const;
@@ -142,12 +144,12 @@
virtual void resetStorage();
// Informs the subscribers that an anomaly has occurred.
- void informSubscribers();
+ void informSubscribers(const HashableDimensionKey& key);
FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
- FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
+ FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
};
} // namespace statsd
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
index d30810f..7576a38 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
@@ -46,7 +46,7 @@
if (itr->second != nullptr &&
static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) {
- declareAnomaly(timestampNs);
+ declareAnomaly(timestampNs, dimensionKey);
stopAlarm(dimensionKey);
}
}
@@ -55,7 +55,7 @@
const uint64_t& timestampNs) {
uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
- if (isInRefractoryPeriod(timestampNs)) {
+ if (isInRefractoryPeriod(timestampNs, dimensionKey)) {
VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period");
return;
}
@@ -104,7 +104,7 @@
// Now declare each of these alarms to have fired.
for (const auto& kv : matchedAlarms) {
- declareAnomaly(timestampNs /* TODO: , kv.first */);
+ declareAnomaly(timestampNs, kv.first);
mAlarms.erase(kv.first);
firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it.
}
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
index 182ce3b..de7093d 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -50,7 +50,7 @@
const uint64_t& timestampNs);
// Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker
- // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor.
+ // and removes it from firedAlarms.
// TODO: This will actually be called from a different thread, so make it thread-safe!
// This means that almost every function in DurationAnomalyTracker needs to be locked.
// But this should be done at the level of StatsLogProcessor, which needs to lock
@@ -70,10 +70,10 @@
void resetStorage() override;
FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
- FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm);
FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
- FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 6087ae5..16fc7ee 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -84,7 +84,7 @@
FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
- FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
+ FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 023c25e..371460e 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -169,7 +169,8 @@
std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
- FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 293726c..638b7ad 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -67,7 +67,8 @@
FRIEND_TEST(OringDurationTrackerTest, TestCrossBucketBoundary);
FRIEND_TEST(OringDurationTrackerTest, TestDurationConditionChange);
FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
- FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm);
};
} // namespace statsd
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index 5842bc8..66bfa68 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -54,11 +54,73 @@
return bucket;
}
+// 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 auto& itr = bucket->find(key);
+ if (itr != bucket->end()) {
+ return itr->second;
+ }
+ return 0;
+}
+
+// Returns true if keys in trueList are detected as anomalies and keys in falseList are not.
+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) {
+ if (!tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) {
+ return false;
+ }
+ }
+ for (HashableDimensionKey key : falseList) {
+ if (tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Calls tracker.detectAndDeclareAnomaly on each key in the bucket.
+void detectAndDeclareAnomalies(AnomalyTracker& tracker,
+ const int64_t& bucketNum,
+ const std::shared_ptr<DimToValMap>& bucket,
+ const int64_t& eventTimestamp) {
+ for (const auto& kv : *bucket) {
+ tracker.detectAndDeclareAnomaly(eventTimestamp, bucketNum, kv.first, kv.second);
+ }
+}
+
+// Asserts that the refractory time for each key in timestamps is the corresponding
+// timestamp (in ns) + refractoryPeriodSec.
+// If a timestamp value is negative, instead asserts that the refractory period is inapplicable
+// (either non-existant or already past).
+void checkRefractoryTimes(AnomalyTracker& tracker,
+ const int64_t& currTimestampNs,
+ const int32_t& refractoryPeriodSec,
+ const std::unordered_map<HashableDimensionKey, 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.
+ EXPECT_LT(tracker.getRefractoryPeriodEndsSec(kv.first),
+ currTimestampNs / NS_PER_SEC + 1)
+ << "Failure was at currTimestampNs " << currTimestampNs;
+ } else {
+ EXPECT_EQ(tracker.getRefractoryPeriodEndsSec(kv.first),
+ kv.second / NS_PER_SEC + refractoryPeriodSec)
+ << "Failure was at currTimestampNs " << currTimestampNs;
+ }
+ }
+}
+
TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
const int64_t bucketSizeNs = 30 * NS_PER_SEC;
+ const int32_t refractoryPeriodSec = 2 * bucketSizeNs / NS_PER_SEC;
Alert alert;
alert.set_num_buckets(3);
- alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
+ alert.set_refractory_period_secs(refractoryPeriodSec);
alert.set_trigger_if_sum_gt(2);
AnomalyTracker anomalyTracker(alert, kConfigKey);
@@ -66,26 +128,31 @@
HashableDimensionKey keyB = getMockDimensionKey(1, "b");
HashableDimensionKey keyC = getMockDimensionKey(1, "c");
- std::shared_ptr<DimToValMap> bucket0 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
- int64_t eventTimestamp0 = 10;
- std::shared_ptr<DimToValMap> bucket1 = MockBucket({{keyA, 1}});
- int64_t eventTimestamp1 = bucketSizeNs + 11;
- std::shared_ptr<DimToValMap> bucket2 = MockBucket({{keyB, 1}});
- int64_t eventTimestamp2 = 2 * bucketSizeNs + 12;
- std::shared_ptr<DimToValMap> bucket3 = MockBucket({{keyA, 2}});
- int64_t eventTimestamp3 = 3 * bucketSizeNs + 13;
- std::shared_ptr<DimToValMap> bucket4 = MockBucket({{keyB, 1}});
- int64_t eventTimestamp4 = 4 * bucketSizeNs + 14;
- std::shared_ptr<DimToValMap> bucket5 = MockBucket({{keyA, 2}});
- int64_t eventTimestamp5 = 5 * bucketSizeNs + 15;
- std::shared_ptr<DimToValMap> bucket6 = MockBucket({{keyA, 2}});
- int64_t eventTimestamp6 = 6 * bucketSizeNs + 16;
+ int64_t eventTimestamp0 = 10 * NS_PER_SEC;
+ int64_t eventTimestamp1 = bucketSizeNs + 11 * NS_PER_SEC;
+ int64_t eventTimestamp2 = 2 * bucketSizeNs + 12 * NS_PER_SEC;
+ int64_t eventTimestamp3 = 3 * bucketSizeNs + 13 * NS_PER_SEC;
+ int64_t eventTimestamp4 = 4 * bucketSizeNs + 14 * NS_PER_SEC;
+ int64_t eventTimestamp5 = 5 * bucketSizeNs + 5 * NS_PER_SEC;
+ int64_t eventTimestamp6 = 6 * bucketSizeNs + 16 * NS_PER_SEC;
+ std::shared_ptr<DimToValMap> bucket0 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
+ std::shared_ptr<DimToValMap> bucket1 = MockBucket({{keyA, 1}});
+ std::shared_ptr<DimToValMap> bucket2 = MockBucket({{keyB, 1}});
+ std::shared_ptr<DimToValMap> bucket3 = MockBucket({{keyA, 2}});
+ std::shared_ptr<DimToValMap> bucket4 = MockBucket({{keyB, 5}});
+ std::shared_ptr<DimToValMap> bucket5 = MockBucket({{keyA, 2}});
+ std::shared_ptr<DimToValMap> bucket6 = MockBucket({{keyA, 2}});
+
+ // Start time with no events.
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
- EXPECT_FALSE(anomalyTracker.detectAnomaly(0, *bucket0));
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp0, 0, *bucket0);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L);
+
+ // Event from bucket #0 occurs.
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 0, bucket0, {}, {keyA, keyB, keyC}));
+ detectAndDeclareAnomalies(anomalyTracker, 0, bucket0, eventTimestamp1);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp0, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, -1}, {keyC, -1}});
// Adds past bucket #0
anomalyTracker.addPastBucket(bucket0, 0);
@@ -94,9 +161,12 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
- EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L);
+
+ // Event from bucket #1 occurs.
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 1, bucket1, {}, {keyA, keyB, keyC}));
+ detectAndDeclareAnomalies(anomalyTracker, 1, bucket1, eventTimestamp1);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, -1}, {keyC, -1}});
// Adds past bucket #0 again. The sum does not change.
anomalyTracker.addPastBucket(bucket0, 0);
@@ -105,9 +175,10 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
- EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L);
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 1, bucket1, {}, {keyA, keyB, keyC}));
+ detectAndDeclareAnomalies(anomalyTracker, 1, bucket1, eventTimestamp1 + 1);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, -1}, {keyC, -1}});
// Adds past bucket #1.
anomalyTracker.addPastBucket(bucket1, 1);
@@ -116,9 +187,12 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
- EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
+
+ // Event from bucket #2 occurs. New anomaly on keyB.
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 2, bucket2, {keyB}, {keyA, keyC}));
+ detectAndDeclareAnomalies(anomalyTracker, 2, bucket2, eventTimestamp2);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}});
// Adds past bucket #1 again. Nothing changes.
anomalyTracker.addPastBucket(bucket1, 1);
@@ -127,9 +201,11 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
- EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
+ // Event from bucket #2 occurs (again).
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 2, bucket2, {keyB}, {keyA, keyC}));
+ detectAndDeclareAnomalies(anomalyTracker, 2, bucket2, eventTimestamp2 + 1);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}});
// Adds past bucket #2.
anomalyTracker.addPastBucket(bucket2, 2);
@@ -137,10 +213,12 @@
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
- EXPECT_TRUE(anomalyTracker.detectAnomaly(3, *bucket3));
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3);
- // Within refractory period.
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
+
+ // Event from bucket #3 occurs. New anomaly on keyA.
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 3, bucket3, {keyA}, {keyB, keyC}));
+ detectAndDeclareAnomalies(anomalyTracker, 3, bucket3, eventTimestamp3);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp3, refractoryPeriodSec,
+ {{keyA, eventTimestamp3}, {keyB, eventTimestamp2}, {keyC, -1}});
// Adds bucket #3.
anomalyTracker.addPastBucket(bucket3, 3L);
@@ -148,37 +226,46 @@
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
- EXPECT_FALSE(anomalyTracker.detectAnomaly(4, *bucket4));
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
+
+ // Event from bucket #4 occurs. New anomaly on keyB.
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 4, bucket4, {keyB}, {keyA, keyC}));
+ detectAndDeclareAnomalies(anomalyTracker, 4, bucket4, eventTimestamp4);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp4, refractoryPeriodSec,
+ {{keyA, eventTimestamp3}, {keyB, eventTimestamp4}, {keyC, -1}});
// Adds bucket #4.
anomalyTracker.addPastBucket(bucket4, 4);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
- EXPECT_TRUE(anomalyTracker.detectAnomaly(5, *bucket5));
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp5);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL);
+
+ // Event from bucket #5 occurs. New anomaly on keyA, which is still in refractory.
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 5, bucket5, {keyA, keyB}, {keyC}));
+ detectAndDeclareAnomalies(anomalyTracker, 5, bucket5, eventTimestamp5);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp5, refractoryPeriodSec,
+ {{keyA, eventTimestamp3}, {keyB, eventTimestamp4}, {keyC, -1}});
// Adds bucket #5.
anomalyTracker.addPastBucket(bucket5, 5);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
- EXPECT_TRUE(anomalyTracker.detectAnomaly(6, *bucket6));
- // Within refractory period.
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp5);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL);
+
+ // Event from bucket #6 occurs. New anomaly on keyA, which is now out of refractory.
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 6, bucket6, {keyA, keyB}, {keyC}));
+ detectAndDeclareAnomalies(anomalyTracker, 6, bucket6, eventTimestamp6);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec,
+ {{keyA, eventTimestamp6}, {keyB, eventTimestamp4}, {keyC, -1}});
}
TEST(AnomalyTrackerTest, TestSparseBuckets) {
const int64_t bucketSizeNs = 30 * NS_PER_SEC;
+ const int32_t refractoryPeriodSec = 2 * bucketSizeNs / NS_PER_SEC;
Alert alert;
alert.set_num_buckets(3);
- alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
+ alert.set_refractory_period_secs(refractoryPeriodSec);
alert.set_trigger_if_sum_gt(2);
AnomalyTracker anomalyTracker(alert, kConfigKey);
@@ -204,9 +291,10 @@
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
- EXPECT_FALSE(anomalyTracker.detectAnomaly(9, *bucket9));
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 9, *bucket9);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1);
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 9, bucket9, {}, {keyA, keyB, keyC, keyD}));
+ detectAndDeclareAnomalies(anomalyTracker, 9, bucket9, eventTimestamp1);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
// Add past bucket #9
anomalyTracker.addPastBucket(bucket9, 9);
@@ -215,25 +303,27 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
- EXPECT_TRUE(anomalyTracker.detectAnomaly(16, *bucket16));
- EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 16, bucket16, {keyB}, {keyA, keyC, keyD}));
+ // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 16, *bucket16);
- EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
+ detectAndDeclareAnomalies(anomalyTracker, 16, bucket16, eventTimestamp2);
+ // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
// Add past bucket #16
anomalyTracker.addPastBucket(bucket16, 16);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
- EXPECT_TRUE(anomalyTracker.detectAnomaly(18, *bucket18));
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 18, bucket18, {keyB}, {keyA, keyC, keyD}));
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
// Within refractory period.
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
+ detectAndDeclareAnomalies(anomalyTracker, 18, bucket18, eventTimestamp3);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp3, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
@@ -243,13 +333,14 @@
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
- EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD}));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4);
+ detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp4, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
// Add bucket #18 again. Nothing changes.
anomalyTracker.addPastBucket(bucket18, 18);
@@ -257,13 +348,14 @@
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
- EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD}));
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20);
+ detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4 + 1);
// Within refractory period.
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp4 + 1, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
// Add past bucket #20
anomalyTracker.addPastBucket(bucket20, 20);
@@ -271,32 +363,37 @@
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 3LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
- EXPECT_FALSE(anomalyTracker.detectAnomaly(25, *bucket25));
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 25, bucket25, {}, {keyA, keyB, keyC, keyD}));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L);
- EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 25, *bucket25);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4);
+ // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ detectAndDeclareAnomalies(anomalyTracker, 25, bucket25, eventTimestamp5);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp5, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
// Add past bucket #25
anomalyTracker.addPastBucket(bucket25, 25);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L);
- EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+ // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyD), 1LL);
- EXPECT_FALSE(anomalyTracker.detectAnomaly(28, *bucket28));
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {},
+ {keyA, keyB, keyC, keyD, keyE}));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
- EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 28, *bucket28);
- EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4);
+ // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6);
+ // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, -1}});
// Updates current bucket #28.
(*bucket28)[keyE] = 5;
- EXPECT_TRUE(anomalyTracker.detectAnomaly(28, *bucket28));
+ EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {keyE},
+ {keyA, keyB, keyC, keyD}));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
- EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
- anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6 + 7, 28, *bucket28);
- EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
- EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp6 + 7);
+ // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6 + 7);
+ // TODO: after detectAnomaly fix: EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec,
+ {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, eventTimestamp6 + 7}});
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 768336b..c391513 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -191,13 +191,14 @@
EXPECT_EQ(1LL, bucketInfo.mCount);
}
-TEST(CountMetricProducerTest, TestAnomalyDetection) {
+TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) {
Alert alert;
alert.set_id(11);
alert.set_metric_id(1);
alert.set_trigger_if_sum_gt(2);
alert.set_num_buckets(2);
- alert.set_refractory_period_secs(1);
+ const int32_t refPeriodSec = 1;
+ alert.set_refractory_period_secs(refPeriodSec);
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
@@ -220,7 +221,7 @@
LogEvent event4(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1);
LogEvent event5(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2);
LogEvent event6(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3);
- LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3 + NS_PER_SEC);
+ LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC);
// Two events in bucket #0.
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
@@ -228,13 +229,13 @@
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second);
- EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL);
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_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->getLastAnomalyTimestampNs(), -1LL);
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
// Two events in bucket #3.
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
@@ -243,12 +244,14 @@
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->getLastAnomalyTimestampNs(), (long long)event5.GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_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->getLastAnomalyTimestampNs(), (long long)event7.GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+ event7.GetTimestampNs() / NS_PER_SEC + refPeriodSec);
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index ad9c5a3..82772d8 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -201,6 +201,8 @@
alert.set_metric_id(metricId);
alert.set_trigger_if_sum_gt(25);
alert.set_num_buckets(2);
+ const int32_t refPeriodSec = 60;
+ alert.set_refractory_period_secs(refPeriodSec);
sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert);
int tagId = 1;
@@ -213,10 +215,10 @@
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
EXPECT_EQ(13L,
gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
- EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL);
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
std::shared_ptr<LogEvent> event2 =
- std::make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 10);
+ std::make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 20);
event2->write("some value");
event2->write(15);
event2->init();
@@ -225,19 +227,21 @@
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
EXPECT_EQ(15L,
gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
- EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event2->GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+ event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
std::shared_ptr<LogEvent> event3 =
std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10);
event3->write("some value");
- event3->write(24);
+ event3->write(26);
event3->init();
gaugeProducer.onDataPulled({event3});
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- EXPECT_EQ(24L,
+ EXPECT_EQ(26L,
gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
- EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event3->GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+ event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
// The event4 does not have the gauge field. Thus the current bucket value is 0.
std::shared_ptr<LogEvent> event4 =
@@ -247,7 +251,6 @@
gaugeProducer.onDataPulled({event4});
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second->empty());
- EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event3->GetTimestampNs());
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index f98be1b..0772b0d40 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -210,7 +210,8 @@
alert.set_metric_id(metricId);
alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
alert.set_num_buckets(2);
- alert.set_refractory_period_secs(1);
+ const int32_t refPeriodSec = 1;
+ alert.set_refractory_period_secs(refPeriodSec);
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -225,15 +226,16 @@
tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey());
tracker.noteStop(key1, eventStartTimeNs + 10, false);
- EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs, -1);
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
EXPECT_EQ(10LL, tracker.mDuration);
tracker.noteStart(key2, true, eventStartTimeNs + 20, ConditionKey());
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, &buckets);
tracker.noteStop(key2, eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration);
- EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs,
- (long long)(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC));
+
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey),
+ (eventStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 3 + refPeriodSec);
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 89c6abe..6b8893e 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -309,13 +309,14 @@
tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
}
-TEST(OringDurationTrackerTest, TestAnomalyDetection) {
+TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) {
Alert alert;
alert.set_id(101);
alert.set_metric_id(1);
alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
alert.set_num_buckets(2);
- alert.set_refractory_period_secs(1);
+ const int32_t refPeriodSec = 45;
+ alert.set_refractory_period_secs(refPeriodSec);
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -328,23 +329,86 @@
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
- tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
- tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 10, false);
- EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs, -1);
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
+ tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
EXPECT_TRUE(tracker.mStarted.empty());
EXPECT_EQ(10LL, tracker.mDuration);
EXPECT_EQ(0u, tracker.mStarted.size());
- tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs + 20, ConditionKey());
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs + 20, ConditionKey());
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
EXPECT_EQ((long long)(51ULL * NS_PER_SEC),
(long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
+ // The alarm is set to fire at 51s, and when it does, an anomaly would be declared. However,
+ // because this is a unit test, the alarm won't actually fire at all. Since the alarm fails
+ // to fire in time, the anomaly is instead caught when noteStop is called, at around 71s.
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 25, &buckets);
- tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 2 * bucketSizeNs + 25, false);
+ tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 25, false);
EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs));
- EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25),
- anomalyTracker->mLastAnomalyTimestampNs);
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey),
+ (eventStartTimeNs + 2 * bucketSizeNs + 25) / NS_PER_SEC + refPeriodSec);
+}
+
+TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) {
+ Alert alert;
+ alert.set_id(101);
+ alert.set_metric_id(1);
+ alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
+ alert.set_num_buckets(2);
+ const int32_t refPeriodSec = 45;
+ alert.set_refractory_period_secs(refPeriodSec);
+
+ unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ ConditionKey conkey;
+ conkey[StringToId("APP_BACKGROUND")] = kConditionKey1;
+ uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
+ 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});
+
+ tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
+ EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+ sp<const AnomalyAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
+ EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
+
+ tracker.noteStop(kEventKey1, 17 * NS_PER_SEC, false); // stop key1 (2 seconds later)
+ EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
+
+ tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again
+ EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+ alarm = anomalyTracker->mAlarms.begin()->second;
+ EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
+
+ tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2
+ EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+ alarm = anomalyTracker->mAlarms.begin()->second;
+ EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
+
+ tracker.noteStop(kEventKey1, 47 * NS_PER_SEC, false); // stop key1
+ EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+ alarm = anomalyTracker->mAlarms.begin()->second;
+ EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
+
+ // Now, at 60s, which is 38s after key1 started again, we have reached 40s of 'on' time.
+ std::unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> firedAlarms({alarm});
+ anomalyTracker->informAlarmsFired(62 * NS_PER_SEC, firedAlarms);
+ EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec);
+
+ tracker.noteStop(kEventKey2, 69 * NS_PER_SEC, false); // stop key2
+ EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec);
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 459da01..fff3dbf 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -249,7 +249,8 @@
alert.set_metric_id(metricId);
alert.set_trigger_if_sum_gt(130);
alert.set_num_buckets(2);
- alert.set_refractory_period_secs(3);
+ const int32_t refPeriodSec = 3;
+ alert.set_refractory_period_secs(refPeriodSec);
ValueMetric metric;
metric.set_id(metricId);
@@ -297,23 +298,28 @@
// Two events in bucket #0.
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
- EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // Value sum == 30 <= 130.
+ // Value sum == 30 <= 130.
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
// One event in bucket #2. No alarm as bucket #0 is trashed out.
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
- EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // Value sum == 130 <= 130.
+ // Value sum == 130 <= 130.
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_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->getLastAnomalyTimestampNs(), (long long)event4->GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_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->getLastAnomalyTimestampNs(), (long long)event4->GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_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->getLastAnomalyTimestampNs(), (long long)event6->GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
+ event6->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
}
} // namespace statsd
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index f643c57..01d6b02 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -98,4 +98,6 @@
boolean isUserNameSet(int userHandle);
boolean hasRestrictedProfiles();
boolean trySetQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target);
+ long getUserStartRealtime();
+ long getUserUnlockRealtime();
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 38993b7..44238df 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1392,6 +1392,34 @@
}
/**
+ * Return the time when the calling user started in elapsed milliseconds since boot,
+ * or 0 if not started.
+ *
+ * @hide
+ */
+ public long getUserStartRealtime() {
+ try {
+ return mService.getUserStartRealtime();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the time when the calling user was unlocked elapsed milliseconds since boot,
+ * or 0 if not unlocked.
+ *
+ * @hide
+ */
+ public long getUserUnlockRealtime() {
+ try {
+ return mService.getUserUnlockRealtime();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the UserInfo object describing a specific user.
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
* @param userHandle the user handle of the user whose information is being requested.
diff --git a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
new file mode 100644
index 0000000..3254ebb
--- /dev/null
+++ b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<!-- This should be kept in sync with task_open_enter.xml -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false" android:zAdjustment="top">
+
+ <alpha android:fromAlpha="0" android:toAlpha="1.0"
+ android:startOffset="300"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_quart"
+ android:duration="167"/>
+
+ <translate android:fromYDelta="110%" android:toYDelta="0"
+ android:startOffset="300"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:duration="417"/>
+
+ <!-- To keep the thumbnail around longer -->
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:startOffset="717"
+ android:duration="200"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml
index e511cc9..b73e14f 100644
--- a/core/res/res/anim/task_open_enter.xml
+++ b/core/res/res/anim/task_open_enter.xml
@@ -16,7 +16,7 @@
** limitations under the License.
*/
-->
-
+<!-- This should in sync with task_open_enter_cross_profile_apps.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false" android:zAdjustment="top">
diff --git a/core/res/res/anim/task_open_enter_cross_profile_apps.xml b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
new file mode 100644
index 0000000..ad89fde
--- /dev/null
+++ b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<!-- This should in sync with task_open_enter.xml -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false" android:zAdjustment="top">
+
+ <alpha android:fromAlpha="0" android:toAlpha="1.0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_quart"
+ android:startOffset="300"
+ android:duration="167"/>
+
+ <translate android:fromYDelta="110%" android:toYDelta="0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:startOffset="300"
+ android:duration="417"/>
+
+ <!-- To keep the transition around longer for the thumbnail, should be kept in sync with
+ cross_profile_apps_thumbmail.xml -->
+ <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:startOffset="717"
+ android:duration="200"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index a659b37..6c1661c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -631,4 +631,8 @@
<!-- Default dialog corner radius -->
<dimen name="dialog_corner_radius">2dp</dimen>
+
+ <!-- Size of thumbnail used in the cross profile apps animation -->
+ <dimen name="cross_profile_apps_thumbnail_size">72dp</dimen>
+
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4343ba0..9f582ad 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1578,8 +1578,10 @@
<java-symbol type="anim" name="voice_activity_close_enter" />
<java-symbol type="anim" name="voice_activity_open_exit" />
<java-symbol type="anim" name="voice_activity_open_enter" />
- <java-symbol type="anim" name="activity_open_exit" />
- <java-symbol type="anim" name="activity_open_enter" />
+ <java-symbol type="anim" name="task_open_exit" />
+ <java-symbol type="anim" name="task_open_enter" />
+ <java-symbol type="anim" name="cross_profile_apps_thumbnail_enter" />
+ <java-symbol type="anim" name="task_open_enter_cross_profile_apps" />
<java-symbol type="array" name="config_autoRotationTiltTolerance" />
<java-symbol type="array" name="config_keyboardTapVibePattern" />
@@ -1726,6 +1728,7 @@
<java-symbol type="style" name="Theme.ExpandedMenu" />
<java-symbol type="string" name="forward_intent_to_owner" />
<java-symbol type="string" name="forward_intent_to_work" />
+ <java-symbol type="dimen" name="cross_profile_apps_thumbnail_size" />
<!-- From services -->
<java-symbol type="anim" name="screen_rotate_0_enter" />
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 7bab318..ebf6672 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -459,8 +459,7 @@
}
// Then, for the pinned set for each launcher, set the pin flag one by one.
- mShortcutUser.mService.getUserShortcutsLocked(getPackageUserId())
- .forAllLaunchers(launcherShortcuts -> {
+ mShortcutUser.forAllLaunchers(launcherShortcuts -> {
final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
getPackageName(), getPackageUserId());
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c3dce31..7d57566 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -63,6 +63,7 @@
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -79,6 +80,7 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.Xml;
@@ -241,8 +243,7 @@
private static final IBinder mUserRestriconToken = new Binder();
/**
- * User-related information that is used for persisting to flash. Only UserInfo is
- * directly exposed to other system apps.
+ * Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps.
*/
@VisibleForTesting
static class UserData {
@@ -260,6 +261,12 @@
// Whether to perist the seed account information to be available after a boot
boolean persistSeedData;
+ /** Elapsed realtime since boot when the user started. */
+ long startRealtime;
+
+ /** Elapsed realtime since boot when the user was unlocked. */
+ long unlockRealtime;
+
void clearSeedAccountData() {
seedAccountName = null;
seedAccountType = null;
@@ -453,6 +460,37 @@
mUms.cleanupPartialUsers();
}
}
+
+ @Override
+ public void onStartUser(int userHandle) {
+ synchronized (mUms.mUsersLock) {
+ final UserData user = mUms.getUserDataLU(userHandle);
+ if (user != null) {
+ user.startRealtime = SystemClock.elapsedRealtime();
+ }
+ }
+ }
+
+ @Override
+ public void onUnlockUser(int userHandle) {
+ synchronized (mUms.mUsersLock) {
+ final UserData user = mUms.getUserDataLU(userHandle);
+ if (user != null) {
+ user.unlockRealtime = SystemClock.elapsedRealtime();
+ }
+ }
+ }
+
+ @Override
+ public void onStopUser(int userHandle) {
+ synchronized (mUms.mUsersLock) {
+ final UserData user = mUms.getUserDataLU(userHandle);
+ if (user != null) {
+ user.startRealtime = 0;
+ user.unlockRealtime = 0;
+ }
+ }
+ }
}
// TODO b/28848102 Add support for test dependencies injection
@@ -1057,6 +1095,29 @@
return mLocalService.isUserRunning(userId);
}
+ @Override
+ public long getUserStartRealtime() {
+ final int userId = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized (mUsersLock) {
+ final UserData user = getUserDataLU(userId);
+ if (user != null) {
+ return user.startRealtime;
+ }
+ return 0;
+ }
+ }
+
+ @Override
+ public long getUserUnlockRealtime() {
+ synchronized (mUsersLock) {
+ final UserData user = getUserDataLU(UserHandle.getUserId(Binder.getCallingUid()));
+ if (user != null) {
+ return user.unlockRealtime;
+ }
+ return 0;
+ }
+ }
+
private void checkManageOrInteractPermIfCallerInOtherProfileGroup(int userId, String name) {
int callingUserId = UserHandle.getCallingUserId();
if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) ||
@@ -3484,6 +3545,7 @@
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
long now = System.currentTimeMillis();
+ final long nowRealtime = SystemClock.elapsedRealtime();
StringBuilder sb = new StringBuilder();
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
@@ -3511,25 +3573,20 @@
}
pw.println(UserState.stateToString(state));
pw.print(" Created: ");
- if (userInfo.creationTime == 0) {
- pw.println("<unknown>");
- } else {
- sb.setLength(0);
- TimeUtils.formatDuration(now - userInfo.creationTime, sb);
- sb.append(" ago");
- pw.println(sb);
- }
+ dumpTimeAgo(pw, sb, now, userInfo.creationTime);
+
pw.print(" Last logged in: ");
- if (userInfo.lastLoggedInTime == 0) {
- pw.println("<unknown>");
- } else {
- sb.setLength(0);
- TimeUtils.formatDuration(now - userInfo.lastLoggedInTime, sb);
- sb.append(" ago");
- pw.println(sb);
- }
+ dumpTimeAgo(pw, sb, now, userInfo.lastLoggedInTime);
+
pw.print(" Last logged in fingerprint: ");
pw.println(userInfo.lastLoggedInFingerprint);
+
+ pw.print(" Start time: ");
+ dumpTimeAgo(pw, sb, nowRealtime, userData.startRealtime);
+
+ pw.print(" Unlock time: ");
+ dumpTimeAgo(pw, sb, nowRealtime, userData.unlockRealtime);
+
pw.print(" Has profile owner: ");
pw.println(mIsUserManaged.get(userId));
pw.println(" Restrictions:");
@@ -3593,6 +3650,17 @@
}
}
+ private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) {
+ if (time == 0) {
+ pw.println("<unknown>");
+ } else {
+ sb.setLength(0);
+ TimeUtils.formatDuration(nowTime - time, sb);
+ sb.append(" ago");
+ pw.println(sb);
+ }
+ }
+
final class MainHandler extends Handler {
@Override
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 2ac7583..d4b437a 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -49,14 +49,17 @@
import static com.android.server.wm.proto.AppTransitionProto.APP_TRANSITION_STATE;
import static com.android.server.wm.proto.AppTransitionProto.LAST_USED_APP_TRANSITION;
+import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.GraphicBuffer;
import android.graphics.Path;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Debug;
import android.os.IBinder;
@@ -70,7 +73,10 @@
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.AppTransitionAnimationSpec;
+import android.view.DisplayListCanvas;
import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
@@ -391,6 +397,11 @@
mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
}
+
+ boolean isNextAppTransitionOpenCrossProfileApps() {
+ return mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
+ }
+
/**
* @return true if and only if we are currently fetching app transition specs from the future
* passed into {@link #overridePendingAppTransitionMultiThumbFuture}
@@ -978,6 +989,43 @@
}
/**
+ * Creates an overlay with a background color and a thumbnail for the cross profile apps
+ * animation.
+ */
+ GraphicBuffer createCrossProfileAppsThumbnail(
+ @DrawableRes int thumbnailDrawableRes, Rect frame) {
+ final int width = frame.width();
+ final int height = frame.height();
+
+ final RenderNode node = RenderNode.create("CrossProfileAppsThumbnail", null);
+ node.setLeftTopRightBottom(0, 0, width, height);
+ node.setClipToBounds(false);
+
+ final DisplayListCanvas canvas = node.start(width, height);
+ canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
+ final int thumbnailSize = mService.mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
+ final Drawable drawable = mService.mContext.getDrawable(thumbnailDrawableRes);
+ drawable.setBounds(
+ (width - thumbnailSize) / 2,
+ (height - thumbnailSize) / 2,
+ (width + thumbnailSize) / 2,
+ (height + thumbnailSize) / 2);
+ drawable.draw(canvas);
+ node.end(canvas);
+
+ return ThreadedRenderer.createHardwareBitmap(node, width, height)
+ .createGraphicBufferHandle();
+ }
+
+ Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
+ final Animation animation = loadAnimationRes(
+ "android", com.android.internal.R.anim.cross_profile_apps_thumbnail_enter);
+ return prepareThumbnailAnimationWithDuration(animation, appRect.width(),
+ appRect.height(), 0, null);
+ }
+
+ /**
* This animation runs for the thumbnail that gets cross faded with the enter/exit activity
* when a thumbnail is specified with the pending animation override.
*/
@@ -1624,9 +1672,10 @@
&& (transit == TRANSIT_ACTIVITY_OPEN
|| transit == TRANSIT_TASK_OPEN
|| transit == TRANSIT_TASK_TO_FRONT)) {
+
a = loadAnimationRes("android", enter
- ? com.android.internal.R.anim.activity_open_enter
- : com.android.internal.R.anim.activity_open_exit);
+ ? com.android.internal.R.anim.task_open_enter_cross_profile_apps
+ : com.android.internal.R.anim.task_open_exit);
Slog.v(TAG,
"applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:"
+ " anim=" + a + " transit=" + appTransitionToString(transit)
@@ -2007,6 +2056,8 @@
return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP";
case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN:
return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN";
+ case NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:
+ return "NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS";
default:
return "unknown type=" + mNextAppTransitionType;
}
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
index 0d65790..487b52c 100644
--- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -85,10 +85,14 @@
}
void startAnimation(Transaction t, Animation anim) {
+ startAnimation(t, anim, null /* position */);
+ }
+
+ void startAnimation(Transaction t, Animation anim, Point position) {
anim.restrictDuration(MAX_ANIMATION_DURATION);
anim.scaleCurrentDuration(mAppToken.mService.getTransitionAnimationScaleLocked());
mSurfaceAnimator.startAnimation(t, new LocalAnimationAdapter(
- new WindowAnimationSpec(anim, null /* position */,
+ new WindowAnimationSpec(anim, position,
mAppToken.mService.mAppTransition.canSkipFirstFrame()),
mAppToken.mService.mSurfaceAnimationRunner), false /* hidden */);
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 01e925e..fc0564d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
@@ -31,6 +30,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
@@ -56,7 +56,6 @@
import android.annotation.CallSuper;
import android.app.Activity;
-import android.app.WindowConfiguration.WindowingMode;
import android.content.res.Configuration;
import android.graphics.GraphicBuffer;
import android.graphics.Point;
@@ -69,13 +68,14 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
-import android.view.SurfaceControl.Transaction;
-import android.view.animation.Animation;
import android.view.IApplicationToken;
import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
+import android.view.animation.Animation;
+import com.android.internal.R;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.input.InputApplicationHandle;
import com.android.server.policy.WindowManagerPolicy.StartingSurface;
@@ -1775,6 +1775,37 @@
mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader));
}
+ /**
+ * Attaches a surface with a thumbnail for the
+ * {@link android.app.ActivityOptions#ANIM_OPEN_CROSS_PROFILE_APPS} animation.
+ */
+ void attachCrossProfileAppsThumbnailAnimation() {
+ if (!isReallyAnimating()) {
+ return;
+ }
+ clearThumbnail();
+
+ final WindowState win = findMainWindow();
+ if (win == null) {
+ return;
+ }
+ final Rect frame = win.mFrame;
+ final int thumbnailDrawableRes = getTask().mUserId == mService.mCurrentUserId
+ ? R.drawable.ic_account_circle
+ : R.drawable.ic_corp_badge_no_background;
+ final GraphicBuffer thumbnail =
+ mService.mAppTransition
+ .createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
+ if (thumbnail == null) {
+ return;
+ }
+ mThumbnail = new AppWindowThumbnail(getPendingTransaction(), this, thumbnail);
+ final Animation animation =
+ mService.mAppTransition.createCrossProfileAppsThumbnailAnimationLocked(win.mFrame);
+ mThumbnail.startAnimation(getPendingTransaction(), animation, new Point(frame.left,
+ frame.top));
+ }
+
private Animation loadThumbnailAnimation(GraphicBuffer thumbnailHeader) {
final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 35a65a6..2cc2a0e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -49,7 +49,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -3465,47 +3464,37 @@
@Override
void assignChildLayers(SurfaceControl.Transaction t) {
-
- final int NORMAL_STACK_STATE = 0;
- final int SPLIT_SCREEN_STACK_STATE = 1;
- final int ASSISTANT_STACK_STATE = 2;
- final int BOOSTED_STATE = 3;
- final int ALWAYS_ON_TOP_STATE = 4;
-
int layer = 0;
- for (int state = 0; state <= ALWAYS_ON_TOP_STATE; state++) {
- for (int i = 0; i < mChildren.size(); i++) {
- final TaskStack s = mChildren.get(i);
- layer++;
- if (state == NORMAL_STACK_STATE && !s.inSplitScreenPrimaryWindowingMode() &&
- !s.isActivityTypeAssistant() &&
- !s.needsZBoost() && !s.isAlwaysOnTop()) {
- s.assignLayer(t, layer);
- } else if (state == SPLIT_SCREEN_STACK_STATE &&
- s.inSplitScreenPrimaryWindowingMode()) {
- s.assignLayer(t, layer);
- } else if (state == ASSISTANT_STACK_STATE &&
- s.isActivityTypeAssistant()) {
- s.assignLayer(t, layer);
- } else if (state == BOOSTED_STATE && s.needsZBoost()) {
- s.assignLayer(t, layer);
- } else if (state == ALWAYS_ON_TOP_STATE &&
- s.isAlwaysOnTop()) {
- s.assignLayer(t, layer);
- }
- }
- // The appropriate place for App-Transitions to occur is right
- // above all other animations but still below things in the Picture-and-Picture
- // windowing mode.
- if (state == BOOSTED_STATE && mAppAnimationLayer != null) {
- t.setLayer(mAppAnimationLayer, layer++);
+
+ // We allow stacks to change visual order from the AM specified order due to
+ // Z-boosting during animations. However we must take care to ensure TaskStacks
+ // which are marked as alwaysOnTop remain that way.
+ for (int i = 0; i < mChildren.size(); i++) {
+ final TaskStack s = mChildren.get(i);
+ s.assignChildLayers();
+ if (!s.needsZBoost() && !s.isAlwaysOnTop()) {
+ s.assignLayer(t, layer++);
}
}
for (int i = 0; i < mChildren.size(); i++) {
final TaskStack s = mChildren.get(i);
- s.assignChildLayers(t);
+ if (s.needsZBoost() && !s.isAlwaysOnTop()) {
+ s.assignLayer(t, layer++);
+ }
+ }
+ for (int i = 0; i < mChildren.size(); i++) {
+ final TaskStack s = mChildren.get(i);
+ if (s.isAlwaysOnTop()) {
+ s.assignLayer(t, layer++);
+ }
}
+ // The appropriate place for App-Transitions to occur is right
+ // above all other animations but still below things in the Picture-and-Picture
+ // windowing mode.
+ if (mAppAnimationLayer != null) {
+ t.setLayer(mAppAnimationLayer, layer++);
+ }
}
@Override
@@ -3539,18 +3528,6 @@
&& imeContainer.getSurfaceControl() != null;
for (int j = 0; j < mChildren.size(); ++j) {
final WindowToken wt = mChildren.get(j);
-
- // The divider is unique in that it does not have an AppWindowToken but needs to be
- // interleaved with them. In particular it must be above any split-screen stacks
- // but below any always-on-top stacks.
- if (wt.windowType == TYPE_DOCK_DIVIDER) {
- final TaskStack dockedStack = getSplitScreenPrimaryStack();
- if (dockedStack != null) {
- wt.assignRelativeLayer(t, dockedStack.getSurfaceControl(),
- Integer.MAX_VALUE);
- continue;
- }
- }
wt.assignLayer(t, j);
wt.assignChildLayers(t);
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 08f49f6..a512fdf 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -20,6 +20,7 @@
import static android.app.ActivityManagerInternal.APP_TRANSITION_SNAPSHOT;
import static android.app.ActivityManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
import static android.app.ActivityManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
+
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
@@ -43,28 +44,19 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
import static com.android.server.wm.WindowManagerService.H.REPORT_WINDOWS_CHANGE;
import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
-import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
-import android.content.res.Configuration;
-import android.graphics.GraphicBuffer;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Binder;
import android.os.Debug;
import android.os.Trace;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseIntArray;
import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager.LayoutParams;
import android.view.animation.Animation;
@@ -414,7 +406,6 @@
}
wtoken.updateReportedVisibilityLocked();
wtoken.waitingToShow = false;
-
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
mService.openSurfaceTransaction();
@@ -435,6 +426,8 @@
}
if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
wtoken.attachThumbnailAnimation();
+ } else if (mService.mAppTransition.isNextAppTransitionOpenCrossProfileApps()) {
+ wtoken.attachCrossProfileAppsThumbnailAnimation();
}
}
return topOpeningApp;
diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
index bcc9a1c..6468763 100644
--- a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
@@ -21,12 +21,10 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
@@ -76,11 +74,11 @@
return super.setRelativeLayer(sc, relativeTo, layer);
}
- private int getLayer(SurfaceControl sc) {
+ int getLayer(SurfaceControl sc) {
return mLayersForControl.getOrDefault(sc, 0);
}
- private SurfaceControl getRelativeLayer(SurfaceControl sc) {
+ SurfaceControl getRelativeLayer(SurfaceControl sc) {
return mRelativeLayersForControl.get(sc);
}
};
@@ -148,9 +146,8 @@
return p;
}
-
- void assertZOrderGreaterThan(LayerRecordingTransaction t, SurfaceControl left,
- SurfaceControl right) throws Exception {
+ void assertZOrderGreaterThan(LayerRecordingTransaction t,
+ SurfaceControl left, SurfaceControl right) throws Exception {
final LinkedList<SurfaceControl> leftParentChain = getAncestors(t, left);
final LinkedList<SurfaceControl> rightParentChain = getAncestors(t, right);
@@ -174,12 +171,9 @@
}
}
- void assertWindowHigher(WindowState left, WindowState right) throws Exception {
- assertZOrderGreaterThan(mTransaction, left.getSurfaceControl(), right.getSurfaceControl());
- }
-
- WindowState createWindow(String name) {
- return createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, name);
+ void assertWindowLayerGreaterThan(LayerRecordingTransaction t,
+ WindowState left, WindowState right) throws Exception {
+ assertZOrderGreaterThan(t, left.getSurfaceControl(), right.getSurfaceControl());
}
@Test
@@ -190,37 +184,38 @@
// The Ime has an higher base layer than app windows and lower base layer than system
// windows, so it should be above app windows and below system windows if there isn't an IME
// target.
- assertWindowHigher(mImeWindow, mChildAppWindowAbove);
- assertWindowHigher(mImeWindow, mAppWindow);
- assertWindowHigher(mNavBarWindow, mImeWindow);
- assertWindowHigher(mStatusBarWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+ assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
// And, IME dialogs should always have an higher layer than the IME.
- assertWindowHigher(mImeDialogWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
}
@Test
public void testAssignWindowLayers_ForImeWithAppTarget() throws Exception {
- final WindowState imeAppTarget = createWindow("imeAppTarget");
+ final WindowState imeAppTarget =
+ createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
sWm.mInputMethodTarget = imeAppTarget;
-
mDisplayContent.assignChildLayers(mTransaction);
// Ime should be above all app windows and below system windows if it is targeting an app
// window.
- assertWindowHigher(mImeWindow, imeAppTarget);
- assertWindowHigher(mImeWindow, mChildAppWindowAbove);
- assertWindowHigher(mImeWindow, mAppWindow);
- assertWindowHigher(mNavBarWindow, mImeWindow);
- assertWindowHigher(mStatusBarWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+ assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
// And, IME dialogs should always have an higher layer than the IME.
- assertWindowHigher(mImeDialogWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
}
@Test
public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() throws Exception {
- final WindowState imeAppTarget = createWindow("imeAppTarget");
+ final WindowState imeAppTarget =
+ createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget,
TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken,
"imeAppTargetChildAboveWindow");
@@ -233,38 +228,41 @@
// Ime should be above all app windows except for child windows that are z-ordered above it
// and below system windows if it is targeting an app window.
- assertWindowHigher(mImeWindow, imeAppTarget);
- assertWindowHigher(imeAppTargetChildAboveWindow, mImeWindow);
- assertWindowHigher(mImeWindow, mChildAppWindowAbove);
- assertWindowHigher(mImeWindow, mAppWindow);
- assertWindowHigher(mNavBarWindow, mImeWindow);
- assertWindowHigher(mStatusBarWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget);
+ assertWindowLayerGreaterThan(mTransaction, imeAppTargetChildAboveWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+ assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
// And, IME dialogs should always have an higher layer than the IME.
- assertWindowHigher(mImeDialogWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
}
@Test
public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() throws Exception {
- final WindowState appBelowImeTarget = createWindow("appBelowImeTarget");
- final WindowState imeAppTarget = createWindow("imeAppTarget");
- final WindowState appAboveImeTarget = createWindow("appAboveImeTarget");
+ final WindowState appBelowImeTarget =
+ createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appBelowImeTarget");
+ final WindowState imeAppTarget =
+ createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
+ final WindowState appAboveImeTarget =
+ createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appAboveImeTarget");
sWm.mInputMethodTarget = imeAppTarget;
mDisplayContent.assignChildLayers(mTransaction);
// Ime should be above all app windows except for non-fullscreen app window above it and
// below system windows if it is targeting an app window.
- assertWindowHigher(mImeWindow, imeAppTarget);
- assertWindowHigher(mImeWindow, appBelowImeTarget);
- assertWindowHigher(appAboveImeTarget, mImeWindow);
- assertWindowHigher(mImeWindow, mChildAppWindowAbove);
- assertWindowHigher(mImeWindow, mAppWindow);
- assertWindowHigher(mNavBarWindow, mImeWindow);
- assertWindowHigher(mStatusBarWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, appBelowImeTarget);
+ assertWindowLayerGreaterThan(mTransaction, appAboveImeTarget, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+ assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
// And, IME dialogs should always have an higher layer than the IME.
- assertWindowHigher(mImeDialogWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
}
@Test
@@ -278,20 +276,20 @@
// The IME target base layer is higher than all window except for the nav bar window, so the
// IME should be above all windows except for the nav bar.
- assertWindowHigher(mImeWindow, imeSystemOverlayTarget);
- assertWindowHigher(mImeWindow, mChildAppWindowAbove);
- assertWindowHigher(mImeWindow, mAppWindow);
- assertWindowHigher(mImeWindow, mDockedDividerWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeSystemOverlayTarget);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow);
// The IME has a higher base layer than the status bar so we may expect it to go
// above the status bar once they are both in the Non-App layer, as past versions of this
// test enforced. However this seems like the wrong behavior unless the status bar is the
// IME target.
- assertWindowHigher(mNavBarWindow, mImeWindow);
- assertWindowHigher(mStatusBarWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
// And, IME dialogs should always have an higher layer than the IME.
- assertWindowHigher(mImeDialogWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
}
@Test
@@ -299,18 +297,17 @@
sWm.mInputMethodTarget = mStatusBarWindow;
mDisplayContent.assignChildLayers(mTransaction);
- assertWindowHigher(mImeWindow, mChildAppWindowAbove);
- assertWindowHigher(mImeWindow, mAppWindow);
- assertWindowHigher(mImeWindow, mDockedDividerWindow);
- assertWindowHigher(mImeWindow, mStatusBarWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeWindow, mStatusBarWindow);
// And, IME dialogs should always have an higher layer than the IME.
- assertWindowHigher(mImeDialogWindow, mImeWindow);
+ assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
}
@Test
public void testStackLayers() throws Exception {
- final WindowState anyWindow1 = createWindow("anyWindow");
final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED,
ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent,
"pinnedStackWindow");
@@ -320,17 +317,12 @@
final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
mDisplayContent, "assistantStackWindow");
- final WindowState anyWindow2 = createWindow("anyWindow2");
mDisplayContent.assignChildLayers(mTransaction);
- // We compare the split-screen windowing mode to two different normal windowing
- // mode windows added before and after it to ensure the correct Z ordering irrespective
- // of ordering in the child list.
- assertWindowHigher(dockedStackWindow, anyWindow1);
- assertWindowHigher(dockedStackWindow, anyWindow2);
- assertWindowHigher(assistantStackWindow, dockedStackWindow);
- assertWindowHigher(pinnedStackWindow, assistantStackWindow);
+ assertWindowLayerGreaterThan(mTransaction, dockedStackWindow, mAppWindow);
+ assertWindowLayerGreaterThan(mTransaction, assistantStackWindow, dockedStackWindow);
+ assertWindowLayerGreaterThan(mTransaction, pinnedStackWindow, assistantStackWindow);
}
@Test
@@ -345,9 +337,9 @@
// Ime should be above all app windows and below system windows if it is targeting an app
// window.
- assertWindowHigher(navBarPanel, mNavBarWindow);
- assertWindowHigher(statusBarPanel, mStatusBarWindow);
- assertWindowHigher(statusBarSubPanel, statusBarPanel);
+ assertWindowLayerGreaterThan(mTransaction, navBarPanel, mNavBarWindow);
+ assertWindowLayerGreaterThan(mTransaction, statusBarPanel, mStatusBarWindow);
+ assertWindowLayerGreaterThan(mTransaction, statusBarSubPanel, statusBarPanel);
}
@Test
@@ -355,7 +347,8 @@
// TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA
// then we can drop all negative layering on the windowing side.
- final WindowState anyWindow = createWindow("anyWindow");
+ final WindowState anyWindow =
+ createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "anyWindow");
final WindowState child = createWindow(anyWindow, TYPE_APPLICATION_MEDIA, mDisplayContent,
"TypeApplicationMediaChild");
final WindowState mediaOverlayChild = createWindow(anyWindow, TYPE_APPLICATION_MEDIA_OVERLAY,
@@ -363,32 +356,7 @@
mDisplayContent.assignChildLayers(mTransaction);
- assertWindowHigher(anyWindow, mediaOverlayChild);
- assertWindowHigher(mediaOverlayChild, child);
- }
-
- @Test
- public void testDockedDividerPosition() throws Exception {
- final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED,
- ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent,
- "pinnedStackWindow");
- final WindowState splitScreenWindow = createWindowOnStack(null,
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
- mDisplayContent, "splitScreenWindow");
- final WindowState splitScreenSecondaryWindow = createWindowOnStack(null,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
- TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow");
- final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
- mDisplayContent, "assistantStackWindow");
- final WindowState dockedDividerWindow = createWindow(null, TYPE_DOCK_DIVIDER,
- mDisplayContent, "dockedDivider");
-
- mDisplayContent.assignChildLayers(mTransaction);
-
- assertWindowHigher(dockedDividerWindow, splitScreenWindow);
- assertWindowHigher(dockedDividerWindow, splitScreenSecondaryWindow);
- assertWindowHigher(assistantStackWindow, dockedDividerWindow);
- assertWindowHigher(pinnedStackWindow, dockedDividerWindow);
+ assertWindowLayerGreaterThan(mTransaction, anyWindow, mediaOverlayChild);
+ assertWindowLayerGreaterThan(mTransaction, mediaOverlayChild, child);
}
}