Merge "Disable media route scanning"
diff --git a/api/current.txt b/api/current.txt
index 1ffba87..3c3cfcc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16296,7 +16296,7 @@
public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult {
method public java.util.List<android.hardware.camera2.CaptureResult> getPartialResults();
- method public <T> T getPhysicalCameraKey(android.hardware.camera2.CaptureResult.Key<T>, java.lang.String);
+ method public java.util.Map<java.lang.String, android.hardware.camera2.CaptureResult> getPhysicalCameraResults();
}
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 70148d9..2d3b65a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2516,6 +2516,7 @@
method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
method public deprecated int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
method public int requestAudioFocus(android.media.AudioFocusRequest, android.media.audiopolicy.AudioPolicy);
+ method public void setFocusRequestResult(android.media.AudioFocusInfo, int, android.media.audiopolicy.AudioPolicy);
method public void unregisterAudioPolicyAsync(android.media.audiopolicy.AudioPolicy);
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
diff --git a/api/test-current.txt b/api/test-current.txt
index ac4ce3b..c30c056 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -211,6 +211,7 @@
method public abstract boolean isPermissionReviewModeEnabled();
field public static final java.lang.String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
field public static final java.lang.String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
+ field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
}
public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 7642aafa..17741a8 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -100,7 +100,8 @@
FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
- FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
+ FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration);
+ FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration);
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index c84a5b4..79b7d9c 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -35,9 +35,7 @@
// TODO: Get rid of bucketNumbers, and return to the original circular array method.
AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
- : mAlert(alert),
- mConfigKey(configKey),
- mNumOfPastBuckets(mAlert.num_buckets() - 1) {
+ : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) {
VLOG("AnomalyTracker() called");
if (mAlert.num_buckets() <= 0) {
ALOGE("Cannot create AnomalyTracker with %lld buckets",
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index ed570e7..e58c535 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -97,6 +97,7 @@
BootSequenceReported boot_sequence_reported = 57;
DaveyOccurred davey_occurred = 58;
OverlayStateChanged overlay_state_changed = 59;
+ ForegroundServiceStateChanged foreground_service_state_changed = 60;
// TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
}
@@ -998,6 +999,25 @@
optional State state = 4;
}
+/*
+ * Logs foreground service starts and stops.
+ * Note that this is not when a service starts or stops, but when it is
+ * considered foreground.
+ * Logged from
+ * //frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
+ */
+message ForegroundServiceStateChanged {
+ optional int32 uid = 1;
+ // package_name + "/" + class_name
+ optional string short_name = 2;
+
+ enum State {
+ ENTER = 1;
+ EXIT = 2;
+ }
+ optional State state = 3;
+}
+
/**
* Pulls bytes transferred via wifi (Sum of foreground and background usage).
*
@@ -1463,3 +1483,4 @@
message FullBatteryCapacity {
optional int32 capacity_uAh = 1;
}
+
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index ae4df3e..5b5b57b 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -170,7 +170,6 @@
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
mPastBuckets.clear();
- mStartTimeNs = mCurrentBucketStartTimeNs;
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
@@ -214,13 +213,11 @@
}
auto it = mCurrentSlicedCounter->find(eventKey);
-
if (it == mCurrentSlicedCounter->end()) {
// ===========GuardRail==============
if (hitGuardRailLocked(eventKey)) {
return;
}
-
// create a counter for the new key
(*mCurrentSlicedCounter)[eventKey] = 1;
} else {
@@ -228,10 +225,14 @@
auto& count = it->second;
count++;
}
-
for (auto& tracker : mAnomalyTrackers) {
+ int64_t countWholeBucket = mCurrentSlicedCounter->find(eventKey)->second;
+ auto prev = mCurrentFullCounters->find(eventKey);
+ if (prev != mCurrentFullCounters->end()) {
+ countWholeBucket += prev->second;
+ }
tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey,
- mCurrentSlicedCounter->find(eventKey)->second);
+ countWholeBucket);
}
VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.c_str(),
@@ -241,33 +242,65 @@
// When a new matched event comes in, we check if event falls into the current
// bucket. If not, flush the old counter to past buckets and initialize the new bucket.
void CountMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) {
- if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
+ uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
+ if (eventTimeNs < currentBucketEndTimeNs) {
return;
}
+ flushCurrentBucketLocked(eventTimeNs);
+ // Setup the bucket start time and number.
+ uint64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
+ mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+ VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
+ (long long)mCurrentBucketStartTimeNs);
+}
+
+void CountMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) {
+ uint64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
CountBucket info;
info.mBucketStartNs = mCurrentBucketStartTimeNs;
- info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ if (eventTimeNs < fullBucketEndTimeNs) {
+ info.mBucketEndNs = eventTimeNs;
+ } else {
+ info.mBucketEndNs = fullBucketEndTimeNs;
+ }
info.mBucketNum = mCurrentBucketNum;
for (const auto& counter : *mCurrentSlicedCounter) {
info.mCount = counter.second;
auto& bucketList = mPastBuckets[counter.first];
bucketList.push_back(info);
- VLOG("metric %lld, dump key value: %s -> %lld",
- (long long)mMetricId, counter.first.c_str(), (long long)counter.second);
+ VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId, counter.first.c_str(),
+ (long long)counter.second);
}
- for (auto& tracker : mAnomalyTrackers) {
- tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum);
+ // If we have finished a full bucket, then send this to anomaly tracker.
+ if (eventTimeNs > fullBucketEndTimeNs) {
+ // Accumulate partial buckets with current value and then send to anomaly tracker.
+ if (mCurrentFullCounters->size() > 0) {
+ for (const auto& keyValuePair : *mCurrentSlicedCounter) {
+ (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second;
+ }
+ for (auto& tracker : mAnomalyTrackers) {
+ tracker->addPastBucket(mCurrentFullCounters, mCurrentBucketNum);
+ }
+ mCurrentFullCounters = std::make_shared<DimToValMap>();
+ } else {
+ // Skip aggregating the partial buckets since there's no previous partial bucket.
+ for (auto& tracker : mAnomalyTrackers) {
+ tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum);
+ }
+ }
+ } else {
+ // Accumulate partial bucket.
+ for (const auto& keyValuePair : *mCurrentSlicedCounter) {
+ (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second;
+ }
}
- // Reset counters (do not clear, since the old one is still referenced in mAnomalyTrackers).
+ // Only resets the counters, but doesn't setup the times nor numbers.
+ // (Do not clear since the old one is still referenced in mAnomalyTrackers).
mCurrentSlicedCounter = std::make_shared<DimToValMap>();
- uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
- mCurrentBucketNum += numBucketsForward;
- VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
- (long long)mCurrentBucketStartTimeNs);
}
// Rough estimate of CountMetricProducer buffer stored. This number will be
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 8659d47..b06c77b 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -71,14 +71,20 @@
void dumpStatesLocked(FILE* out, bool verbose) const override{};
// Util function to flush the old packet.
- void flushIfNeededLocked(const uint64_t& newEventTime);
+ void flushIfNeededLocked(const uint64_t& newEventTime) override;
+
+ void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override;
// TODO: Add a lock to mPastBuckets.
std::unordered_map<MetricDimensionKey, std::vector<CountBucket>> mPastBuckets;
- // The current bucket.
+ // The current bucket (may be a partial bucket).
std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>();
+ // The sum of previous partial buckets in the current full bucket (excluding the current
+ // partial bucket). This is only updated while flushing the current bucket.
+ std::shared_ptr<DimToValMap> mCurrentFullCounters = std::make_shared<DimToValMap>();
+
static const size_t kBucketSize = sizeof(CountBucket{});
bool hitGuardRailLocked(const MetricDimensionKey& newKey);
@@ -87,6 +93,8 @@
FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
+ FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade);
+ FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index efbdae1..2400eba1 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -121,13 +121,13 @@
case DurationMetric_AggregationType_SUM:
return make_unique<OringDurationTracker>(
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
- mDimensionsInCondition, mNested,
- mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
+ mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
+ mStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
case DurationMetric_AggregationType_MAX_SPARSE:
return make_unique<MaxDurationTracker>(
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
- mDimensionsInCondition, mNested,
- mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
+ mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
+ mStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
}
}
@@ -252,17 +252,18 @@
protoOutput->end(protoToken);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
mPastBuckets.clear();
- mStartTimeNs = mCurrentBucketStartTimeNs;
}
-void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) {
- if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) {
+ uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
+
+ if (currentBucketEndTimeNs > eventTimeNs) {
return;
}
VLOG("flushing...........");
for (auto it = mCurrentSlicedDurationTrackerMap.begin();
it != mCurrentSlicedDurationTrackerMap.end();) {
- if (it->second->flushIfNeeded(eventTime, &mPastBuckets)) {
+ if (it->second->flushIfNeeded(eventTimeNs, &mPastBuckets)) {
VLOG("erase bucket for key %s", it->first.c_str());
it = mCurrentSlicedDurationTrackerMap.erase(it);
} else {
@@ -270,11 +271,23 @@
}
}
- int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
+ int numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
+ mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
mCurrentBucketNum += numBucketsForward;
}
+void DurationMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) {
+ for (auto it = mCurrentSlicedDurationTrackerMap.begin();
+ it != mCurrentSlicedDurationTrackerMap.end();) {
+ if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
+ VLOG("erase bucket for key %s", it->first.c_str());
+ it = mCurrentSlicedDurationTrackerMap.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
if (mCurrentSlicedDurationTrackerMap.size() == 0) {
return;
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 152e570..a496016 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -73,6 +73,8 @@
// Util function to flush the old packet.
void flushIfNeededLocked(const uint64_t& eventTime);
+ void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override;
+
const DurationMetric_AggregationType mAggregationType;
// Index of the SimpleAtomMatcher which defines the start.
@@ -112,6 +114,10 @@
FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition);
FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition);
+ FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade);
+ FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket);
+ FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade);
+ FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket);
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates);
};
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 820d591..a021e0a 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -118,7 +118,6 @@
reinterpret_cast<char*>(buffer.get()->data()), buffer.get()->size());
startNewProtoOutputStreamLocked();
- mStartTimeNs = dumpTimeNs;
}
void EventMetricProducer::onConditionChangedLocked(const bool conditionMet,
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index d6cb189..4190f00 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -191,10 +191,20 @@
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
mPastBuckets.clear();
- mStartTimeNs = mCurrentBucketStartTimeNs;
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
+void GaugeMetricProducer::pullLocked() {
+ vector<std::shared_ptr<LogEvent>> allData;
+ if (!mStatsPullerManager->Pull(mPullTagId, &allData)) {
+ ALOGE("Stats puller failed for tag: %d", mPullTagId);
+ return;
+ }
+ for (const auto& data : allData) {
+ onMatchedLogEventLocked(0, *data);
+ }
+}
+
void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet,
const uint64_t eventTime) {
VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
@@ -326,7 +336,6 @@
}
void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() {
- mCurrentSlicedBucketForAnomaly->clear();
status_t err = NO_ERROR;
for (const auto& slice : *mCurrentSlicedBucket) {
if (slice.second.empty() || slice.second.front().mFields->empty()) {
@@ -349,42 +358,57 @@
// if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside
// the GaugeMetricProducer while holding the lock.
void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) {
- if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
+ uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
+
+ if (eventTimeNs < currentBucketEndTimeNs) {
VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
(long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
return;
}
+ flushCurrentBucketLocked(eventTimeNs);
+
+ // Adjusts the bucket start and end times.
+ int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
+ mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+ VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
+ (long long)mCurrentBucketStartTimeNs);
+}
+
+void GaugeMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) {
+ uint64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
+
GaugeBucket info;
info.mBucketStartNs = mCurrentBucketStartTimeNs;
- info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ if (eventTimeNs < fullBucketEndTimeNs) {
+ info.mBucketEndNs = eventTimeNs;
+ } else {
+ info.mBucketEndNs = fullBucketEndTimeNs;
+ }
info.mBucketNum = mCurrentBucketNum;
for (const auto& slice : *mCurrentSlicedBucket) {
info.mGaugeAtoms = slice.second;
auto& bucketList = mPastBuckets[slice.first];
bucketList.push_back(info);
- VLOG("gauge metric %lld, dump key value: %s",
- (long long)mMetricId, slice.first.c_str());
+ VLOG("gauge metric %lld, dump key value: %s", (long long)mMetricId, slice.first.c_str());
}
- // Reset counters
+ // If we have anomaly trackers, we need to update the partial bucket values.
if (mAnomalyTrackers.size() > 0) {
updateCurrentSlicedBucketForAnomaly();
- for (auto& tracker : mAnomalyTrackers) {
- tracker->addPastBucket(mCurrentSlicedBucketForAnomaly, mCurrentBucketNum);
+
+ if (eventTimeNs > fullBucketEndTimeNs) {
+ // This is known to be a full bucket, so send this data to the anomaly tracker.
+ for (auto& tracker : mAnomalyTrackers) {
+ tracker->addPastBucket(mCurrentSlicedBucketForAnomaly, mCurrentBucketNum);
+ }
+ mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
}
}
- mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>();
-
- // Adjusts the bucket start time
- int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
- mCurrentBucketNum += numBucketsForward;
- VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
- (long long)mCurrentBucketStartTimeNs);
}
size_t GaugeMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 86d0ccd..d5d34be 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -62,6 +62,22 @@
// Handles when the pulled data arrives.
void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
+ // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
+ void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) override {
+ std::lock_guard<std::mutex> lock(mMutex);
+
+ if (eventTimeNs > getCurrentBucketEndTimeNs()) {
+ // Flush full buckets on the normal path up to the latest bucket boundary.
+ flushIfNeededLocked(eventTimeNs);
+ }
+ flushCurrentBucketLocked(eventTimeNs);
+ mCurrentBucketStartTimeNs = eventTimeNs;
+ if (mPullTagId != -1) {
+ pullLocked();
+ }
+ };
+
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -91,7 +107,11 @@
void dumpStatesLocked(FILE* out, bool verbose) const override{};
// Util function to flush the old packet.
- void flushIfNeededLocked(const uint64_t& eventTime);
+ void flushIfNeededLocked(const uint64_t& eventTime) override;
+
+ void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override;
+
+ void pullLocked();
std::shared_ptr<StatsPullerManager> mStatsPullerManager;
// tagId for pulled data. -1 if this is not pulled
@@ -101,13 +121,15 @@
// TODO: Add a lock to mPastBuckets.
std::unordered_map<MetricDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
- // The current bucket.
+ // The current partial bucket.
std::shared_ptr<DimToGaugeAtomsMap> mCurrentSlicedBucket;
- // The current bucket for anomaly detection.
+ // The current full bucket for anomaly detection. This is updated to the latest value seen for
+ // this slice (ie, for partial buckets, we use the last partial bucket in this full bucket).
std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly;
- // Translate Atom based bucket to single numeric value bucket for anomaly
+ // Translate Atom based bucket to single numeric value bucket for anomaly and updates the map
+ // for each slice with the latest value.
void updateCurrentSlicedBucketForAnomaly();
// Whitelist of fields to report. Empty means all are reported.
@@ -125,6 +147,8 @@
FRIEND_TEST(GaugeMetricProducerTest, TestWithCondition);
FRIEND_TEST(GaugeMetricProducerTest, TestNoCondition);
+ FRIEND_TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade);
+ FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithUpgrade);
FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
};
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 3b1498f..542dd8a 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -53,15 +53,32 @@
virtual ~MetricProducer(){};
- void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override{
+ /**
+ * Forces this metric to split into a partial bucket right now. If we're past a full bucket, we
+ * first call the standard flushing code to flush up to the latest full bucket. Then we call
+ * the flush again when the end timestamp is forced to be now, and then after flushing, update
+ * the start timestamp to be now.
+ */
+ void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) override {
+ std::lock_guard<std::mutex> lock(mMutex);
+
+ if (eventTimeNs > getCurrentBucketEndTimeNs()) {
+ // Flush full buckets on the normal path up to the latest bucket boundary.
+ flushIfNeededLocked(eventTimeNs);
+ }
+ // Now flush a partial bucket.
+ flushCurrentBucketLocked(eventTimeNs);
+ mCurrentBucketStartTimeNs = eventTimeNs;
+ // Don't update the current bucket number so that the anomaly tracker knows this bucket
+ // is a partial bucket and can merge it with the previous bucket.
+ };
+
+ void notifyAppRemoved(const uint64_t& eventTimeNs, const string& apk, const int uid) override{
// TODO: Implement me.
};
- void notifyAppRemoved(const string& apk, const int uid) override{
- // TODO: Implement me.
- };
-
- void onUidMapReceived() override{
+ void onUidMapReceived(const uint64_t& eventTimeNs) override{
// TODO: Implement me.
};
@@ -87,11 +104,12 @@
};
// Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
+ // This method clears all the past buckets.
void onDumpReport(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) {
std::lock_guard<std::mutex> lock(mMutex);
return onDumpReportLocked(dumpTimeNs, protoOutput);
}
-
+ // This method does not clear the past buckets.
void onDumpReport(const uint64_t dumpTimeNs, StatsLogReport* report) {
std::lock_guard<std::mutex> lock(mMutex);
return onDumpReportLocked(dumpTimeNs, report);
@@ -136,15 +154,43 @@
virtual size_t byteSizeLocked() const = 0;
virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0;
+ /**
+ * Flushes the current bucket if the eventTime is after the current bucket's end time.
+ */
+ virtual void flushIfNeededLocked(const uint64_t& eventTime){};
+
+ /**
+ * For metrics that aggregate (ie, every metric producer except for EventMetricProducer),
+ * we need to be able to flush the current buckets on demand (ie, end the current bucket and
+ * start new bucket). If this function is called when eventTimeNs is greater than the current
+ * bucket's end timestamp, than we flush up to the end of the latest full bucket; otherwise,
+ * we assume that we want to flush a partial bucket. The bucket start timestamp and bucket
+ * number are not changed by this function. This method should only be called by
+ * flushIfNeededLocked or the app upgrade handler; the caller MUST update the bucket timestamp
+ * and bucket number as needed.
+ */
+ virtual void flushCurrentBucketLocked(const uint64_t& eventTimeNs){};
+
+ // Convenience to compute the current bucket's end time, which is always aligned with the
+ // start time of the metric.
+ uint64_t getCurrentBucketEndTimeNs() {
+ return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
+ }
+
const int64_t mMetricId;
const ConfigKey mConfigKey;
- // The start time for the current in memory metrics data.
+ // The time when this metric producer was first created. The end time for the current bucket
+ // can be computed from this based on mCurrentBucketNum.
uint64_t mStartTimeNs;
+ // Start time may not be aligned with the start of statsd if there is an app upgrade in the
+ // middle of a bucket.
uint64_t mCurrentBucketStartTimeNs;
+ // Used by anomaly detector to track which bucket we are in. This is not sent with the produced
+ // report.
uint64_t mCurrentBucketNum;
int64_t mBucketSizeNs;
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 417145c..6573a89 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -28,6 +28,7 @@
#include "stats_util.h"
#include <log/logprint.h>
+#include <private/android_filesystem_config.h>
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_MESSAGE;
@@ -47,7 +48,7 @@
MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config,
const long timeBaseSec, sp<UidMap> uidMap)
- : mConfigKey(key), mUidMap(uidMap), mStatsdUid(getStatsdUid()) {
+ : mConfigKey(key), mUidMap(uidMap) {
mConfigValid =
initStatsdConfig(key, config, *uidMap, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
@@ -59,9 +60,9 @@
// mConfigValid = false;
// ALOGE("Log source white list is empty! This config won't get any data.");
- mAllowedUid.push_back(1000);
- mAllowedUid.push_back(0);
- mAllowedUid.push_back(mStatsdUid);
+ mAllowedUid.push_back(AID_ROOT);
+ mAllowedUid.push_back(AID_STATSD);
+ mAllowedUid.push_back(AID_SYSTEM);
mAllowedLogSources.insert(mAllowedUid.begin(), mAllowedUid.end());
} else {
for (const auto& source : config.allowed_log_source()) {
@@ -120,7 +121,8 @@
return mConfigValid;
}
-void MetricsManager::notifyAppUpgrade(const string& apk, const int uid, const int64_t version) {
+void MetricsManager::notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) {
// check if we care this package
if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
return;
@@ -130,7 +132,8 @@
initLogSourceWhiteList();
}
-void MetricsManager::notifyAppRemoved(const string& apk, const int uid) {
+void MetricsManager::notifyAppRemoved(const uint64_t& eventTimeNs, const string& apk,
+ const int uid) {
// check if we care this package
if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
return;
@@ -140,7 +143,7 @@
initLogSourceWhiteList();
}
-void MetricsManager::onUidMapReceived() {
+void MetricsManager::onUidMapReceived(const uint64_t& eventTimeNs) {
if (mAllowedPkg.size() == 0) {
return;
}
@@ -198,7 +201,7 @@
// unless that caller is statsd itself (statsd is allowed to spoof uids).
long appHookUid = event.GetLong(event.size()-2, &err);
int32_t loggerUid = event.GetUid();
- if (err != NO_ERROR || (loggerUid != appHookUid && loggerUid != mStatsdUid)) {
+ if (err != NO_ERROR || (loggerUid != appHookUid && loggerUid != AID_STATSD)) {
VLOG("AppHook has invalid uid: claimed %ld but caller is %d", appHookUid, loggerUid);
return;
}
@@ -333,16 +336,6 @@
return totalSize;
}
-int32_t MetricsManager::getStatsdUid() {
- auto suit = UidMap::sAidToUidMapping.find("AID_STATSD");
- if (suit != UidMap::sAidToUidMapping.end()) {
- return suit->second;
- } else {
- ALOGE("Statsd failed to find its own uid!");
- return -1;
- }
-}
-
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index a1220f9..9cae70a 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -51,11 +51,12 @@
void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
- void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override;
+ void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) override;
- void notifyAppRemoved(const string& apk, const int uid) override;
+ void notifyAppRemoved(const uint64_t& eventTimeNs, const string& apk, const int uid) override;
- void onUidMapReceived() override;
+ void onUidMapReceived(const uint64_t& eventTimeNs) override;
bool shouldAddUidMapListener() const {
return !mAllowedPkg.empty();
@@ -75,9 +76,6 @@
sp<UidMap> mUidMap;
- // The uid of statsd.
- const int32_t mStatsdUid;
-
bool mConfigValid = false;
// The uid log sources from StatsdConfig.
@@ -139,9 +137,6 @@
void initLogSourceWhiteList();
- // Fetches the uid of statsd from UidMap.
- static int32_t getStatsdUid();
-
// The metrics that don't need to be uploaded or even reported.
std::set<int64_t> mNoReportMetricIds;
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index c9cc7bb..31d9ff8 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -190,7 +190,6 @@
VLOG("metric %lld dump report now...", (long long)mMetricId);
mPastBuckets.clear();
- mStartTimeNs = mCurrentBucketStartTimeNs;
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
@@ -320,8 +319,13 @@
interval.sum += value;
}
+ long wholeBucketVal = interval.sum;
+ auto prev = mCurrentFullBucket.find(eventKey);
+ if (prev != mCurrentFullBucket.end()) {
+ wholeBucketVal += prev->second;
+ }
for (auto& tracker : mAnomalyTrackers) {
- tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, interval.sum);
+ tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, wholeBucketVal);
}
}
@@ -333,16 +337,39 @@
}
void ValueMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) {
- if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
+ uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
+
+ if (currentBucketEndTimeNs > eventTimeNs) {
VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
- (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
+ (long long)(currentBucketEndTimeNs));
return;
}
+
+ flushCurrentBucketLocked(eventTimeNs);
+
+ int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
+ mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+
+ if (numBucketsForward > 1) {
+ VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
+ }
+ VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
+ (long long)mCurrentBucketStartTimeNs);
+}
+
+void ValueMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) {
VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs,
(int)mCurrentSlicedBucket.size());
+ uint64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
+
ValueBucket info;
info.mBucketStartNs = mCurrentBucketStartTimeNs;
- info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ if (eventTimeNs < fullBucketEndTimeNs) {
+ info.mBucketEndNs = eventTimeNs;
+ } else {
+ info.mBucketEndNs = fullBucketEndTimeNs;
+ }
info.mBucketNum = mCurrentBucketNum;
int tainted = 0;
@@ -352,27 +379,42 @@
// it will auto create new vector of ValuebucketInfo if the key is not found.
auto& bucketList = mPastBuckets[slice.first];
bucketList.push_back(info);
-
- for (auto& tracker : mAnomalyTrackers) {
- if (tracker != nullptr) {
- tracker->addPastBucket(slice.first, info.mValue, info.mBucketNum);
- }
- }
}
VLOG("%d tainted pairs in the bucket", tainted);
+ if (eventTimeNs > fullBucketEndTimeNs) { // If full bucket, send to anomaly tracker.
+ // Accumulate partial buckets with current value and then send to anomaly tracker.
+ if (mCurrentFullBucket.size() > 0) {
+ for (const auto& slice : mCurrentSlicedBucket) {
+ mCurrentFullBucket[slice.first] += slice.second.sum;
+ }
+ for (const auto& slice : mCurrentFullBucket) {
+ for (auto& tracker : mAnomalyTrackers) {
+ if (tracker != nullptr) {
+ tracker->addPastBucket(slice.first, slice.second, mCurrentBucketNum);
+ }
+ }
+ }
+ mCurrentFullBucket.clear();
+ } else {
+ // Skip aggregating the partial buckets since there's no previous partial bucket.
+ for (const auto& slice : mCurrentSlicedBucket) {
+ for (auto& tracker : mAnomalyTrackers) {
+ if (tracker != nullptr) {
+ tracker->addPastBucket(slice.first, slice.second.sum, mCurrentBucketNum);
+ }
+ }
+ }
+ }
+ } else {
+ // Accumulate partial bucket.
+ for (const auto& slice : mCurrentSlicedBucket) {
+ mCurrentFullBucket[slice.first] += slice.second.sum;
+ }
+ }
+
// Reset counters
mCurrentSlicedBucket.clear();
-
- int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
- mCurrentBucketNum += numBucketsForward;
-
- if (numBucketsForward > 1) {
- VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
- }
- VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
- (long long)mCurrentBucketStartTimeNs);
}
size_t ValueMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 121ec7d..bf5b7df 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -47,6 +47,39 @@
void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
+ // ValueMetric needs special logic if it's a pulled atom.
+ void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) override {
+ std::lock_guard<std::mutex> lock(mMutex);
+
+ if (mPullTagId != -1) {
+ vector<shared_ptr<LogEvent>> allData;
+ mStatsPullerManager->Pull(mPullTagId, &allData);
+ if (allData.size() == 0) {
+ // This shouldn't happen since this valuemetric is not useful now.
+ }
+
+ // Pretend the pulled data occurs right before the app upgrade event.
+ mCondition = false;
+ for (const auto& data : allData) {
+ data->setTimestampNs(eventTimeNs - 1);
+ onMatchedLogEventLocked(0, *data);
+ }
+
+ flushCurrentBucketLocked(eventTimeNs);
+ mCurrentBucketStartTimeNs = eventTimeNs;
+
+ mCondition = true;
+ for (const auto& data : allData) {
+ data->setTimestampNs(eventTimeNs);
+ onMatchedLogEventLocked(0, *data);
+ }
+ } else { // For pushed value metric, we simply flush and reset the current bucket start.
+ flushCurrentBucketLocked(eventTimeNs);
+ mCurrentBucketStartTimeNs = eventTimeNs;
+ }
+ };
+
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -70,7 +103,9 @@
void dumpStatesLocked(FILE* out, bool verbose) const override{};
// Util function to flush the old packet.
- void flushIfNeededLocked(const uint64_t& eventTime);
+ void flushIfNeededLocked(const uint64_t& eventTime) override;
+
+ void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override;
const FieldMatcher mValueField;
@@ -101,6 +136,8 @@
std::unordered_map<MetricDimensionKey, Interval> mCurrentSlicedBucket;
+ std::unordered_map<MetricDimensionKey, long> mCurrentFullBucket;
+
// Save the past buckets and we can clear when the StatsLogReport is dumped.
// TODO: Add a lock to mPastBuckets.
std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>> mPastBuckets;
@@ -114,6 +151,8 @@
FRIEND_TEST(ValueMetricProducerTest, TestNonDimensionalEvents);
FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition);
+ FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade);
+ FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade);
FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition);
FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection);
};
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 45735a8..356a81c 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -63,7 +63,8 @@
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,
+ uint64_t currentBucketStartNs, uint64_t currentBucketNum, uint64_t startTimeNs,
+ uint64_t bucketSizeNs, bool conditionSliced,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
: mConfigKey(key),
mTrackerId(id),
@@ -75,7 +76,9 @@
mNested(nesting),
mCurrentBucketStartTimeNs(currentBucketStartNs),
mDuration(0),
- mCurrentBucketNum(0),
+ mDurationFullBucket(0),
+ mCurrentBucketNum(currentBucketNum),
+ mStartTimeNs(startTimeNs),
mConditionSliced(conditionSliced),
mAnomalyTrackers(anomalyTrackers){};
@@ -98,6 +101,12 @@
uint64_t timestampNs,
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) = 0;
+ // Should only be called during an app upgrade or from this tracker's flushIfNeeded. If from
+ // an app upgrade, we assume that we're trying to form a partial bucket.
+ virtual bool flushCurrentBucket(
+ const uint64_t& eventTimeNs,
+ std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) = 0;
+
// Predict the anomaly timestamp given the current status.
virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
const uint64_t currentTimestamp) const = 0;
@@ -153,6 +162,13 @@
}
}
}
+
+ // Convenience to compute the current bucket's end time, which is always aligned with the
+ // start time of the metric.
+ uint64_t getCurrentBucketEndTimeNs() {
+ return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
+ }
+
// A reference to the DurationMetricProducer's config key.
const ConfigKey& mConfigKey;
@@ -172,10 +188,14 @@
uint64_t mCurrentBucketStartTimeNs;
- int64_t mDuration; // current recorded duration result
+ int64_t mDuration; // current recorded duration result (for partial bucket)
+
+ int64_t mDurationFullBucket; // Sum of past partial buckets in current full bucket.
uint64_t mCurrentBucketNum;
+ const uint64_t mStartTimeNs;
+
const bool mConditionSliced;
std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index db7dea4..c3bafc6 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -28,11 +28,13 @@
const MetricDimensionKey& eventKey,
sp<ConditionWizard> wizard, int conditionIndex,
const FieldMatcher& dimensionInCondition, bool nesting,
- uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+ uint64_t currentBucketStartNs, uint64_t currentBucketNum,
+ uint64_t startTimeNs, uint64_t bucketSizeNs,
bool conditionSliced,
const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
: DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
- currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers) {
+ currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
+ conditionSliced, anomalyTrackers) {
}
unique_ptr<DurationTracker> MaxDurationTracker::clone(const uint64_t eventTime) {
@@ -102,7 +104,6 @@
void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
bool forceStop) {
- declareAnomalyIfAlarmExpired(eventTime);
VLOG("MaxDuration: key %s stop", key.c_str());
if (mInfos.find(key) == mInfos.end()) {
// we didn't see a start event before. do nothing.
@@ -122,7 +123,7 @@
VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(),
(long long)duration.lastStartTime, (long long)eventTime,
(long long)durationTime);
- duration.lastDuration = duration.lastDuration + durationTime;
+ duration.lastDuration += durationTime;
VLOG(" record duration: %lld ", (long long)duration.lastDuration);
}
break;
@@ -138,7 +139,6 @@
if (duration.lastDuration > mDuration) {
mDuration = duration.lastDuration;
- detectAndDeclareAnomaly(eventTime, mCurrentBucketNum, mDuration);
VLOG("Max: new max duration: %lld", (long long)mDuration);
}
// Once an atom duration ends, we erase it. Next time, if we see another atom event with the
@@ -158,88 +158,67 @@
}
}
-bool MaxDurationTracker::flushIfNeeded(
- uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
- if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
- return false;
- }
-
+bool MaxDurationTracker::flushCurrentBucket(
+ const uint64_t& eventTimeNs,
+ std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) {
VLOG("MaxDurationTracker flushing.....");
// adjust the bucket start time
- int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+ int numBucketsForward = 0;
+ uint64_t fullBucketEnd = getCurrentBucketEndTimeNs();
+ uint64_t currentBucketEndTimeNs;
+ if (eventTimeNs >= fullBucketEnd) {
+ numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs;
+ currentBucketEndTimeNs = fullBucketEnd;
+ } else {
+ // This must be a partial bucket.
+ currentBucketEndTimeNs = eventTimeNs;
+ }
- uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
-
- DurationBucket info;
- info.mBucketStartNs = mCurrentBucketStartTimeNs;
- info.mBucketEndNs = endTime;
- info.mBucketNum = mCurrentBucketNum;
-
- uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
- mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
-
- bool hasOnGoingStartedEvent = false; // a kStarted event last across bucket boundaries.
bool hasPendingEvent =
false; // has either a kStarted or kPaused event across bucket boundaries
- // meaning we need to carry them over to the new bucket.
+ // meaning we need to carry them over to the new bucket.
for (auto it = mInfos.begin(); it != mInfos.end(); ++it) {
int64_t finalDuration = it->second.lastDuration;
- if (it->second.state == kStarted) {
- // the event is still on-going, duration needs to be updated.
- // |..lastDurationTime_recorded...last_start -----|bucket_end. We need to record the
- // duration between lastStartTime and bucketEnd.
- int64_t durationTime = endTime - it->second.lastStartTime;
-
- finalDuration += durationTime;
- VLOG(" unrecorded %lld -> %lld", (long long)(durationTime), (long long)finalDuration);
- // if the event is still on-going, we need to fill the buckets between prev_bucket and
- // now_bucket. |prev_bucket|...|..|...|now_bucket|
- hasOnGoingStartedEvent = true;
- }
-
- if (finalDuration > mDuration) {
- mDuration = finalDuration;
- }
-
if (it->second.state == DurationState::kStopped) {
// No need to keep buckets for events that were stopped before.
mInfos.erase(it);
} else {
hasPendingEvent = true;
- // for kPaused, and kStarted event, we will keep track of them, and reset the start time
- // and duration.
- it->second.lastStartTime = mCurrentBucketStartTimeNs;
- it->second.lastDuration = 0;
}
}
+ // mDuration is updated in noteStop to the maximum duration that ended in the current bucket.
if (mDuration != 0) {
+ DurationBucket info;
+ info.mBucketStartNs = mCurrentBucketStartTimeNs;
+ info.mBucketEndNs = currentBucketEndTimeNs;
+ info.mBucketNum = mCurrentBucketNum;
info.mDuration = mDuration;
(*output)[mEventKey].push_back(info);
- addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
VLOG(" final duration for last bucket: %lld", (long long)mDuration);
}
- mDuration = 0;
- if (hasOnGoingStartedEvent) {
- for (int i = 1; i < numBucketsForward; i++) {
- DurationBucket info;
- info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
- info.mBucketEndNs = endTime + mBucketSizeNs * i;
- info.mBucketNum = mCurrentBucketNum + i;
- info.mDuration = mBucketSizeNs;
- (*output)[mEventKey].push_back(info);
- addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
- VLOG(" filling gap bucket with duration %lld", (long long)mBucketSizeNs);
- }
+ if (numBucketsForward > 0) {
+ mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+ } else { // We must be forming a partial bucket.
+ mCurrentBucketStartTimeNs = eventTimeNs;
}
- mCurrentBucketNum += numBucketsForward;
+ mDuration = 0;
// If this tracker has no pending events, tell owner to remove.
return !hasPendingEvent;
}
+bool MaxDurationTracker::flushIfNeeded(
+ uint64_t eventTimeNs, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
+ if (eventTimeNs < getCurrentBucketEndTimeNs()) {
+ return false;
+ }
+ return flushCurrentBucket(eventTimeNs, output);
+}
+
void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
// Now for each of the on-going event, check if the condition has changed for them.
for (auto& pair : mInfos) {
@@ -267,7 +246,6 @@
void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
const uint64_t timestamp) {
- declareAnomalyIfAlarmExpired(timestamp);
auto it = mInfos.find(key);
if (it == mInfos.end()) {
return;
@@ -297,7 +275,6 @@
}
if (it->second.lastDuration > mDuration) {
mDuration = it->second.lastDuration;
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
}
}
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index 4d32a06..fba4119 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -28,10 +28,11 @@
// they stop or bucket expires.
class MaxDurationTracker : public DurationTracker {
public:
- MaxDurationTracker(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,
+ MaxDurationTracker(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 currentBucketNum,
+ uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
MaxDurationTracker(const MaxDurationTracker& tracker) = default;
@@ -47,6 +48,9 @@
bool flushIfNeeded(
uint64_t timestampNs,
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
+ bool flushCurrentBucket(
+ const uint64_t& eventTimeNs,
+ std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>*) override;
void onSlicedConditionMayChange(const uint64_t timestamp) override;
void onConditionChanged(bool condition, const uint64_t timestamp) override;
@@ -68,7 +72,6 @@
FRIEND_TEST(MaxDurationTrackerTest, TestCrossBucketBoundary);
FRIEND_TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition);
FRIEND_TEST(MaxDurationTrackerTest, TestStopAll);
- FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 0feae36..85f7b7c 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -26,12 +26,13 @@
OringDurationTracker::OringDurationTracker(
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,
+ sp<ConditionWizard> wizard, int conditionIndex, const FieldMatcher& dimensionInCondition,
+ bool nesting, uint64_t currentBucketStartNs, uint64_t currentBucketNum,
+ uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
: DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
- currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers),
+ currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
+ conditionSliced, anomalyTrackers),
mStarted(),
mPaused() {
mLastStartTime = 0;
@@ -100,7 +101,7 @@
}
if (mStarted.empty()) {
mDuration += (timestamp - mLastStartTime);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
(long long)mDuration);
}
@@ -125,7 +126,7 @@
mDuration += (timestamp - mLastStartTime);
VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
(long long)mDuration);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
}
stopAnomalyAlarm();
@@ -134,51 +135,83 @@
mConditionKeyMap.clear();
}
-bool OringDurationTracker::flushIfNeeded(
- uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
- if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) {
- return false;
- }
+bool OringDurationTracker::flushCurrentBucket(
+ const uint64_t& eventTimeNs,
+ std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) {
VLOG("OringDurationTracker Flushing.............");
- // adjust the bucket start time
- int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- DurationBucket current_info;
- current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
- current_info.mBucketEndNs = current_info.mBucketStartNs + mBucketSizeNs;
- current_info.mBucketNum = mCurrentBucketNum;
+
+ // Note that we have to mimic the bucket time changes we do in the
+ // MetricProducer#notifyAppUpgrade.
+
+ int numBucketsForward = 0;
+ uint64_t fullBucketEnd = getCurrentBucketEndTimeNs();
+ uint64_t currentBucketEndTimeNs;
+
+ if (eventTimeNs >= fullBucketEnd) {
+ numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs;
+ currentBucketEndTimeNs = fullBucketEnd;
+ } else {
+ // This must be a partial bucket.
+ currentBucketEndTimeNs = eventTimeNs;
+ }
+
// Process the current bucket.
if (mStarted.size() > 0) {
- mDuration += (current_info.mBucketEndNs - mLastStartTime);
+ mDuration += (currentBucketEndTimeNs - mLastStartTime);
}
if (mDuration > 0) {
+ DurationBucket current_info;
+ current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
+ current_info.mBucketEndNs = currentBucketEndTimeNs;
+ current_info.mBucketNum = mCurrentBucketNum;
current_info.mDuration = mDuration;
(*output)[mEventKey].push_back(current_info);
- addPastBucketToAnomalyTrackers(current_info.mDuration, current_info.mBucketNum);
+ mDurationFullBucket += mDuration;
+ if (eventTimeNs > fullBucketEnd) {
+ // End of full bucket, can send to anomaly tracker now.
+ addPastBucketToAnomalyTrackers(mDurationFullBucket, current_info.mBucketNum);
+ mDurationFullBucket = 0;
+ }
VLOG(" duration: %lld", (long long)current_info.mDuration);
}
if (mStarted.size() > 0) {
for (int i = 1; i < numBucketsForward; i++) {
DurationBucket info;
- info.mBucketStartNs = mCurrentBucketStartTimeNs + mBucketSizeNs * i;
+ info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1);
info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
info.mBucketNum = mCurrentBucketNum + i;
info.mDuration = mBucketSizeNs;
(*output)[mEventKey].push_back(info);
+ // Safe to send these buckets to anomaly tracker since they must be full buckets.
+ // If it's a partial bucket, numBucketsForward would be 0.
addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
VLOG(" add filling bucket with duration %lld", (long long)info.mDuration);
}
}
- mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
- mCurrentBucketNum += numBucketsForward;
- mLastStartTime = mCurrentBucketStartTimeNs;
mDuration = 0;
+ if (numBucketsForward > 0) {
+ mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+ } else { // We must be forming a partial bucket.
+ mCurrentBucketStartTimeNs = eventTimeNs;
+ }
+ mLastStartTime = mCurrentBucketStartTimeNs;
+
// if all stopped, then tell owner it's safe to remove this tracker.
return mStarted.empty() && mPaused.empty();
}
+bool OringDurationTracker::flushIfNeeded(
+ uint64_t eventTimeNs, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
+ if (eventTimeNs < getCurrentBucketEndTimeNs()) {
+ return false;
+ }
+ return flushCurrentBucket(eventTimeNs, output);
+}
+
void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
declareAnomalyIfAlarmExpired(timestamp);
vector<pair<HashableDimensionKey, int>> startedToPaused;
@@ -211,7 +244,7 @@
mDuration += (timestamp - mLastStartTime);
VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
(long long)mDuration);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
}
}
@@ -275,7 +308,7 @@
mDuration += (timestamp - mLastStartTime);
mPaused.insert(mStarted.begin(), mStarted.end());
mStarted.clear();
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
}
}
if (mStarted.empty()) {
@@ -298,7 +331,7 @@
// As we move into the future, old buckets get overwritten (so their old data is erased).
// Sum of past durations. Will change as we overwrite old buckets.
- int64_t pastNs = mDuration;
+ int64_t pastNs = mDuration + mDurationFullBucket;
pastNs += anomalyTracker.getSumOverPastBuckets(mEventKey);
// How much of the threshold is still unaccounted after considering pastNs.
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 75b5a81..73e50e0 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -30,7 +30,8 @@
OringDurationTracker(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,
+ uint64_t currentBucketStartNs, uint64_t currentBucketNum,
+ uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
OringDurationTracker(const OringDurationTracker& tracker) = default;
@@ -46,6 +47,9 @@
void onSlicedConditionMayChange(const uint64_t timestamp) override;
void onConditionChanged(bool condition, const uint64_t timestamp) override;
+ bool flushCurrentBucket(
+ const uint64_t& eventTimeNs,
+ std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
bool flushIfNeeded(
uint64_t timestampNs,
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
diff --git a/cmds/statsd/src/packages/PackageInfoListener.h b/cmds/statsd/src/packages/PackageInfoListener.h
index df29eb0..03cb364 100644
--- a/cmds/statsd/src/packages/PackageInfoListener.h
+++ b/cmds/statsd/src/packages/PackageInfoListener.h
@@ -28,13 +28,15 @@
public:
// Uid map will notify this listener that the app with apk name and uid has been upgraded to
// the specified version.
- virtual void notifyAppUpgrade(const std::string& apk, const int uid, const int64_t version) = 0;
+ virtual void notifyAppUpgrade(const uint64_t& eventTimeNs, const std::string& apk,
+ const int uid, const int64_t version) = 0;
// Notify interested listeners that the given apk and uid combination no longer exits.
- virtual void notifyAppRemoved(const std::string& apk, const int uid) = 0;
+ virtual void notifyAppRemoved(const uint64_t& eventTimeNs, const std::string& apk,
+ const int uid) = 0;
// Notify the listener that the UidMap snapshot is available.
- virtual void onUidMapReceived() = 0;
+ virtual void onUidMapReceived(const uint64_t& eventTimeNs) = 0;
};
} // namespace statsd
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 91279661..691423e 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -119,7 +119,7 @@
for (auto weakPtr : broadcastList) {
auto strongPtr = weakPtr.promote();
if (strongPtr != NULL) {
- strongPtr->onUidMapReceived();
+ strongPtr->onUidMapReceived(timestamp);
}
}
}
@@ -166,7 +166,7 @@
for (auto weakPtr : broadcastList) {
auto strongPtr = weakPtr.promote();
if (strongPtr != NULL) {
- strongPtr->notifyAppUpgrade(appName, uid, versionCode);
+ strongPtr->notifyAppUpgrade(timestamp, appName, uid, versionCode);
}
}
}
@@ -239,7 +239,7 @@
for (auto weakPtr : broadcastList) {
auto strongPtr = weakPtr.promote();
if (strongPtr != NULL) {
- strongPtr->notifyAppRemoved(app, uid);
+ strongPtr->notifyAppRemoved(timestamp, app, uid);
}
}
}
@@ -316,13 +316,13 @@
mLastUpdatePerConfigKey[key] = timestamp;
int64_t newMin = getMinimumTimestampNs();
- if (newMin > prevMin) {
+ if (newMin > prevMin) { // Delete anything possible now that the minimum has moved forward.
int64_t cutoff_nanos = newMin;
auto snapshots = mOutput.mutable_snapshots();
auto it_snapshots = snapshots->cbegin();
while (it_snapshots != snapshots->cend()) {
if (it_snapshots->timestamp_nanos() < cutoff_nanos) {
- // it_snapshots now points to the following element.
+ // it_snapshots points to the following element after erasing.
it_snapshots = snapshots->erase(it_snapshots);
} else {
++it_snapshots;
@@ -332,7 +332,7 @@
auto it_deltas = deltas->cbegin();
while (it_deltas != deltas->cend()) {
if (it_deltas->timestamp_nanos() < cutoff_nanos) {
- // it_deltas now points to the following element.
+ // it_snapshots points to the following element after erasing.
it_deltas = deltas->erase(it_deltas);
} else {
++it_deltas;
diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
index 1186a16..a99dbe8 100644
--- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
@@ -40,9 +40,11 @@
auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
// The predicate is dimensioning by any attribution node and both by uid and tag.
- *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
- CreateAttributionUidAndTagDimensions(
+ FieldMatcher dimensions = CreateAttributionUidAndTagDimensions(
android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST});
+ // Also slice by the wakelock tag
+ dimensions.add_child()->set_field(3); // The wakelock tag is set in field 3 of the wakelock.
+ *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions;
*config.add_predicate() = holdingWakelockPredicate;
auto durationMetric = config.add_duration_metric();
@@ -58,133 +60,198 @@
return config;
}
-} // namespace
+std::vector<AttributionNode> attributions1 = {CreateAttribution(111, "App1"),
+ CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
-TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) {
- ConfigKey cfgKey;
- for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) {
- auto config = CreateStatsdConfig(aggregationType);
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t bucketSizeNs =
+std::vector<AttributionNode> attributions2 = {CreateAttribution(111, "App2"),
+ CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+/*
+Events:
+Screen off is met from (200ns,1 min+500ns].
+Acquire event for wl1 from 2ns to 1min+2ns
+Acquire event for wl2 from 1min-10ns to 2min-15ns
+*/
+void FeedEvents(StatsdConfig config, sp<StatsLogProcessor> processor) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_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());
-
- auto screenTurnedOnEvent =
- CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
- bucketStartTimeNs + 1);
- auto screenTurnedOffEvent =
- CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
- bucketStartTimeNs + 200);
- auto screenTurnedOnEvent2 =
+ auto screenTurnedOnEvent = CreateScreenStateChangedEvent(
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 1);
+ auto screenTurnedOffEvent = CreateScreenStateChangedEvent(
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 200);
+ auto screenTurnedOnEvent2 =
CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
bucketStartTimeNs + bucketSizeNs + 500);
- std::vector<AttributionNode> attributions1 =
- {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(222, "GMSCoreModule2")};
+ auto acquireEvent1 = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2);
+ auto releaseEvent1 =
+ CreateReleaseWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2);
+ auto acquireEvent2 =
+ CreateAcquireWakelockEvent(attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10);
+ auto releaseEvent2 = CreateReleaseWakelockEvent(attributions2, "wl2",
+ bucketStartTimeNs + 2 * bucketSizeNs - 15);
- std::vector<AttributionNode> attributions2 =
- {CreateAttribution(111, "App2"), CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(222, "GMSCoreModule2")};
+ std::vector<std::unique_ptr<LogEvent>> events;
- auto acquireEvent1 = CreateAcquireWakelockEvent(
- attributions1, "wl1", bucketStartTimeNs + 2);
- auto acquireEvent2 = CreateAcquireWakelockEvent(
- attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10);
+ events.push_back(std::move(screenTurnedOnEvent));
+ events.push_back(std::move(screenTurnedOffEvent));
+ events.push_back(std::move(screenTurnedOnEvent2));
+ events.push_back(std::move(acquireEvent1));
+ events.push_back(std::move(acquireEvent2));
+ events.push_back(std::move(releaseEvent1));
+ events.push_back(std::move(releaseEvent2));
- auto releaseEvent1 = CreateReleaseWakelockEvent(
- attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2);
- auto releaseEvent2 = CreateReleaseWakelockEvent(
- attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15);
+ sortLogEventsByTimestamp(&events);
-
- std::vector<std::unique_ptr<LogEvent>> events;
-
- events.push_back(std::move(screenTurnedOnEvent));
- events.push_back(std::move(screenTurnedOffEvent));
- events.push_back(std::move(screenTurnedOnEvent2));
- events.push_back(std::move(acquireEvent1));
- events.push_back(std::move(acquireEvent2));
- events.push_back(std::move(releaseEvent1));
- events.push_back(std::move(releaseEvent2));
-
- 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);
- // Only 1 dimension output. The tag dimension in the predicate has been aggregated.
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
-
- auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
- // Validate dimension value.
- ValidateAttributionUidDimension(
- data.dimensions_in_what(),
- android::util::WAKELOCK_STATE_CHANGED, 111);
- // Validate bucket info.
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
- data = reports.reports(0).metrics(0).duration_metrics().data(0);
- // The wakelock holding interval starts from the screen off event and to the end of the 1st
- // bucket.
- EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200);
-
- reports.Clear();
- processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
- EXPECT_EQ(reports.reports_size(), 1);
- EXPECT_EQ(reports.reports(0).metrics_size(), 1);
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
- // Dump the report after the end of 2nd bucket.
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
- data = reports.reports(0).metrics(0).duration_metrics().data(0);
- // Validate dimension value.
- ValidateAttributionUidDimension(
- data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 111);
- // Two output buckets.
- // The wakelock holding interval in the 1st bucket starts from the screen off event and to
- // the end of the 1st bucket.
- EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(),
- bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200));
- // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and
- // ends at the second screen on event.
- EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL);
-
- events.clear();
- events.push_back(CreateScreenStateChangedEvent(
- android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
- bucketStartTimeNs + 2 * bucketSizeNs + 90));
- events.push_back(CreateAcquireWakelockEvent(
- attributions1, "wl3", bucketStartTimeNs + 2 * bucketSizeNs + 100));
- events.push_back(CreateReleaseWakelockEvent(
- attributions1, "wl3", bucketStartTimeNs + 5 * bucketSizeNs + 100));
- sortLogEventsByTimestamp(&events);
- for (const auto& event : events) {
- processor->OnLogEvent(event.get());
- }
- reports.Clear();
- processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports);
- EXPECT_EQ(reports.reports_size(), 1);
- EXPECT_EQ(reports.reports(0).metrics_size(), 1);
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6);
- data = reports.reports(0).metrics(0).duration_metrics().data(0);
- ValidateAttributionUidDimension(
- data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 111);
- // The last wakelock holding spans 4 buckets.
- EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100);
- EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs);
- EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs);
- EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL);
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
}
}
+} // namespace
+
+TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration) {
+ ConfigKey cfgKey;
+ auto config = CreateStatsdConfig(DurationMetric::SUM);
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_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());
+ FeedEvents(config, processor);
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports);
+
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ // Only 1 dimension output. The tag dimension in the predicate has been aggregated.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+
+ auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ // Validate dimension value.
+ ValidateAttributionUidDimension(data.dimensions_in_what(),
+ android::util::WAKELOCK_STATE_CHANGED, 111);
+ // Validate bucket info.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
+ data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ // The wakelock holding interval starts from the screen off event and to the end of the 1st
+ // bucket.
+ EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200);
+
+ reports.Clear();
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+ // Dump the report after the end of 2nd bucket.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
+ data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ // Validate dimension value.
+ ValidateAttributionUidDimension(data.dimensions_in_what(),
+ android::util::WAKELOCK_STATE_CHANGED, 111);
+ // Two output buckets.
+ // The wakelock holding interval in the 1st bucket starts from the screen off event and to
+ // the end of the 1st bucket.
+ EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(),
+ bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200));
+ // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and
+ // ends at the second screen on event.
+ EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL);
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(
+ CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 2 * bucketSizeNs + 90));
+ events.push_back(CreateAcquireWakelockEvent(attributions1, "wl3",
+ bucketStartTimeNs + 2 * bucketSizeNs + 100));
+ events.push_back(CreateReleaseWakelockEvent(attributions1, "wl3",
+ bucketStartTimeNs + 5 * bucketSizeNs + 100));
+ sortLogEventsByTimestamp(&events);
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+ reports.Clear();
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6);
+ data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ ValidateAttributionUidDimension(data.dimensions_in_what(),
+ android::util::WAKELOCK_STATE_CHANGED, 111);
+ // The last wakelock holding spans 4 buckets.
+ EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100);
+ EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs);
+ EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs);
+ EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL);
+}
+
+TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration) {
+ ConfigKey cfgKey;
+ auto config = CreateStatsdConfig(DurationMetric::MAX_SPARSE);
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_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());
+ FeedEvents(config, processor);
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports);
+
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ // Nothing has ended in the first bucket.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 0);
+
+ reports.Clear();
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+ // Dump the report after the end of 2nd bucket. One dimension with one bucket.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
+ auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ // Validate dimension value.
+ ValidateAttributionUidDimension(data.dimensions_in_what(),
+ android::util::WAKELOCK_STATE_CHANGED, 111);
+ // The max is acquire event for wl1 to screen off start.
+ EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs + 2 - 200);
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(
+ CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 2 * bucketSizeNs + 90));
+ events.push_back(CreateAcquireWakelockEvent(attributions1, "wl3",
+ bucketStartTimeNs + 2 * bucketSizeNs + 100));
+ events.push_back(CreateReleaseWakelockEvent(attributions1, "wl3",
+ bucketStartTimeNs + 5 * bucketSizeNs + 100));
+ sortLogEventsByTimestamp(&events);
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+ reports.Clear();
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
+ data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ ValidateAttributionUidDimension(data.dimensions_in_what(),
+ android::util::WAKELOCK_STATE_CHANGED, 111);
+ // The last wakelock holding spans 4 buckets.
+ EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 3 * bucketSizeNs);
+ EXPECT_EQ((unsigned long long)data.bucket_info(1).start_bucket_nanos(),
+ bucketStartTimeNs + 5 * bucketSizeNs);
+ EXPECT_EQ((unsigned long long)data.bucket_info(1).end_bucket_nanos(),
+ bucketStartTimeNs + 6 * bucketSizeNs);
+}
+
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 50b3532..87a1079 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -191,6 +191,117 @@
EXPECT_EQ(1LL, bucketInfo.mCount);
}
+TEST(CountMetricProducerTest, TestEventWithAppUpgrade) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+
+ int tagId = 1;
+ int conditionTagId = 2;
+
+ CountMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ Alert alert;
+ alert.set_num_buckets(3);
+ alert.set_trigger_if_sum_gt(2);
+ LogEvent event1(tagId, bucketStartTimeNs + 1);
+ event1.write("111"); // uid
+ event1.init();
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard,
+ bucketStartTimeNs);
+ sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert);
+ EXPECT_TRUE(anomalyTracker != nullptr);
+
+ // Bucket is flushed yet.
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
+ EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+ EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ // App upgrade forces bucket flush.
+ // Check that there's a past bucket and the bucket end is not adjusted.
+ countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((long long)bucketStartTimeNs,
+ countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+ EXPECT_EQ((long long)eventUpgradeTimeNs,
+ countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
+ EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+ // Anomaly tracker only contains full buckets.
+ EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ uint64_t lastEndTimeNs = countProducer.getCurrentBucketEndTimeNs();
+ // Next event occurs in same bucket as partial bucket created.
+ LogEvent event2(tagId, bucketStartTimeNs + 59 * NS_PER_SEC + 10);
+ event2.write("222"); // uid
+ event2.init();
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
+ EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ // Third event in following bucket.
+ LogEvent event3(tagId, bucketStartTimeNs + 62 * NS_PER_SEC + 10);
+ event3.write("333"); // uid
+ event3.init();
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
+ EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+}
+
+TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+
+ int tagId = 1;
+ int conditionTagId = 2;
+
+ CountMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ LogEvent event1(tagId, bucketStartTimeNs + 1);
+ event1.write("111"); // uid
+ event1.init();
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard,
+ bucketStartTimeNs);
+
+ // Bucket is flushed yet.
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
+ EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
+ // App upgrade forces bucket flush.
+ // Check that there's a past bucket and the bucket end is not adjusted.
+ countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((int64_t)bucketStartTimeNs,
+ countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
+ (uint64_t)countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
+ EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+
+ // Next event occurs in same bucket as partial bucket created.
+ LogEvent event2(tagId, bucketStartTimeNs + 70 * NS_PER_SEC + 10);
+ event2.write("222"); // uid
+ event2.init();
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
+ EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+
+ // Third event in following bucket.
+ LogEvent event3(tagId, bucketStartTimeNs + 121 * NS_PER_SEC + 10);
+ event3.write("333"); // uid
+ event3.init();
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
+ EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((int64_t)eventUpgradeTimeNs,
+ countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+ (uint64_t)countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs);
+}
+
TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) {
Alert alert;
alert.set_id(11);
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index c9fe252..23e15f7 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -116,6 +116,231 @@
EXPECT_EQ(1ULL, buckets2[0].mDuration);
}
+TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) {
+ /**
+ * The duration starts from the first bucket, through the two partial buckets (10-70sec),
+ * another bucket, and ends at the beginning of the next full bucket.
+ * Expected buckets:
+ * - [10,25]: 14 secs
+ * - [25,70]: All 45 secs
+ * - [70,130]: All 60 secs
+ * - [130, 210]: Only 5 secs (event ended at 135sec)
+ */
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+ uint64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
+ uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+
+ int tagId = 1;
+
+ DurationMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ FieldMatcher dimensions;
+ DurationMetricProducer durationProducer(
+ kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
+
+ durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+ EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ std::vector<DurationBucket> buckets =
+ durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
+ EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketEndNs);
+ EXPECT_EQ(eventUpgradeTimeNs - startTimeNs, buckets[0].mDuration);
+ EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs));
+ buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(3UL, buckets.size());
+ EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - eventUpgradeTimeNs, buckets[1].mDuration);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
+ EXPECT_EQ(bucketSizeNs, buckets[2].mDuration);
+}
+
+TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) {
+ /**
+ * Expected buckets (start at 11s, upgrade at 75s, end at 135s):
+ * - [10,70]: 59 secs
+ * - [70,75]: 5 sec
+ * - [75,130]: 55 secs
+ */
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+ uint64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
+ uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+
+ int tagId = 1;
+
+ DurationMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ FieldMatcher dimensions;
+ DurationMetricProducer durationProducer(
+ kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
+
+ durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+ EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ std::vector<DurationBucket> buckets =
+ durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs);
+ EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketEndNs);
+ EXPECT_EQ(eventUpgradeTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration);
+ EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs));
+ buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(3UL, buckets.size());
+ EXPECT_EQ(eventUpgradeTimeNs, buckets[2].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - eventUpgradeTimeNs, buckets[2].mDuration);
+}
+
+TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+ uint64_t startTimeNs = bucketStartTimeNs + 1;
+ uint64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC;
+
+ int tagId = 1;
+
+ // Setup metric with alert.
+ DurationMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
+ Alert alert;
+ alert.set_num_buckets(3);
+ alert.set_trigger_if_sum_gt(2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ FieldMatcher dimensions;
+ DurationMetricProducer durationProducer(
+ kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
+ sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert);
+ EXPECT_TRUE(anomalyTracker != nullptr);
+
+ durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs));
+ durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs));
+
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs,
+ (uint64_t)anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+}
+
+TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+ uint64_t startTimeNs = bucketStartTimeNs + 1;
+ uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+
+ int tagId = 1;
+
+ DurationMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
+ LogEvent event1(tagId, startTimeNs);
+ event1.write("111"); // uid
+ event1.init();
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ FieldMatcher dimensions;
+ DurationMetricProducer durationProducer(
+ kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
+
+ durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+ EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+
+ durationProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1);
+ std::vector<DurationBucket> buckets =
+ durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(1UL, buckets.size());
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[0].mBucketEndNs);
+ EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
+}
+
+TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+ uint64_t startTimeNs = bucketStartTimeNs + 1;
+ uint64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC;
+
+ int tagId = 1;
+
+ DurationMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
+ LogEvent event1(tagId, startTimeNs);
+ event1.write("111"); // uid
+ event1.init();
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ FieldMatcher dimensions;
+ DurationMetricProducer durationProducer(
+ kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
+
+ durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+ EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ // Stop occurs in the same partial bucket as created for the app upgrade.
+ durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
+ std::vector<DurationBucket> buckets =
+ durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(1UL, buckets.size());
+ EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs);
+ EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 58be5b0..470d4d0 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -44,6 +44,7 @@
const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs;
+const uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
TEST(GaugeMetricProducerTest, TestNoCondition) {
GaugeMetric metric;
@@ -119,6 +120,143 @@
EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
}
+TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) {
+ GaugeMetric metric;
+ metric.set_id(metricId);
+ metric.set_bucket(ONE_MINUTE);
+ metric.mutable_gauge_fields_filter()->set_include_all(true);
+
+ Alert alert;
+ alert.set_id(101);
+ alert.set_metric_id(metricId);
+ alert.set_trigger_if_sum_gt(25);
+ alert.set_num_buckets(100);
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ shared_ptr<MockStatsPullerManager> pullerManager =
+ make_shared<StrictMock<MockStatsPullerManager>>();
+ GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
+ -1 /* -1 means no pulling */, bucketStartTimeNs,
+ pullerManager);
+ sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert);
+ EXPECT_TRUE(anomalyTracker != nullptr);
+
+ shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ event1->write(1);
+ event1->write(10);
+ event1->init();
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+ EXPECT_EQ(1UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY));
+
+ gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(0UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY));
+ EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(0UL, gaugeProducer.mCurrentBucketNum);
+ EXPECT_EQ(eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+ // Partial buckets are not sent to anomaly tracker.
+ EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ // Create an event in the same partial bucket.
+ shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 59 * NS_PER_SEC);
+ event2->write(1);
+ event2->write(10);
+ event2->init();
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+ EXPECT_EQ(0UL, gaugeProducer.mCurrentBucketNum);
+ EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+ // Partial buckets are not sent to anomaly tracker.
+ EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ // Next event should trigger creation of new bucket and send previous full bucket to anomaly
+ // tracker.
+ shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 65 * NS_PER_SEC);
+ event3->write(1);
+ event3->write(10);
+ event3->init();
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentBucketNum);
+ EXPECT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)bucketStartTimeNs + bucketSizeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(1, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ // Next event should trigger creation of new bucket.
+ shared_ptr<LogEvent> event4 =
+ make_shared<LogEvent>(tagId, bucketStartTimeNs + 125 * NS_PER_SEC);
+ event4->write(1);
+ event4->write(10);
+ event4->init();
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
+ EXPECT_EQ(2UL, gaugeProducer.mCurrentBucketNum);
+ EXPECT_EQ(3UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+}
+
+TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) {
+ GaugeMetric metric;
+ metric.set_id(metricId);
+ metric.set_bucket(ONE_MINUTE);
+ auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields();
+ gaugeFieldMatcher->set_field(tagId);
+ gaugeFieldMatcher->add_child()->set_field(2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ shared_ptr<MockStatsPullerManager> pullerManager =
+ make_shared<StrictMock<MockStatsPullerManager>>();
+ EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return());
+ EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, eventUpgradeTimeNs);
+ event->write("some value");
+ event->write(2);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
+ tagId, bucketStartTimeNs, pullerManager);
+
+ vector<shared_ptr<LogEvent>> allData;
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
+ event->write("some value");
+ event->write(1);
+ event->init();
+ allData.push_back(event);
+ gaugeProducer.onDataPulled(allData);
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin()
+ ->second.front()
+ .mFields->begin()
+ ->second.value_int());
+
+ gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(0UL, gaugeProducer.mCurrentBucketNum);
+ EXPECT_EQ((uint64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(2, gaugeProducer.mCurrentSlicedBucket->begin()
+ ->second.front()
+ .mFields->begin()
+ ->second.value_int());
+
+ allData.clear();
+ event = make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 1);
+ event->write("some value");
+ event->write(3);
+ event->init();
+ allData.push_back(event);
+ gaugeProducer.onDataPulled(allData);
+ EXPECT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(3, gaugeProducer.mCurrentSlicedBucket->begin()
+ ->second.front()
+ .mFields->begin()
+ ->second.value_int());
+}
+
TEST(GaugeMetricProducerTest, TestWithCondition) {
GaugeMetric metric;
metric.set_id(metricId);
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 203f028..2658e4e 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -41,6 +41,11 @@
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");
+const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
@@ -54,11 +59,13 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
uint64_t bucketStartTimeNs = 10000000000;
- uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, false, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, {});
tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
// Event starts again. This would not change anything as it already starts.
@@ -87,12 +94,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, false, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, {});
tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
@@ -101,15 +111,14 @@
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, &buckets);
tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
EXPECT_TRUE(tracker.mInfos.empty());
-
- EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
- EXPECT_EQ(1u, buckets[eventKey].size());
- EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
+ EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40, &buckets);
- EXPECT_EQ(2u, buckets[eventKey].size());
- EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
- EXPECT_EQ(40ULL, buckets[eventKey][1].mDuration);
+ EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
+ EXPECT_EQ(1u, buckets[eventKey].size());
+ EXPECT_EQ(bucketSizeNs + 40 - 1, buckets[eventKey][0].mDuration);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[eventKey][0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs);
}
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
@@ -122,12 +131,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, false, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, {});
// The event starts.
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -137,14 +149,18 @@
ConditionKey());
// The event stops at early 4th bucket.
+ // Notestop is called from DurationMetricProducer's onMatchedLogEvent, which calls
+ // flushIfneeded.
tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, &buckets);
tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (3 * bucketSizeNs) + 20,
false /*stop all*/);
- EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
- EXPECT_EQ(3u, buckets[eventKey].size());
- EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[eventKey][0].mDuration);
- EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[eventKey][1].mDuration);
- EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[eventKey][2].mDuration);
+ EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
+
+ tracker.flushIfNeeded(bucketStartTimeNs + 4 * bucketSizeNs, &buckets);
+ EXPECT_EQ(1u, buckets[eventKey].size());
+ EXPECT_EQ((3 * bucketSizeNs) + 20 - 1, buckets[eventKey][0].mDuration);
+ EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[eventKey][0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs);
}
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
@@ -157,12 +173,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, false, {});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, {});
// 2 starts
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -171,21 +190,16 @@
tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/);
tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1, &buckets);
-
- EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
- EXPECT_EQ(2u, buckets[eventKey].size());
- EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
- EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
+ // Because of nesting, still not stopped.
+ EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
// real stop now.
tracker.noteStop(DEFAULT_DIMENSION_KEY,
bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets);
- EXPECT_EQ(3u, buckets[eventKey].size());
- EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
- EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
- EXPECT_EQ(5ULL, buckets[eventKey][2].mDuration);
+ EXPECT_EQ(1u, buckets[eventKey].size());
+ EXPECT_EQ(2 * bucketSizeNs + 5 - 1, buckets[eventKey][0].mDuration);
}
TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
@@ -204,14 +218,17 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
int64_t durationTimeNs = 2 * 1000;
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, true, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ true, {});
EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
@@ -226,45 +243,6 @@
EXPECT_EQ(5ULL, buckets[eventKey][0].mDuration);
}
-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);
- alert.set_metric_id(metricId);
- alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
- alert.set_num_buckets(2);
- const int32_t refPeriodSec = 1;
- alert.set_refractory_period_secs(refPeriodSec);
-
- unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-
- 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);
- 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);
- 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->getRefractoryPeriodEndsSec(eventKey),
- (eventStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 3 + refPeriodSec);
-}
-
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 80e16a1..4b579b1 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -38,6 +38,12 @@
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");
+const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
TEST(OringDurationTrackerTest, TestDurationOverlap) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
@@ -51,13 +57,16 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t durationTimeNs = 2 * 1000;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, false, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -84,12 +93,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, false, {});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
@@ -115,12 +127,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, false, {});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
@@ -145,13 +160,16 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t durationTimeNs = 2 * 1000;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, false, {});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -190,13 +208,16 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t durationTimeNs = 2 * 1000;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, true, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, true, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -231,12 +252,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t durationTimeNs = 2 * 1000;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, true, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, true, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
// condition to false; record duration 5n
@@ -271,11 +295,14 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, true, {});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, true, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
@@ -311,12 +338,14 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
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, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, true, {anomalyTracker});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, true, {anomalyTracker});
// Nothing in the past bucket.
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
@@ -379,12 +408,14 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
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, dimensionInCondition,
- true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
+ true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, false, {anomalyTracker});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
@@ -434,8 +465,8 @@
sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false,
- {anomalyTracker});
+ true /*nesting*/, bucketStartTimeNs, 0, 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 55c078d..325a372 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -44,6 +44,7 @@
const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs;
+const int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
/*
* Tests pulled atoms with no conditions
@@ -202,6 +203,101 @@
EXPECT_EQ(false, curInterval.startUpdated);
}
+TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) {
+ ValueMetric metric;
+ metric.set_id(metricId);
+ metric.set_bucket(ONE_MINUTE);
+ metric.mutable_value_field()->set_field(tagId);
+ metric.mutable_value_field()->add_child()->set_field(2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ shared_ptr<MockStatsPullerManager> pullerManager =
+ make_shared<StrictMock<MockStatsPullerManager>>();
+ ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs,
+ pullerManager);
+
+ shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ event1->write(1);
+ event1->write(10);
+ event1->init();
+ valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+
+ valueProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)eventUpgradeTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+
+ shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 59 * NS_PER_SEC);
+ event2->write(1);
+ event2->write(10);
+ event2->init();
+ valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)eventUpgradeTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+
+ // Next value should create a new bucket.
+ shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 65 * NS_PER_SEC);
+ event3->write(1);
+ event3->write(10);
+ event3->init();
+ valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
+ EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs);
+}
+
+TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) {
+ ValueMetric metric;
+ metric.set_id(metricId);
+ metric.set_bucket(ONE_MINUTE);
+ metric.mutable_value_field()->set_field(tagId);
+ metric.mutable_value_field()->add_child()->set_field(2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ shared_ptr<MockStatsPullerManager> pullerManager =
+ make_shared<StrictMock<MockStatsPullerManager>>();
+ EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return());
+ EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ event->write(tagId);
+ event->write(120);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+ ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, tagId, bucketStartTimeNs,
+ pullerManager);
+
+ vector<shared_ptr<LogEvent>> allData;
+ allData.clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
+ event->write(tagId);
+ event->write(100);
+ event->init();
+ allData.push_back(event);
+
+ valueProducer.onDataPulled(allData);
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+
+ valueProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)eventUpgradeTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(20L, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mValue);
+
+ allData.clear();
+ event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
+ event->write(tagId);
+ event->write(150);
+ event->init();
+ allData.push_back(event);
+ valueProducer.onDataPulled(allData);
+ EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)bucket2StartTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(30L, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mValue);
+}
+
TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) {
ValueMetric metric;
metric.set_id(metricId);
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index 76a3682..2ba4c00 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -88,7 +88,7 @@
public void onGetInstantAppResolveInfo(Intent sanitizedIntent, int[] hostDigestPrefix,
String token, InstantAppResolutionCallback callback) {
// if not overridden, forward to old methods and filter out non-web intents
- if (sanitizedIntent.isBrowsableWebIntent()) {
+ if (sanitizedIntent.isWebIntent()) {
onGetInstantAppResolveInfo(hostDigestPrefix, token, callback);
} else {
callback.onInstantAppResolveInfo(Collections.emptyList());
@@ -107,7 +107,7 @@
String token, InstantAppResolutionCallback callback) {
Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden");
// if not overridden, forward to old methods and filter out non-web intents
- if (sanitizedIntent.isBrowsableWebIntent()) {
+ if (sanitizedIntent.isWebIntent()) {
onGetInstantAppIntentFilter(hostDigestPrefix, token, callback);
} else {
callback.onInstantAppResolveInfo(Collections.emptyList());
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6e40986..8393caa 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1055,11 +1055,10 @@
/**
* {@link #extras} key: A
* {@link android.content.ContentUris content URI} pointing to an image that can be displayed
- * in the background when the notification is selected. The URI must point to an image stream
- * suitable for passing into
+ * in the background when the notification is selected. Used on television platforms.
+ * The URI must point to an image stream suitable for passing into
* {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
- * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider
- * URI used for this purpose must require no permissions to read the image data.
+ * BitmapFactory.decodeStream}; all other content types will be ignored.
*/
public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 538623f..b10e608 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -24,6 +24,7 @@
import android.app.Notification.Builder;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.drawable.Icon;
import android.net.Uri;
@@ -350,6 +351,14 @@
* the same tag and id has already been posted by your application and has not yet been
* canceled, it will be replaced by the updated information.
*
+ * All {@link android.service.notification.NotificationListenerService listener services} will
+ * be granted {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} access to any {@link Uri uris}
+ * provided on this notification or the
+ * {@link NotificationChannel} this notification is posted to using
+ * {@link Context#grantUriPermission(String, Uri, int)}. Permission will be revoked when the
+ * notification is canceled, or you can revoke permissions with
+ * {@link Context#revokeUriPermission(Uri, int)}.
+ *
* @param tag A string identifier for this notification. May be {@code null}.
* @param id An identifier for this notification. The pair (tag, id) must be unique
* within your application.
@@ -370,11 +379,13 @@
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
+
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
+
}
fixLegacySmallIcon(notification, pkg);
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
@@ -385,6 +396,7 @@
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
notification.reduceImageSizes(mContext);
+
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
boolean isLowRam = am.isLowRamDevice();
final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam);
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 09f9684..09a5b59 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -676,6 +676,35 @@
mExcludeSdp = excludeSdp;
}
+ /**
+ * Set the LE Transmit Data Length to be the maximum that the BT Controller is capable of. This
+ * parameter is used by the BT Controller to set the maximum transmission packet size on this
+ * connection. This function is currently used for testing only.
+ * @hide
+ */
+ public void requestMaximumTxDataLength() throws IOException {
+ if (mDevice == null) {
+ throw new IOException("requestMaximumTxDataLength is called on null device");
+ }
+
+ try {
+ if (mSocketState == SocketState.CLOSED) {
+ throw new IOException("socket closed");
+ }
+ IBluetooth bluetoothProxy =
+ BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
+ if (bluetoothProxy == null) {
+ throw new IOException("Bluetooth is off");
+ }
+
+ if (DBG) Log.d(TAG, "requestMaximumTxDataLength");
+ bluetoothProxy.getSocketManager().requestMaximumTxDataLength(mDevice);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ throw new IOException("unable to send RPC: " + e.getMessage());
+ }
+ }
+
private String convertAddr(final byte[] addr) {
return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9b62f19..fa73e3c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -10089,9 +10089,8 @@
}
/** @hide */
- public boolean isBrowsableWebIntent() {
+ public boolean isWebIntent() {
return ACTION_VIEW.equals(mAction)
- && hasCategory(CATEGORY_BROWSABLE)
&& hasWebURI();
}
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index 1a207ba..8464e26 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -16,10 +16,14 @@
package android.content.om;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Immutable overlay information about a package. All PackageInfos that
* represent an overlay package will have a corresponding OverlayInfo.
@@ -27,6 +31,19 @@
* @hide
*/
public final class OverlayInfo implements Parcelable {
+
+ @IntDef(prefix = "STATE_", value = {
+ STATE_UNKNOWN,
+ STATE_MISSING_TARGET,
+ STATE_NO_IDMAP,
+ STATE_DISABLED,
+ STATE_ENABLED,
+ STATE_TARGET_UPGRADING,
+ STATE_OVERLAY_UPGRADING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {}
+
/**
* An internal state used as the initial state of an overlay. OverlayInfo
* objects exposed outside the {@link
@@ -61,6 +78,18 @@
public static final int STATE_ENABLED = 3;
/**
+ * The target package is currently being upgraded; the state will change
+ * once the package installation has finished.
+ */
+ public static final int STATE_TARGET_UPGRADING = 4;
+
+ /**
+ * The overlay package is currently being upgraded; the state will change
+ * once the package installation has finished.
+ */
+ public static final int STATE_OVERLAY_UPGRADING = 5;
+
+ /**
* Package name of the overlay package
*/
public final String packageName;
@@ -77,13 +106,8 @@
/**
* The state of this OverlayInfo as defined by the STATE_* constants in this class.
- *
- * @see #STATE_MISSING_TARGET
- * @see #STATE_NO_IDMAP
- * @see #STATE_DISABLED
- * @see #STATE_ENABLED
*/
- public final int state;
+ public final @State int state;
/**
* User handle for which this overlay applies
@@ -96,13 +120,13 @@
* @param source the source OverlayInfo to base the new instance on
* @param state the new state for the source OverlayInfo
*/
- public OverlayInfo(@NonNull OverlayInfo source, int state) {
+ public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
this(source.packageName, source.targetPackageName, source.baseCodePath, state,
source.userId);
}
public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
- @NonNull String baseCodePath, int state, int userId) {
+ @NonNull String baseCodePath, @State int state, int userId) {
this.packageName = packageName;
this.targetPackageName = targetPackageName;
this.baseCodePath = baseCodePath;
@@ -136,6 +160,8 @@
case STATE_NO_IDMAP:
case STATE_DISABLED:
case STATE_ENABLED:
+ case STATE_TARGET_UPGRADING:
+ case STATE_OVERLAY_UPGRADING:
break;
default:
throw new IllegalArgumentException("State " + state + " is not a valid state");
@@ -156,7 +182,8 @@
dest.writeInt(userId);
}
- public static final Parcelable.Creator<OverlayInfo> CREATOR = new Parcelable.Creator<OverlayInfo>() {
+ public static final Parcelable.Creator<OverlayInfo> CREATOR =
+ new Parcelable.Creator<OverlayInfo>() {
@Override
public OverlayInfo createFromParcel(Parcel source) {
return new OverlayInfo(source);
@@ -189,14 +216,9 @@
* Translate a state to a human readable string. Only intended for
* debugging purposes.
*
- * @see #STATE_MISSING_TARGET
- * @see #STATE_NO_IDMAP
- * @see #STATE_DISABLED
- * @see #STATE_ENABLED
- *
* @return a human readable String representing the state.
*/
- public static String stateToString(int state) {
+ public static String stateToString(@State int state) {
switch (state) {
case STATE_UNKNOWN:
return "STATE_UNKNOWN";
@@ -208,6 +230,10 @@
return "STATE_DISABLED";
case STATE_ENABLED:
return "STATE_ENABLED";
+ case STATE_TARGET_UPGRADING:
+ return "STATE_TARGET_UPGRADING";
+ case STATE_OVERLAY_UPGRADING:
+ return "STATE_OVERLAY_UPGRADING";
default:
return "<unknown state>";
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index b61a6d9..db1630b 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1601,7 +1601,7 @@
* @hide
*/
public boolean isAllowedToUseHiddenApi() {
- return false;
+ return isSystemApp();
}
/**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4e5f835..486c86c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -441,6 +441,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static final int MATCH_FACTORY_ONLY = 0x00200000;
/**
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2da8937..3afc346 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6738,31 +6738,6 @@
+ " " + packageName + "}";
}
- public String dumpState_temp() {
- String flags = "";
- flags += ((applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : "");
- flags += ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : "");
- if ("".equals(flags)) {
- flags = "-";
- }
- String privFlags = "";
- privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : "");
- privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : "");
- privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : "");
- if ("".equals(privFlags)) {
- privFlags = "-";
- }
- return "Package{"
- + Integer.toHexString(System.identityHashCode(this))
- + " " + packageName
- + ", ver:" + getLongVersionCode()
- + ", path: " + codePath
- + ", flags: " + flags
- + ", privFlags: " + privFlags
- + ", extra: " + (mExtras == null ? "<<NULL>>" : Integer.toHexString(System.identityHashCode(mExtras)) + "}")
- + "}";
- }
-
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 40ee834..df64401 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -923,6 +923,14 @@
* {@link CameraCaptureSession#setRepeatingRequest} or
* {@link CameraCaptureSession#setRepeatingBurst}</p>
*
+ * <p>Individual physical camera settings will only be honored for camera session
+ * that was initialiazed with corresponding physical camera id output configuration
+ * {@link OutputConfiguration#setPhysicalCameraId} and the same output targets are
+ * also attached in the request by {@link CaptureRequest.Builder#addTarget}.</p>
+ *
+ * <p>The output is undefined for any logical camera streams in case valid physical camera
+ * settings are attached.</p>
+ *
* @param templateType An enumeration selecting the use case for this request. Not all template
* types are supported on every device. See the documentation for each template type for
* details.
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index bae2d04..0be45a0 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -17,7 +17,6 @@
package android.hardware.camera2;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
@@ -26,6 +25,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* <p>The total assembled results of a single image capture from the image sensor.</p>
@@ -49,9 +49,9 @@
*
* <p>For a logical multi-camera device, if the CaptureRequest contains a surface for an underlying
* physical camera, the corresponding {@link TotalCaptureResult} object will include the metadata
- * for that physical camera. And its keys and values can be accessed by
- * {@link #getPhysicalCameraKey}. If all requested surfaces are for the logical camera, no
- * metadata for physical camera will be included.</p>
+ * for that physical camera. And the mapping between the physical camera id and result metadata
+ * can be accessed via {@link #getPhysicalCameraResults}. If all requested surfaces are for the
+ * logical camera, no metadata for physical camera will be included.</p>
*
* <p>{@link TotalCaptureResult} objects are immutable.</p>
*
@@ -62,7 +62,7 @@
private final List<CaptureResult> mPartialResults;
private final int mSessionId;
// The map between physical camera id and capture result
- private final HashMap<String, CameraMetadataNative> mPhysicalCaptureResults;
+ private final HashMap<String, CaptureResult> mPhysicalCaptureResults;
/**
* Takes ownership of the passed-in camera metadata and the partial results
@@ -83,10 +83,12 @@
mSessionId = sessionId;
- mPhysicalCaptureResults = new HashMap<String, CameraMetadataNative>();
+ mPhysicalCaptureResults = new HashMap<String, CaptureResult>();
for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) {
+ CaptureResult physicalResult = new CaptureResult(
+ onePhysicalResult.getCameraMetadata(), parent, extras);
mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(),
- onePhysicalResult.getCameraMetadata());
+ physicalResult);
}
}
@@ -101,7 +103,7 @@
mPartialResults = new ArrayList<>();
mSessionId = CameraCaptureSession.SESSION_ID_NONE;
- mPhysicalCaptureResults = new HashMap<String, CameraMetadataNative>();
+ mPhysicalCaptureResults = new HashMap<String, CaptureResult>();
}
/**
@@ -132,36 +134,20 @@
}
/**
- * Get a capture result field value for a particular physical camera id.
+ * Get the map between physical camera ids and their capture result metadata
*
- * <p>The field definitions can be found in {@link CaptureResult}.</p>
- *
- * <p>This function can be called for logical camera devices, which are devices that have
+ * <p>This function can be called for logical multi-camera devices, which are devices that have
* REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to {@link
* CameraCharacteristics#getPhysicalCameraIds} return a non-empty list of physical devices that
- * are backing the logical camera. The camera id included in physicalCameraId argument
- * selects an individual physical device, and returns its specific capture result field.</p>
+ * are backing the logical camera.</p>
*
- * <p>This function should only be called if one or more streams from the underlying
- * 'physicalCameraId' was requested by the corresponding capture request.</p>
- *
- * @throws IllegalArgumentException if the key was not valid, or the physicalCameraId is not
- * applicable to the current camera, or a stream from 'physicalCameraId' is not requested by the
- * corresponding capture request.
- *
- * @param key The result field to read.
- * @param physicalCameraId The physical camera the result originates from.
- *
- * @return The value of that key, or {@code null} if the field is not set.
- */
- @Nullable
- public <T> T getPhysicalCameraKey(Key<T> key, @NonNull String physicalCameraId) {
- if (!mPhysicalCaptureResults.containsKey(physicalCameraId)) {
- throw new IllegalArgumentException(
- "No TotalCaptureResult exists for physical camera " + physicalCameraId);
- }
+ * <p>If one or more streams from the underlying physical cameras were requested by the
+ * corresponding capture request, this function returns the result metadata for those physical
+ * cameras. Otherwise, an empty map is returned.</p>
- CameraMetadataNative physicalMetadata = mPhysicalCaptureResults.get(physicalCameraId);
- return physicalMetadata.get(key);
+ * @return unmodifiable map between physical camera ids and their capture result metadata
+ */
+ public Map<String, CaptureResult> getPhysicalCameraResults() {
+ return Collections.unmodifiableMap(mPhysicalCaptureResults);
}
}
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index eb4bced..b205e2c 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -368,7 +368,7 @@
* desirable for the camera application to request streams from individual physical cameras.
* This call achieves it by mapping the OutputConfiguration to the physical camera id.</p>
*
- * <p>The valid physical camera id can be queried by {@link
+ * <p>The valid physical camera ids can be queried by {@link
* android.hardware.camera2.CameraCharacteristics#getPhysicalCameraIds}.
* </p>
*
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 4aadc5b..682fdb7 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -784,7 +784,7 @@
private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
// Debuggable builds will throw an AssertionError if the number of map entries exceeds:
- private static final int CRASH_AT_SIZE = 5_000;
+ private static final int CRASH_AT_SIZE = 20_000;
/**
* We next warn when we exceed this bucket size.
diff --git a/core/java/android/os/IServiceManager.java b/core/java/android/os/IServiceManager.java
index 2176a78..89bf7b9 100644
--- a/core/java/android/os/IServiceManager.java
+++ b/core/java/android/os/IServiceManager.java
@@ -76,9 +76,15 @@
int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0;
int DUMP_FLAG_PRIORITY_HIGH = 1 << 1;
int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2;
+ /**
+ * Services are by default registered with a DEFAULT dump priority. DEFAULT priority has the
+ * same priority as NORMAL priority but the services are not called with dump priority
+ * arguments.
+ */
+ int DUMP_FLAG_PRIORITY_DEFAULT = 1 << 3;
int DUMP_FLAG_PRIORITY_ALL = DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH
- | DUMP_FLAG_PRIORITY_NORMAL;
+ | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PRIORITY_DEFAULT;
/* Allows services to dump sections in protobuf format. */
- int DUMP_FLAG_PROTO = 1 << 3;
+ int DUMP_FLAG_PROTO = 1 << 4;
}
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 42ec315..3be76d6 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -83,7 +83,7 @@
* @param service the service object
*/
public static void addService(String name, IBinder service) {
- addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL);
+ addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
}
/**
@@ -96,7 +96,7 @@
* to access this service
*/
public static void addService(String name, IBinder service, boolean allowIsolated) {
- addService(name, service, allowIsolated, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL);
+ addService(name, service, allowIsolated, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8c65041..f179371 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3692,6 +3692,20 @@
new SettingsValidators.InclusiveIntegerRangeValidator(0, 3);
/**
+ * User-selected RTT mode
+ * 0 = OFF
+ * 1 = FULL
+ * 2 = VCO
+ * 3 = HCO
+ * Uses the same constants as TTY (e.g. {@link android.telecom.TelecomManager#TTY_MODE_OFF})
+ * @hide
+ */
+ public static final String RTT_CALLING_MODE = "rtt_calling_mode";
+
+ /** @hide */
+ public static final Validator RTT_CALLING_MODE_VALIDATOR = TTY_MODE_VALIDATOR;
+
+ /**
* Whether the sounds effects (key clicks, lid open ...) are enabled. The value is
* boolean (1 or 0).
*/
@@ -4016,6 +4030,7 @@
DTMF_TONE_WHEN_DIALING,
DTMF_TONE_TYPE_WHEN_DIALING,
HEARING_AID,
+ RTT_CALLING_MODE,
TTY_MODE,
MASTER_MONO,
SOUND_EFFECTS_ENABLED,
@@ -4214,6 +4229,7 @@
VALIDATORS.put(DTMF_TONE_TYPE_WHEN_DIALING, DTMF_TONE_TYPE_WHEN_DIALING_VALIDATOR);
VALIDATORS.put(HEARING_AID, HEARING_AID_VALIDATOR);
VALIDATORS.put(TTY_MODE, TTY_MODE_VALIDATOR);
+ VALIDATORS.put(RTT_CALLING_MODE, RTT_CALLING_MODE_VALIDATOR);
VALIDATORS.put(NOTIFICATION_LIGHT_PULSE, NOTIFICATION_LIGHT_PULSE_VALIDATOR);
VALIDATORS.put(POINTER_LOCATION, POINTER_LOCATION_VALIDATOR);
VALIDATORS.put(SHOW_TOUCHES, SHOW_TOUCHES_VALIDATOR);
@@ -10255,6 +10271,20 @@
public static final String BATTERY_TIP_CONSTANTS = "battery_tip_constants";
/**
+ * An integer to show the version of the anomaly config. Ex: 1, which means
+ * current version is 1.
+ * @hide
+ */
+ public static final String ANOMALY_CONFIG_VERSION = "anomaly_config_version";
+
+ /**
+ * A base64-encoded string represents anomaly stats config, used for
+ * {@link android.app.StatsManager}.
+ * @hide
+ */
+ public static final String ANOMALY_CONFIG = "anomaly_config";
+
+ /**
* Always on display(AOD) specific settings
* This is encoded as a key=value list, separated by commas. Ex:
*
diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java
index 29c6cea..7383de7 100644
--- a/core/java/android/service/autofill/AutofillServiceInfo.java
+++ b/core/java/android/service/autofill/AutofillServiceInfo.java
@@ -44,6 +44,7 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.Map;
/**
@@ -247,4 +248,13 @@
&& !mCompatibilityPackages.isEmpty()).append("]");
return builder.toString();
}
+
+ /**
+ * Dumps it!
+ */
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("Component: "); pw.println(getServiceInfo().getComponentName());
+ pw.print(prefix); pw.print("Settings: "); pw.println(mSettingsActivity);
+ pw.print(prefix); pw.print("Compat packages: "); pw.println(mCompatibilityPackages);
+ }
}
diff --git a/core/java/android/transition/TransitionUtils.java b/core/java/android/transition/TransitionUtils.java
index 084b79d..60b77bc 100644
--- a/core/java/android/transition/TransitionUtils.java
+++ b/core/java/android/transition/TransitionUtils.java
@@ -163,10 +163,14 @@
public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds,
ViewGroup sceneRoot) {
final boolean addToOverlay = !view.isAttachedToWindow();
+ ViewGroup parent = null;
+ int indexInParent = 0;
if (addToOverlay) {
if (sceneRoot == null || !sceneRoot.isAttachedToWindow()) {
return null;
}
+ parent = (ViewGroup) view.getParent();
+ indexInParent = parent.indexOfChild(view);
sceneRoot.getOverlay().add(view);
}
Bitmap bitmap = null;
@@ -190,6 +194,7 @@
}
if (addToOverlay) {
sceneRoot.getOverlay().remove(view);
+ parent.addView(view, indexInParent);
}
return bitmap;
}
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index f0838a1..77c652e 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -402,8 +402,11 @@
// Becoming GONE
if (startView == endView) {
viewToKeep = endView;
- } else {
+ } else if (mCanRemoveViews) {
overlayView = startView;
+ } else {
+ overlayView = TransitionUtils.copyViewImage(sceneRoot, startView,
+ (View) startView.getParent());
}
}
}
diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java
index 8edf97e..69c38ee 100644
--- a/core/java/android/view/textclassifier/SmartSelection.java
+++ b/core/java/android/view/textclassifier/SmartSelection.java
@@ -108,9 +108,9 @@
}
/**
- * Returns the language of the model.
+ * Returns a comma separated list of locales supported by the model as BCP 47 tags.
*/
- public static String getLanguage(int fd) {
+ public static String getLanguages(int fd) {
return nativeGetLanguage(fd);
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 9f389ba..fc03493 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -27,8 +27,10 @@
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
+import android.os.UserManager;
import android.provider.Browser;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
@@ -56,6 +58,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -99,11 +102,9 @@
private final Object mLock = new Object();
@GuardedBy("mLock") // Do not access outside this lock.
- private Map<Locale, String> mModelFilePaths;
+ private List<ModelFile> mAllModelFiles;
@GuardedBy("mLock") // Do not access outside this lock.
- private Locale mLocale;
- @GuardedBy("mLock") // Do not access outside this lock.
- private int mVersion;
+ private ModelFile mModel;
@GuardedBy("mLock") // Do not access outside this lock.
private SmartSelection mSmartSelection;
@@ -279,18 +280,18 @@
private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException {
synchronized (mLock) {
localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList;
- final Locale locale = findBestSupportedLocaleLocked(localeList);
- if (locale == null) {
- throw new FileNotFoundException("No file for null locale");
+ final ModelFile bestModel = findBestModelLocked(localeList);
+ if (bestModel == null) {
+ throw new FileNotFoundException("No model for " + localeList.toLanguageTags());
}
- if (mSmartSelection == null || !Objects.equals(mLocale, locale)) {
+ if (mSmartSelection == null || !Objects.equals(mModel, bestModel)) {
+ Log.d(DEFAULT_LOG_TAG, "Loading " + bestModel);
destroySmartSelectionIfExistsLocked();
- final ParcelFileDescriptor fd = getFdLocked(locale);
- final int modelFd = fd.getFd();
- mVersion = SmartSelection.getVersion(modelFd);
- mSmartSelection = new SmartSelection(modelFd);
+ final ParcelFileDescriptor fd = ParcelFileDescriptor.open(
+ new File(bestModel.getPath()), ParcelFileDescriptor.MODE_READ_ONLY);
+ mSmartSelection = new SmartSelection(fd.getFd());
closeAndLogError(fd);
- mLocale = locale;
+ mModel = bestModel;
}
return mSmartSelection;
}
@@ -298,74 +299,8 @@
private String getSignature(String text, int start, int end) {
synchronized (mLock) {
- return DefaultLogger.createSignature(text, start, end, mContext, mVersion, mLocale);
- }
- }
-
- @GuardedBy("mLock") // Do not call outside this lock.
- private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException {
- ParcelFileDescriptor updateFd;
- int updateVersion = -1;
- try {
- updateFd = ParcelFileDescriptor.open(
- new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY);
- if (updateFd != null) {
- updateVersion = SmartSelection.getVersion(updateFd.getFd());
- }
- } catch (FileNotFoundException e) {
- updateFd = null;
- }
- ParcelFileDescriptor factoryFd;
- int factoryVersion = -1;
- try {
- final String factoryModelFilePath = getFactoryModelFilePathsLocked().get(locale);
- if (factoryModelFilePath != null) {
- factoryFd = ParcelFileDescriptor.open(
- new File(factoryModelFilePath), ParcelFileDescriptor.MODE_READ_ONLY);
- if (factoryFd != null) {
- factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
- }
- } else {
- factoryFd = null;
- }
- } catch (FileNotFoundException e) {
- factoryFd = null;
- }
-
- if (updateFd == null) {
- if (factoryFd != null) {
- return factoryFd;
- } else {
- throw new FileNotFoundException(
- String.format(Locale.US, "No model file found for %s", locale));
- }
- }
-
- final int updateFdInt = updateFd.getFd();
- final boolean localeMatches = Objects.equals(
- locale.getLanguage().trim().toLowerCase(),
- SmartSelection.getLanguage(updateFdInt).trim().toLowerCase());
- if (factoryFd == null) {
- if (localeMatches) {
- return updateFd;
- } else {
- closeAndLogError(updateFd);
- throw new FileNotFoundException(
- String.format(Locale.US, "No model file found for %s", locale));
- }
- }
-
- if (!localeMatches) {
- closeAndLogError(updateFd);
- return factoryFd;
- }
-
- if (updateVersion > factoryVersion) {
- closeAndLogError(factoryFd);
- return updateFd;
- } else {
- closeAndLogError(updateFd);
- return factoryFd;
+ return DefaultLogger.createSignature(text, start, end, mContext, mModel.getVersion(),
+ mModel.getSupportedLocales());
}
}
@@ -377,60 +312,66 @@
}
}
+ /**
+ * Finds the most appropriate model to use for the given target locale list.
+ *
+ * The basic logic is: we ignore all models that don't support any of the target locales. For
+ * the remaining candidates, we take the update model unless its version number is lower than
+ * the factory version. It's assumed that factory models do not have overlapping locale ranges
+ * and conflict resolution between these models hence doesn't matter.
+ */
@GuardedBy("mLock") // Do not call outside this lock.
@Nullable
- private Locale findBestSupportedLocaleLocked(LocaleList localeList) {
+ private ModelFile findBestModelLocked(LocaleList localeList) {
// Specified localeList takes priority over the system default, so it is listed first.
final String languages = localeList.isEmpty()
? LocaleList.getDefault().toLanguageTags()
: localeList.toLanguageTags() + "," + LocaleList.getDefault().toLanguageTags();
final List<Locale.LanguageRange> languageRangeList = Locale.LanguageRange.parse(languages);
- final List<Locale> supportedLocales =
- new ArrayList<>(getFactoryModelFilePathsLocked().keySet());
- final Locale updatedModelLocale = getUpdatedModelLocale();
- if (updatedModelLocale != null) {
- supportedLocales.add(updatedModelLocale);
+ ModelFile bestModel = null;
+ int bestModelVersion = -1;
+ for (ModelFile model : listAllModelsLocked()) {
+ if (model.isAnyLanguageSupported(languageRangeList)) {
+ if (model.getVersion() >= bestModelVersion) {
+ bestModel = model;
+ bestModelVersion = model.getVersion();
+ }
+ }
}
- return Locale.lookup(languageRangeList, supportedLocales);
+ return bestModel;
}
+ /** Returns a list of all model files available, in order of precedence. */
@GuardedBy("mLock") // Do not call outside this lock.
- private Map<Locale, String> getFactoryModelFilePathsLocked() {
- if (mModelFilePaths == null) {
- final Map<Locale, String> modelFilePaths = new HashMap<>();
+ private List<ModelFile> listAllModelsLocked() {
+ if (mAllModelFiles == null) {
+ final List<ModelFile> allModels = new ArrayList<>();
+ // The update model has the highest precedence.
+ if (new File(UPDATED_MODEL_FILE_PATH).exists()) {
+ final ModelFile updatedModel = ModelFile.fromPath(UPDATED_MODEL_FILE_PATH);
+ if (updatedModel != null) {
+ allModels.add(updatedModel);
+ }
+ }
+ // Factory models should never have overlapping locales, so the order doesn't matter.
final File modelsDir = new File(MODEL_DIR);
if (modelsDir.exists() && modelsDir.isDirectory()) {
- final File[] models = modelsDir.listFiles();
+ final File[] modelFiles = modelsDir.listFiles();
final Pattern modelFilenamePattern = Pattern.compile(MODEL_FILE_REGEX);
- final int size = models.length;
- for (int i = 0; i < size; i++) {
- final File modelFile = models[i];
+ for (File modelFile : modelFiles) {
final Matcher matcher = modelFilenamePattern.matcher(modelFile.getName());
if (matcher.matches() && modelFile.isFile()) {
- final String language = matcher.group(1);
- final Locale locale = Locale.forLanguageTag(language);
- modelFilePaths.put(locale, modelFile.getAbsolutePath());
+ final ModelFile model = ModelFile.fromPath(modelFile.getAbsolutePath());
+ if (model != null) {
+ allModels.add(model);
+ }
}
}
}
- mModelFilePaths = modelFilePaths;
+ mAllModelFiles = allModels;
}
- return mModelFilePaths;
- }
-
- @Nullable
- private Locale getUpdatedModelLocale() {
- try {
- final ParcelFileDescriptor updateFd = ParcelFileDescriptor.open(
- new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY);
- final Locale locale = Locale.forLanguageTag(
- SmartSelection.getLanguage(updateFd.getFd()));
- closeAndLogError(updateFd);
- return locale;
- } catch (FileNotFoundException e) {
- return null;
- }
+ return mAllModelFiles;
}
private TextClassification createClassificationResult(
@@ -520,6 +461,95 @@
}
/**
+ * Describes TextClassifier model files on disk.
+ */
+ private static final class ModelFile {
+
+ private final String mPath;
+ private final String mName;
+ private final int mVersion;
+ private final List<Locale> mSupportedLocales;
+
+ /** Returns null if the path did not point to a compatible model. */
+ static @Nullable ModelFile fromPath(String path) {
+ final File file = new File(path);
+ try {
+ final ParcelFileDescriptor modelFd = ParcelFileDescriptor.open(
+ file, ParcelFileDescriptor.MODE_READ_ONLY);
+ final int version = SmartSelection.getVersion(modelFd.getFd());
+ final String supportedLocalesStr = SmartSelection.getLanguages(modelFd.getFd());
+ if (supportedLocalesStr.isEmpty()) {
+ Log.d(DEFAULT_LOG_TAG, "Ignoring " + file.getAbsolutePath());
+ return null;
+ }
+ final List<Locale> supportedLocales = new ArrayList<>();
+ for (String langTag : supportedLocalesStr.split(",")) {
+ supportedLocales.add(Locale.forLanguageTag(langTag));
+ }
+ closeAndLogError(modelFd);
+ return new ModelFile(path, file.getName(), version, supportedLocales);
+ } catch (FileNotFoundException e) {
+ Log.e(DEFAULT_LOG_TAG, "Failed to peek " + file.getAbsolutePath(), e);
+ return null;
+ }
+ }
+
+ /** The absolute path to the model file. */
+ String getPath() {
+ return mPath;
+ }
+
+ /** A name to use for signature generation. Effectively the name of the model file. */
+ String getName() {
+ return mName;
+ }
+
+ /** Returns the version tag in the model's metadata. */
+ int getVersion() {
+ return mVersion;
+ }
+
+ /** Returns whether the language supports any language in the given ranges. */
+ boolean isAnyLanguageSupported(List<Locale.LanguageRange> languageRanges) {
+ return Locale.lookup(languageRanges, mSupportedLocales) != null;
+ }
+
+ /** All locales supported by the model. */
+ List<Locale> getSupportedLocales() {
+ return Collections.unmodifiableList(mSupportedLocales);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (other == null || !ModelFile.class.isAssignableFrom(other.getClass())) {
+ return false;
+ } else {
+ final ModelFile otherModel = (ModelFile) other;
+ return mPath.equals(otherModel.mPath);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringJoiner localesJoiner = new StringJoiner(",");
+ for (Locale locale : mSupportedLocales) {
+ localesJoiner.add(locale.toLanguageTag());
+ }
+ return String.format(Locale.US, "ModelFile { path=%s name=%s version=%d locales=%s }",
+ mPath, mName, mVersion, localesJoiner.toString());
+ }
+
+ private ModelFile(String path, String name, int version, List<Locale> supportedLocales) {
+ mPath = path;
+ mName = name;
+ mVersion = version;
+ mSupportedLocales = supportedLocales;
+ }
+ }
+
+ /**
* Creates intents based on the classification type.
*/
static final class IntentFactory {
@@ -541,7 +571,7 @@
case TextClassifier.TYPE_EMAIL:
return createForEmail(text);
case TextClassifier.TYPE_PHONE:
- return createForPhone(text);
+ return createForPhone(context, text);
case TextClassifier.TYPE_ADDRESS:
return createForAddress(text);
case TextClassifier.TYPE_URL:
@@ -573,15 +603,23 @@
}
@NonNull
- private static List<Intent> createForPhone(String text) {
- return Arrays.asList(
- new Intent(Intent.ACTION_DIAL)
- .setData(Uri.parse(String.format("tel:%s", text))),
- new Intent(Intent.ACTION_INSERT_OR_EDIT)
- .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
- .putExtra(ContactsContract.Intents.Insert.PHONE, text),
- new Intent(Intent.ACTION_SENDTO)
- .setData(Uri.parse(String.format("smsto:%s", text))));
+ private static List<Intent> createForPhone(Context context, String text) {
+ final List<Intent> intents = new ArrayList<>();
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ final Bundle userRestrictions = userManager != null
+ ? userManager.getUserRestrictions() : new Bundle();
+ if (!userRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false)) {
+ intents.add(new Intent(Intent.ACTION_DIAL)
+ .setData(Uri.parse(String.format("tel:%s", text))));
+ }
+ intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
+ .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+ .putExtra(ContactsContract.Intents.Insert.PHONE, text));
+ if (!userRestrictions.getBoolean(UserManager.DISALLOW_SMS, false)) {
+ intents.add(new Intent(Intent.ACTION_SENDTO)
+ .setData(Uri.parse(String.format("smsto:%s", text))));
+ }
+ return intents;
}
@NonNull
diff --git a/core/java/android/view/textclassifier/logging/DefaultLogger.java b/core/java/android/view/textclassifier/logging/DefaultLogger.java
index 6b84835..03a6d3a 100644
--- a/core/java/android/view/textclassifier/logging/DefaultLogger.java
+++ b/core/java/android/view/textclassifier/logging/DefaultLogger.java
@@ -17,7 +17,6 @@
package android.view.textclassifier.logging;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.metrics.LogMaker;
import android.util.Log;
@@ -27,8 +26,10 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.Preconditions;
+import java.util.List;
import java.util.Locale;
import java.util.Objects;
+import java.util.StringJoiner;
/**
* Default Logger.
@@ -210,12 +211,16 @@
*/
public static String createSignature(
String text, int start, int end, Context context, int modelVersion,
- @Nullable Locale locale) {
+ List<Locale> locales) {
Preconditions.checkNotNull(text);
Preconditions.checkNotNull(context);
- final String modelName = (locale != null)
- ? String.format(Locale.US, "%s_v%d", locale.toLanguageTag(), modelVersion)
- : "";
+ Preconditions.checkNotNull(locales);
+ final StringJoiner localesJoiner = new StringJoiner(",");
+ for (Locale locale : locales) {
+ localesJoiner.add(locale.toLanguageTag());
+ }
+ final String modelName = String.format(Locale.US, "%s_v%d", localesJoiner.toString(),
+ modelVersion);
final int hash = Objects.hash(text, start, end, context.getPackageName());
return SignatureParser.createSignature(CLASSIFIER_ID, modelName, hash);
}
@@ -242,9 +247,9 @@
static String getModelName(String signature) {
Preconditions.checkNotNull(signature);
- final int start = signature.indexOf("|");
+ final int start = signature.indexOf("|") + 1;
final int end = signature.indexOf("|", start);
- if (start >= 0 && end >= start) {
+ if (start >= 1 && end >= start) {
return signature.substring(start, end);
}
return "";
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 1dfff5e..6bd6930 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -106,7 +106,7 @@
int flags = PackageManager.MATCH_DEFAULT_ONLY
| (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
| (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
- if (intent.isBrowsableWebIntent()
+ if (intent.isWebIntent()
|| (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
flags |= PackageManager.MATCH_INSTANT;
}
diff --git a/core/java/com/android/internal/colorextraction/types/Tonal.java b/core/java/com/android/internal/colorextraction/types/Tonal.java
index 9b7383f..7b25a06 100644
--- a/core/java/com/android/internal/colorextraction/types/Tonal.java
+++ b/core/java/com/android/internal/colorextraction/types/Tonal.java
@@ -405,12 +405,13 @@
return v - (float) Math.floor(v);
}
- static class TonalPalette {
- final float[] h;
- final float[] s;
- final float[] l;
- final float minHue;
- final float maxHue;
+ @VisibleForTesting
+ public static class TonalPalette {
+ public final float[] h;
+ public final float[] s;
+ public final float[] l;
+ public final float minHue;
+ public final float maxHue;
TonalPalette(float[] h, float[] s, float[] l) {
if (h.length != s.length || s.length != l.length) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 40dcf25b..cf3e073 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -3743,7 +3743,7 @@
if (mNumHistoryItems == MAX_HISTORY_ITEMS
|| mNumHistoryItems == MAX_MAX_HISTORY_ITEMS) {
- addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW);
+ addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur);
}
if (mNumHistoryItems >= MAX_HISTORY_ITEMS) {
@@ -3760,7 +3760,7 @@
}
}
- addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE);
+ addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
}
public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index 74dbaba..f05bd32 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -139,6 +139,10 @@
* @see com.android.server.wm.WindowManagerService#inputMethodClientHasFocus(IInputMethodClient)
*/
int ERROR_NOT_IME_TARGET_WINDOW = 11;
+ /**
+ * Indicates that focused view in the current window is not an editor.
+ */
+ int ERROR_NO_EDITOR = 12;
}
@ResultCode
@@ -258,6 +262,8 @@
return "ERROR_NULL";
case ResultCode.ERROR_NO_IME:
return "ERROR_NO_IME";
+ case ResultCode.ERROR_NO_EDITOR:
+ return "ERROR_NO_EDITOR";
case ResultCode.ERROR_INVALID_PACKAGE_NAME:
return "ERROR_INVALID_PACKAGE_NAME";
case ResultCode.ERROR_SYSTEM_NOT_READY:
@@ -288,6 +294,10 @@
*/
public static final InputBindResult NO_IME = error(ResultCode.ERROR_NO_IME);
/**
+ * Predefined error object for {@link ResultCode#NO_EDITOR}.
+ */
+ public static final InputBindResult NO_EDITOR = error(ResultCode.ERROR_NO_EDITOR);
+ /**
* Predefined error object for {@link ResultCode#ERROR_INVALID_PACKAGE_NAME}.
*/
public static final InputBindResult INVALID_PACKAGE_NAME =
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f627aaa..deefddb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3861,6 +3861,7 @@
android:excludeFromRecents="true"
android:label="@string/user_owner_label"
android:exported="true"
+ android:visibleToInstantApps="true"
>
</activity>
<activity-alias android:name="com.android.internal.app.ForwardIntentToParent"
diff --git a/core/res/res/xml/color_extraction.xml b/core/res/res/xml/color_extraction.xml
index 7d52b20..93ab0ff 100644
--- a/core/res/res/xml/color_extraction.xml
+++ b/core/res/res/xml/color_extraction.xml
@@ -257,52 +257,58 @@
<!-- Red Orange -->
<range h="20, 40"
s="0.7, 1"
- l="0.28, 0.643"/>
+ l="0.2, 0.643"/>
<range h="20, 40"
s="0.3, 0.7"
l="0.414, 0.561"/>
<range h="20, 40"
- s="0, 3"
+ s="0, 0.3"
l="0.343, 0.584"/>
<!-- Orange -->
<range h="40, 60"
s="0.7, 1"
- l="0.173, 0.349"/>
+ l="0.173, 0.38"/>
<range h="40, 60"
s="0.3, 0.7"
l="0.233, 0.427"/>
<range h="40, 60"
s="0, 0.3"
- l="0.231, 0.484"/>
+ l="0.231, 0.48"/>
<!-- Yellow 60 -->
<range h="60, 80"
s="0.7, 1"
- l="0.488, 0.737"/>
+ l="0.15, 0.40"/>
<range h="60, 80"
s="0.3, 0.7"
- l="0.673, 0.837"/>
+ l="0.15, 0.42"/>
+ <range h="60, 80"
+ s="0, 0.3"
+ l="0.35, 0.57"/>
<!-- Yellow Green 80 -->
<range h="80, 100"
s="0.7, 1"
- l="0.469, 0.61"/>
+ l="0.36, 0.65"/>
+ <range h="80, 100"
+ s="0.3, 0.7"
+ l="0.48, 0.57"/>
<!-- Yellow green 100 -->
<range h="100, 120"
s="0.7, 1"
- l="0.388, 0.612"/>
+ l="0.388, 0.67"/>
<range h="100, 120"
s="0.3, 0.7"
- l="0.424, 0.541"/>
+ l="0.424, 0.58"/>
<!-- Green -->
<range h="120, 140"
s="0.7, 1"
- l="0.375, 0.52"/>
+ l="0.37, 0.65"/>
<range h="120, 140"
s="0.3, 0.7"
- l="0.435, 0.524"/>
+ l="0.435, 0.58"/>
<!-- Green Blue 140 -->
<range h="140, 160"
s="0.7, 1"
- l="0.496, 0.641"/>
+ l="0.43, 0.641"/>
<!-- Seaoam -->
<range h="160, 180"
s="0.7, 1"
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 3e62183..e1f95a3 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -109,6 +109,8 @@
Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS,
Settings.Global.ANIMATOR_DURATION_SCALE,
Settings.Global.ANOMALY_DETECTION_CONSTANTS,
+ Settings.Global.ANOMALY_CONFIG,
+ Settings.Global.ANOMALY_CONFIG_VERSION,
Settings.Global.APN_DB_UPDATE_CONTENT_URL,
Settings.Global.APN_DB_UPDATE_METADATA_URL,
Settings.Global.APP_IDLE_CONSTANTS,
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
index d8e1fc1..b48a46b 100644
--- a/core/tests/overlaytests/host/Android.mk
+++ b/core/tests/overlaytests/host/Android.mk
@@ -24,7 +24,11 @@
OverlayHostTests_BadSignatureOverlay \
OverlayHostTests_PlatformSignatureStaticOverlay \
OverlayHostTests_PlatformSignatureOverlay \
- OverlayHostTests_PlatformSignatureOverlayV2
+ OverlayHostTests_UpdateOverlay \
+ OverlayHostTests_FrameworkOverlayV1 \
+ OverlayHostTests_FrameworkOverlayV2 \
+ OverlayHostTests_AppOverlayV1 \
+ OverlayHostTests_AppOverlayV2
include $(BUILD_HOST_JAVA_LIBRARY)
# Include to build test-apps.
diff --git a/core/tests/overlaytests/host/AndroidTest.xml b/core/tests/overlaytests/host/AndroidTest.xml
index 6884623..1f750a8 100644
--- a/core/tests/overlaytests/host/AndroidTest.xml
+++ b/core/tests/overlaytests/host/AndroidTest.xml
@@ -18,6 +18,11 @@
<option name="test-tag" value="OverlayHostTests" />
<option name="test-suite-tag" value="apct" />
+ <!-- Install the device tests that will be used to verify things on device. -->
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="OverlayHostTests_UpdateOverlay.apk" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.HostTest">
<option name="class" value="com.android.server.om.hosttest.InstallOverlayTests" />
</test>
diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
index 5093710..bf91a16 100644
--- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
+++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
@@ -23,14 +23,48 @@
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(DeviceJUnit4ClassRunner.class)
public class InstallOverlayTests extends BaseHostJUnit4Test {
-
- private static final String OVERLAY_PACKAGE_NAME =
+ private static final String SIG_OVERLAY_PACKAGE_NAME =
"com.android.server.om.hosttest.signature_overlay";
+ private static final String APP_OVERLAY_PACKAGE_NAME =
+ "com.android.server.om.hosttest.app_overlay";
+ private static final String FRAMEWORK_OVERLAY_PACKAGE_NAME =
+ "com.android.server.om.hosttest.framework_overlay";
+ private static final String[] ALL_PACKAGES = new String[] {
+ SIG_OVERLAY_PACKAGE_NAME, APP_OVERLAY_PACKAGE_NAME, FRAMEWORK_OVERLAY_PACKAGE_NAME
+ };
+
+ private static final String DEVICE_TEST_PKG =
+ "com.android.server.om.hosttest.update_overlay_test";
+ private static final String DEVICE_TEST_CLS = DEVICE_TEST_PKG + ".UpdateOverlayTest";
+
+ @Before
+ public void ensureNoOverlays() throws Exception {
+ // Make sure we're starting with a clean slate.
+ for (String pkg : ALL_PACKAGES) {
+ assertFalse(pkg + " should not be installed", isPackageInstalled(pkg));
+ assertFalse(pkg + " should not be registered with overlay manager service",
+ overlayManagerContainsPackage(pkg));
+ }
+ }
+
+ /*
+ For some reason, SuiteApkInstaller is *not* uninstalling overlays, even though #installPackage()
+ claims it will auto-clean.
+ TODO(b/72877546): Remove when auto-clean is fixed.
+ */
+ @After
+ public void uninstallOverlays() throws Exception {
+ for (String pkg : ALL_PACKAGES) {
+ uninstallPackage(pkg);
+ }
+ }
@Test
public void failToInstallNonPlatformSignedOverlay() throws Exception {
@@ -40,7 +74,7 @@
} catch (Exception e) {
// Expected.
}
- assertFalse(overlayManagerContainsPackage());
+ assertFalse(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME));
}
@Test
@@ -51,28 +85,64 @@
} catch (Exception e) {
// Expected.
}
- assertFalse(overlayManagerContainsPackage());
+ assertFalse(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME));
}
@Test
- public void succeedToInstallPlatformSignedOverlay() throws Exception {
+ public void installPlatformSignedOverlay() throws Exception {
installPackage("OverlayHostTests_PlatformSignatureOverlay.apk");
- assertTrue(overlayManagerContainsPackage());
+ assertTrue(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME));
}
@Test
- public void succeedToInstallPlatformSignedOverlayAndUpdate() throws Exception {
- installPackage("OverlayHostTests_PlatformSignatureOverlay.apk");
- assertTrue(overlayManagerContainsPackage());
- assertEquals("v1", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName());
+ public void installPlatformSignedAppOverlayAndUpdate() throws Exception {
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS, "expectAppResource"));
- installPackage("OverlayHostTests_PlatformSignatureOverlayV2.apk");
- assertTrue(overlayManagerContainsPackage());
- assertEquals("v2", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName());
+ installPackage("OverlayHostTests_AppOverlayV1.apk");
+ setOverlayEnabled(APP_OVERLAY_PACKAGE_NAME, true);
+ assertTrue(overlayManagerContainsPackage(APP_OVERLAY_PACKAGE_NAME));
+ assertEquals("v1", getDevice()
+ .getAppPackageInfo(APP_OVERLAY_PACKAGE_NAME)
+ .getVersionName());
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS,
+ "expectAppOverlayV1Resource"));
+
+ installPackage("OverlayHostTests_AppOverlayV2.apk");
+ assertTrue(overlayManagerContainsPackage(APP_OVERLAY_PACKAGE_NAME));
+ assertEquals("v2", getDevice()
+ .getAppPackageInfo(APP_OVERLAY_PACKAGE_NAME)
+ .getVersionName());
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS,
+ "expectAppOverlayV2Resource"));
}
- private boolean overlayManagerContainsPackage() throws Exception {
- return getDevice().executeShellCommand("cmd overlay list")
- .contains(OVERLAY_PACKAGE_NAME);
+ @Test
+ public void installPlatformSignedFrameworkOverlayAndUpdate() throws Exception {
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS, "expectAppResource"));
+
+ installPackage("OverlayHostTests_FrameworkOverlayV1.apk");
+ setOverlayEnabled(FRAMEWORK_OVERLAY_PACKAGE_NAME, true);
+ assertTrue(overlayManagerContainsPackage(FRAMEWORK_OVERLAY_PACKAGE_NAME));
+ assertEquals("v1", getDevice()
+ .getAppPackageInfo(FRAMEWORK_OVERLAY_PACKAGE_NAME)
+ .getVersionName());
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS,
+ "expectFrameworkOverlayV1Resource"));
+
+ installPackage("OverlayHostTests_FrameworkOverlayV2.apk");
+ assertTrue(overlayManagerContainsPackage(FRAMEWORK_OVERLAY_PACKAGE_NAME));
+ assertEquals("v2", getDevice()
+ .getAppPackageInfo(FRAMEWORK_OVERLAY_PACKAGE_NAME)
+ .getVersionName());
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS,
+ "expectFrameworkOverlayV2Resource"));
+ }
+
+ private void setOverlayEnabled(String pkg, boolean enabled) throws Exception {
+ getDevice().executeShellCommand("cmd overlay " + (enabled ? "enable " : "disable ") + pkg);
+ }
+
+ private boolean overlayManagerContainsPackage(String pkg) throws Exception {
+ return getDevice().executeShellCommand("cmd overlay list").contains(pkg);
}
}
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
index b051a82..4249549 100644
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
@@ -40,13 +40,4 @@
LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
include $(BUILD_PACKAGE)
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlayV2
-LOCAL_COMPATIBILITY_SUITE := general-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-include $(BUILD_PACKAGE)
-
my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
new file mode 100644
index 0000000..bd6d73d
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
@@ -0,0 +1,73 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+include $(BUILD_PACKAGE)
+
+my_package_prefix := com.android.server.om.hosttest.framework_overlay
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
+LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v1/res
+LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
+LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v2/res
+LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
+include $(BUILD_PACKAGE)
+
+my_package_prefix := com.android.server.om.hosttest.app_overlay
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
+LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
+LOCAL_MANIFEST_FILE := app/AndroidManifest.xml
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
+LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
+LOCAL_MANIFEST_FILE := app/AndroidManifest.xml
+include $(BUILD_PACKAGE)
+
+my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..06077a7
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.om.hosttest.update_overlay_test">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.om.hosttest.update_overlay_test"
+ android:label="Update Overlay Test"/>
+</manifest>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/AndroidManifest.xml
new file mode 100644
index 0000000..73804eb
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.om.hosttest.app_overlay">
+ <overlay android:targetPackage="com.android.server.om.hosttest.update_overlay_test" />
+</manifest>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v1/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v1/res/values/values.xml
new file mode 100644
index 0000000..63f85c2
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v1/res/values/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="app_resource">App Resource Overlay V1</string>
+</resources>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/res/values/values.xml
new file mode 100644
index 0000000..fa4a697
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/res/values/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="app_resource">App Resource Overlay V2</string>
+</resources>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/AndroidManifest.xml
new file mode 100644
index 0000000..8c8fe94
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.om.hosttest.framework_overlay">
+ <overlay android:targetPackage="android" />
+</manifest>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v1/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v1/res/values/values.xml
new file mode 100644
index 0000000..fedb2c6
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v1/res/values/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="ok">Framework Overlay V1</string>
+</resources>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v2/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v2/res/values/values.xml
new file mode 100644
index 0000000..8aebf483
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v2/res/values/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="ok">Framework Overlay V2</string>
+</resources>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/res/values/values.xml
new file mode 100644
index 0000000..7393166
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/res/values/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="app_resource">App Resource</string>
+</resources>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java b/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java
new file mode 100644
index 0000000..d46bb37
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.server.om.hosttest.update_overlay_test;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UpdateOverlayTest {
+ private Resources mResources;
+
+ @Before
+ public void setUp() throws Exception {
+ final Configuration defaultLocaleConfiguration = new Configuration();
+ defaultLocaleConfiguration.setLocale(Locale.US);
+ mResources = InstrumentationRegistry
+ .getInstrumentation()
+ .getContext()
+ .createConfigurationContext(defaultLocaleConfiguration)
+ .getResources();
+ }
+
+ @Test
+ public void expectAppResource() throws Exception {
+ assertEquals("App Resource", mResources.getString(R.string.app_resource));
+ }
+
+ @Test
+ public void expectAppOverlayV1Resource() throws Exception {
+ assertEquals("App Resource Overlay V1", mResources.getString(R.string.app_resource));
+ }
+
+ @Test
+ public void expectAppOverlayV2Resource() throws Exception {
+ assertEquals("App Resource Overlay V2", mResources.getString(R.string.app_resource));
+ }
+
+ @Test
+ public void expectFrameworkOverlayResource() throws Exception {
+ assertEquals("OK", mResources.getString(android.R.string.ok));
+ }
+
+ @Test
+ public void expectFrameworkOverlayV1Resource() throws Exception {
+ assertEquals("Framework Overlay V1", mResources.getString(android.R.string.ok));
+ }
+
+ @Test
+ public void expectFrameworkOverlayV2Resource() throws Exception {
+ assertEquals("Framework Overlay V2", mResources.getString(android.R.string.ok));
+ }
+}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 04c5295..f41267e 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -60,6 +60,7 @@
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
@@ -801,36 +802,18 @@
* @return The new typeface.
*/
public static Typeface createFromAsset(AssetManager mgr, String path) {
- if (path == null) {
- throw new NullPointerException(); // for backward compatibility
- }
- synchronized (sDynamicCacheLock) {
- Typeface typeface = new Builder(mgr, path).build();
- if (typeface != null) return typeface;
+ Preconditions.checkNotNull(path); // for backward compatibility
+ Preconditions.checkNotNull(mgr);
- final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
- null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
- DEFAULT_FAMILY);
- typeface = sDynamicTypefaceCache.get(key);
- if (typeface != null) return typeface;
-
- final FontFamily fontFamily = new FontFamily();
- if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */,
- 0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
- null /* axes */)) {
- if (!fontFamily.freeze()) {
- return Typeface.DEFAULT;
- }
- final FontFamily[] families = { fontFamily };
- typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
- sDynamicTypefaceCache.put(key, typeface);
- return typeface;
- } else {
- fontFamily.abortCreation();
- }
+ Typeface typeface = new Builder(mgr, path).build();
+ if (typeface != null) return typeface;
+ // check if the file exists, and throw an exception for backward compatibility
+ try (InputStream inputStream = mgr.open(path)) {
+ } catch (IOException e) {
+ throw new RuntimeException("Font asset not found " + path);
}
- throw new RuntimeException("Font asset not found " + path);
+
+ return Typeface.DEFAULT;
}
/**
@@ -848,13 +831,22 @@
/**
* Create a new typeface from the specified font file.
*
- * @param path The path to the font data.
+ * @param file The path to the font data.
* @return The new typeface.
*/
- public static Typeface createFromFile(@Nullable File path) {
+ public static Typeface createFromFile(@Nullable File file) {
// For the compatibility reasons, leaving possible NPE here.
// See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
- return createFromFile(path.getAbsolutePath());
+
+ Typeface typeface = new Builder(file).build();
+ if (typeface != null) return typeface;
+
+ // check if the file exists, and throw an exception for backward compatibility
+ if (!file.exists()) {
+ throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
+ }
+
+ return Typeface.DEFAULT;
}
/**
@@ -864,19 +856,8 @@
* @return The new typeface.
*/
public static Typeface createFromFile(@Nullable String path) {
- final FontFamily fontFamily = new FontFamily();
- if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) {
- if (!fontFamily.freeze()) {
- return Typeface.DEFAULT;
- }
- FontFamily[] families = { fontFamily };
- return createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
- } else {
- fontFamily.abortCreation();
- }
- throw new RuntimeException("Font not found " + path);
+ Preconditions.checkNotNull(path); // for backward compatibility
+ return createFromFile(new File(path));
}
/**
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index c33dce1..d3c6edd 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -236,6 +236,62 @@
*/
public static final String GNSS_HARDWARE_MODEL_NAME_UNKNOWN = "Model Name Unknown";
+ /**
+ * Broadcast intent action for Settings app to inject a footer at the bottom of location
+ * settings.
+ *
+ * <p>This broadcast is used for two things:
+ * <ol>
+ * <li>For receivers to inject a footer with provided text. This is for use only by apps
+ * that are included in the system image. </li>
+ * <li>For receivers to know their footer is injected under location settings.</li>
+ * </ol>
+ *
+ * <p>To inject a footer to location settings, you must declare a broadcast receiver of
+ * {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION} in the manifest as so:
+ * <pre>
+ * <receiver android:name="com.example.android.footer.MyFooterInjector">
+ * <intent-filter>
+ * <action android:name="com.android.settings.location.INJECT_FOOTER" />
+ * </intent-filter>
+ * <meta-data
+ * android:name="com.android.settings.location.FOOTER_STRING"
+ * android:resource="@string/my_injected_footer_string" />
+ * </receiver>
+ * </pre>
+ *
+ * <p>On entering location settings, Settings app will send a
+ * {@link #SETTINGS_FOOTER_DISPLAYED_ACTION} broadcast to receivers whose footer is successfully
+ * injected. On leaving location settings, the footer becomes not visible to users. Settings app
+ * will send a {@link #SETTINGS_FOOTER_REMOVED_ACTION} broadcast to those receivers.
+ *
+ * @hide
+ */
+ public static final String SETTINGS_FOOTER_DISPLAYED_ACTION =
+ "com.android.settings.location.DISPLAYED_FOOTER";
+
+ /**
+ * Broadcast intent action when location settings footer is not visible to users.
+ *
+ * <p>See {@link #SETTINGS_FOOTER_DISPLAYED_ACTION} for more detail on how to use.
+ *
+ * @hide
+ */
+ public static final String SETTINGS_FOOTER_REMOVED_ACTION =
+ "com.android.settings.location.REMOVED_FOOTER";
+
+ /**
+ * Metadata name for {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION} broadcast
+ * receivers to specify a string resource id as location settings footer text. This is for use
+ * only by apps that are included in the system image.
+ *
+ * <p>See {@link #SETTINGS_FOOTER_DISPLAYED_ACTION} for more detail on how to use.
+ *
+ * @hide
+ */
+ public static final String METADATA_SETTINGS_FOOTER_STRING =
+ "com.android.settings.location.FOOTER_STRING";
+
// Map from LocationListeners to their associated ListenerTransport objects
private HashMap<LocationListener,ListenerTransport> mListeners =
new HashMap<LocationListener,ListenerTransport>();
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index 5d0c8e2..5467a69 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -38,6 +38,10 @@
private int mLossReceived;
private int mFlags;
+ // generation count for the validity of a request/response async exchange between
+ // external focus policy and MediaFocusControl
+ private long mGenCount = -1;
+
/**
* Class constructor
@@ -61,6 +65,16 @@
mSdkTarget = sdk;
}
+ /** @hide */
+ public void setGen(long g) {
+ mGenCount = g;
+ }
+
+ /** @hide */
+ public long getGen() {
+ return mGenCount;
+ }
+
/**
* The audio attributes for the audio focus request.
@@ -128,6 +142,7 @@
dest.writeInt(mLossReceived);
dest.writeInt(mFlags);
dest.writeInt(mSdkTarget);
+ dest.writeLong(mGenCount);
}
@Override
@@ -168,6 +183,8 @@
if (mSdkTarget != other.mSdkTarget) {
return false;
}
+ // mGenCount is not used to verify equality between two focus holds as multiple requests
+ // (hence of different generations) could correspond to the same hold
return true;
}
@@ -175,7 +192,7 @@
= new Parcelable.Creator<AudioFocusInfo>() {
public AudioFocusInfo createFromParcel(Parcel in) {
- return new AudioFocusInfo(
+ final AudioFocusInfo afi = new AudioFocusInfo(
AudioAttributes.CREATOR.createFromParcel(in), //AudioAttributes aa
in.readInt(), // int clientUid
in.readString(), //String clientId
@@ -185,6 +202,8 @@
in.readInt(), //int flags
in.readInt() //int sdkTarget
);
+ afi.setGen(in.readLong());
+ return afi;
}
public AudioFocusInfo[] newArray(int size) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index bf51d97..0be54ec 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -32,6 +32,7 @@
import android.content.Context;
import android.content.Intent;
import android.media.audiopolicy.AudioPolicy;
+import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionLegacyHelper;
@@ -54,10 +55,13 @@
import android.util.Slog;
import android.view.KeyEvent;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -2338,6 +2342,20 @@
}
}
}
+
+ @Override
+ public void dispatchFocusResultFromExtPolicy(int requestResult, String clientId) {
+ synchronized (mFocusRequestsLock) {
+ // TODO use generation counter as the key instead
+ final BlockingFocusResultReceiver focusReceiver =
+ mFocusRequestsAwaitingResult.remove(clientId);
+ if (focusReceiver != null) {
+ focusReceiver.notifyResult(requestResult);
+ } else {
+ Log.e(TAG, "dispatchFocusResultFromExtPolicy found no result receiver");
+ }
+ }
+ }
};
private String getIdForAudioFocusListener(OnAudioFocusChangeListener l) {
@@ -2390,6 +2408,40 @@
*/
public static final int AUDIOFOCUS_REQUEST_DELAYED = 2;
+ /** @hide */
+ @IntDef(flag = false, prefix = "AUDIOFOCUS_REQUEST", value = {
+ AUDIOFOCUS_REQUEST_FAILED,
+ AUDIOFOCUS_REQUEST_GRANTED,
+ AUDIOFOCUS_REQUEST_DELAYED }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusRequestResult {}
+
+ /**
+ * @hide
+ * code returned when a synchronous focus request on the client-side is to be blocked
+ * until the external audio focus policy decides on the response for the client
+ */
+ public static final int AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY = 100;
+
+ /**
+ * Timeout duration in ms when waiting on an external focus policy for the result for a
+ * focus request
+ */
+ private static final int EXT_FOCUS_POLICY_TIMEOUT_MS = 200;
+
+ private static final String FOCUS_CLIENT_ID_STRING = "android_audio_focus_client_id";
+
+ private final Object mFocusRequestsLock = new Object();
+ /**
+ * Map of all receivers of focus request results, one per unresolved focus request.
+ * Receivers are added before sending the request to the external focus policy,
+ * and are removed either after receiving the result, or after the timeout.
+ * This variable is lazily initialized.
+ */
+ @GuardedBy("mFocusRequestsLock")
+ private HashMap<String, BlockingFocusResultReceiver> mFocusRequestsAwaitingResult;
+
/**
* Request audio focus.
@@ -2656,18 +2708,100 @@
// some tests don't have a Context
sdk = Build.VERSION.SDK_INT;
}
- try {
- status = service.requestAudioFocus(afr.getAudioAttributes(),
- afr.getFocusGain(), mICallBack,
- mAudioFocusDispatcher,
- getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener()),
- getContext().getOpPackageName() /* package name */, afr.getFlags(),
- ap != null ? ap.cb() : null,
- sdk);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+
+ final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
+ final BlockingFocusResultReceiver focusReceiver;
+ synchronized (mFocusRequestsLock) {
+ try {
+ // TODO status contains result and generation counter for ext policy
+ status = service.requestAudioFocus(afr.getAudioAttributes(),
+ afr.getFocusGain(), mICallBack,
+ mAudioFocusDispatcher,
+ clientId,
+ getContext().getOpPackageName() /* package name */, afr.getFlags(),
+ ap != null ? ap.cb() : null,
+ sdk);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
+ // default path with no external focus policy
+ return status;
+ }
+ if (mFocusRequestsAwaitingResult == null) {
+ mFocusRequestsAwaitingResult =
+ new HashMap<String, BlockingFocusResultReceiver>(1);
+ }
+ focusReceiver = new BlockingFocusResultReceiver(clientId);
+ mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
}
- return status;
+ focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);
+ if (DEBUG && !focusReceiver.receivedResult()) {
+ Log.e(TAG, "requestAudio response from ext policy timed out, denying request");
+ }
+ synchronized (mFocusRequestsLock) {
+ mFocusRequestsAwaitingResult.remove(clientId);
+ }
+ return focusReceiver.requestResult();
+ }
+
+ // helper class that abstracts out the handling of spurious wakeups in Object.wait()
+ private static final class SafeWaitObject {
+ private boolean mQuit = false;
+
+ public void safeNotify() {
+ synchronized (this) {
+ mQuit = true;
+ this.notify();
+ }
+ }
+
+ public void safeWait(long millis) throws InterruptedException {
+ final long timeOutTime = java.lang.System.currentTimeMillis() + millis;
+ synchronized (this) {
+ while (!mQuit) {
+ final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis();
+ if (timeToWait < 0) { break; }
+ this.wait(timeToWait);
+ }
+ }
+ }
+ }
+
+ private static final class BlockingFocusResultReceiver {
+ private final SafeWaitObject mLock = new SafeWaitObject();
+ @GuardedBy("mLock")
+ private boolean mResultReceived = false;
+ // request denied by default (e.g. timeout)
+ private int mFocusRequestResult = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ private final String mFocusClientId;
+
+ BlockingFocusResultReceiver(String clientId) {
+ mFocusClientId = clientId;
+ }
+
+ boolean receivedResult() { return mResultReceived; }
+ int requestResult() { return mFocusRequestResult; }
+
+ void notifyResult(int requestResult) {
+ synchronized (mLock) {
+ mResultReceived = true;
+ mFocusRequestResult = requestResult;
+ mLock.safeNotify();
+ }
+ }
+
+ public void waitForResult(long timeOutMs) {
+ synchronized (mLock) {
+ if (mResultReceived) {
+ // the result was received before waiting
+ return;
+ }
+ try {
+ mLock.safeWait(timeOutMs);
+ } catch (InterruptedException e) { }
+ }
+ }
}
/**
@@ -2714,6 +2848,32 @@
/**
* @hide
+ * Set the result to the audio focus request received through
+ * {@link AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}.
+ * @param afi the information about the focus requester
+ * @param requestResult the result to the focus request to be passed to the requester
+ * @param ap a valid registered {@link AudioPolicy} configured as a focus policy.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void setFocusRequestResult(@NonNull AudioFocusInfo afi,
+ @FocusRequestResult int requestResult, @NonNull AudioPolicy ap) {
+ if (afi == null) {
+ throw new IllegalArgumentException("Illegal null AudioFocusInfo");
+ }
+ if (ap == null) {
+ throw new IllegalArgumentException("Illegal null AudioPolicy");
+ }
+ final IAudioService service = getService();
+ try {
+ service.setFocusRequestResultFromExtPolicy(afi, requestResult, ap.cb());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
* Notifies an application with a focus listener of gain or loss of audio focus.
* This method can only be used by owners of an {@link AudioPolicy} configured with
* {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to true.
diff --git a/media/java/android/media/IAudioFocusDispatcher.aidl b/media/java/android/media/IAudioFocusDispatcher.aidl
index 09575f7..3b33c5b 100644
--- a/media/java/android/media/IAudioFocusDispatcher.aidl
+++ b/media/java/android/media/IAudioFocusDispatcher.aidl
@@ -25,4 +25,6 @@
void dispatchAudioFocusChange(int focusChange, String clientId);
+ void dispatchFocusResultFromExtPolicy(int requestResult, String clientId);
+
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 88d0a60..cd4143c 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -207,5 +207,8 @@
int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(in BluetoothDevice device,
int state, int profile, boolean suppressNoisyIntent);
+ oneway void setFocusRequestResultFromExtPolicy(in AudioFocusInfo afi, int requestResult,
+ in IAudioPolicyCallback pcb);
+
// WARNING: read warning at top of file, it is recommended to add new methods at the end
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 4de731a..2190635 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -463,9 +463,9 @@
* Only ever called if the {@link AudioPolicy} was built with
* {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}.
* @param afi information about the focus request and the requester
- * @param requestResult the result that was returned synchronously by the framework to the
- * application, {@link #AUDIOFOCUS_REQUEST_FAILED},or
- * {@link #AUDIOFOCUS_REQUEST_DELAYED}.
+ * @param requestResult deprecated after the addition of
+ * {@link AudioManager#setFocusRequestResult(AudioFocusInfo, int, AudioPolicy)}
+ * in Android P, always equal to {@link #AUDIOFOCUS_REQUEST_GRANTED}.
*/
public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {}
/**
@@ -534,7 +534,7 @@
sendMsg(MSG_FOCUS_REQUEST, afi, requestResult);
if (DEBUG) {
Log.v(TAG, "notifyAudioFocusRequest: pack=" + afi.getPackageName() + " client="
- + afi.getClientId() + "reqRes=" + requestResult);
+ + afi.getClientId() + " gen=" + afi.getGen());
}
}
diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk
index 741db34..3c2ca2d 100644
--- a/packages/SettingsLib/common.mk
+++ b/packages/SettingsLib/common.mk
@@ -16,11 +16,11 @@
ifeq ($(LOCAL_USE_AAPT2),true)
LOCAL_STATIC_JAVA_LIBRARIES += \
android-support-annotations \
- android-arch-lifecycle-common
+ apptoolkit-lifecycle-common
LOCAL_STATIC_ANDROID_LIBRARIES += \
android-support-v4 \
- android-arch-lifecycle-runtime \
+ apptoolkit-lifecycle-runtime \
android-support-v7-recyclerview \
android-support-v7-preference \
android-support-v7-appcompat \
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index adb4dbf..85a579d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3017,7 +3017,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 153;
+ private static final int SETTINGS_VERSION = 154;
private final int mUserId;
@@ -3627,6 +3627,23 @@
currentVersion = 153;
}
+ if (currentVersion == 153) {
+ // Version 154: Read notification badge configuration from config.
+ // If user has already set the value, don't do anything.
+ final SettingsState systemSecureSettings = getSecureSettingsLocked(userId);
+ final Setting showNotificationBadges = systemSecureSettings.getSettingLocked(
+ Settings.Secure.NOTIFICATION_BADGING);
+ if (showNotificationBadges.isNull()) {
+ final boolean defaultValue = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_notificationBadging);
+ systemSecureSettings.insertSettingLocked(
+ Secure.NOTIFICATION_BADGING,
+ defaultValue ? "1" : "0",
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ currentVersion = 154;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 1897171..1da50ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -88,7 +88,8 @@
* Layout is happening from end -> start
*/
private void calculateIconTranslations() {
- float translationX = getWidth();
+ float width = getWidth();
+ float translationX = width;
float contentStart = getPaddingStart();
int childCount = getChildCount();
// Underflow === don't show content until that index
@@ -133,6 +134,15 @@
}
}
}
+
+ // Stole this from NotificationIconContainer. Not optimal but keeps the layout logic clean
+ if (isLayoutRtl()) {
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ ViewState state = getViewStateFromChild(child);
+ state.xTranslation = width - state.xTranslation - child.getWidth();
+ }
+ }
}
private void applyIconStates() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index 5f763a4..1204dda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -86,13 +86,13 @@
mHeadsUpManager);
gutsManager.setUpWithPresenter(mPresenter, entryManager, mListContainer,
mCheckSaveListener, mOnClickListener);
- notificationListener.setUpWithPresenter(mPresenter, entryManager);
notificationLogger.setUpWithEntryManager(entryManager, mListContainer);
mediaManager.setUpWithPresenter(mPresenter, entryManager);
remoteInputManager.setUpWithPresenter(mPresenter, entryManager, mRemoteInputManagerCallback,
mDelegate);
lockscreenUserManager.setUpWithPresenter(mPresenter, entryManager);
viewHierarchyManager.setUpWithPresenter(mPresenter, entryManager, mListContainer);
+ notificationListener.setUpWithPresenter(mPresenter, entryManager);
assertFalse(mDependency.hasInstantiatedDependency(StatusBarWindowManager.class));
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 297dcf1..989a7b5 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -861,8 +861,13 @@
pw.print(prefix); pw.print("User: "); pw.println(mUserId);
pw.print(prefix); pw.print("UID: "); pw.println(getServiceUidLocked());
- pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null
- ? mInfo.getServiceInfo().getComponentName() : null);
+ pw.print(prefix); pw.print("Autofill Service Info: ");
+ if (mInfo == null) {
+ pw.println("N/A");
+ } else {
+ pw.println();
+ mInfo.dump(prefix2, pw);
+ }
pw.print(prefix); pw.print("Component from settings: ");
pw.println(getComponentNameFromSettings());
pw.print(prefix); pw.print("Default component: ");
@@ -870,6 +875,7 @@
pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
pw.print(prefix); pw.print("Field classification enabled: ");
pw.println(isFieldClassificationEnabledLocked());
+ pw.print(prefix); pw.print("Compat pkgs: "); pw.println(getWhitelistedCompatModePackages());
pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
@@ -996,14 +1002,18 @@
}
if (!Build.IS_ENG) {
// TODO: Build a map and watch for settings changes (this is called on app start)
- final String whiteListedPackages = Settings.Global.getString(
- mContext.getContentResolver(),
- Settings.Global.AUTOFILL_COMPAT_ALLOWED_PACKAGES);
+ final String whiteListedPackages = getWhitelistedCompatModePackages();
return whiteListedPackages != null && whiteListedPackages.contains(packageName);
}
return true;
}
+ private String getWhitelistedCompatModePackages() {
+ return Settings.Global.getString(
+ mContext.getContentResolver(),
+ Settings.Global.AUTOFILL_COMPAT_ALLOWED_PACKAGES);
+ }
+
private void sendStateToClients(boolean resetClient) {
final RemoteCallbackList<IAutoFillManagerClient> clients;
final int userClientCount;
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index f49cd67..b4af432 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -58,6 +58,7 @@
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Pair;
@@ -79,7 +80,6 @@
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Random;
@@ -224,10 +224,12 @@
interface Stats {
int REBATCH_ALL_ALARMS = 0;
+ int REORDER_ALARMS_FOR_STANDBY = 1;
}
private final StatLogger mStatLogger = new StatLogger(new String[] {
"REBATCH_ALL_ALARMS",
+ "REORDER_ALARMS_FOR_STANDBY",
});
/**
@@ -522,6 +524,10 @@
return newStart;
}
+ boolean remove(Alarm alarm) {
+ return remove(a -> (a == alarm));
+ }
+
boolean remove(Predicate<Alarm> predicate) {
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
@@ -741,6 +747,23 @@
return (index == 0);
}
+ private void insertAndBatchAlarmLocked(Alarm alarm) {
+ final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1
+ : attemptCoalesceLocked(alarm.whenElapsed, alarm.maxWhenElapsed);
+
+ if (whichBatch < 0) {
+ addBatchLocked(mAlarmBatches, new Batch(alarm));
+ } else {
+ final Batch batch = mAlarmBatches.get(whichBatch);
+ if (batch.add(alarm)) {
+ // The start time of this batch advanced, so batch ordering may
+ // have just been broken. Move it to where it now belongs.
+ mAlarmBatches.remove(whichBatch);
+ addBatchLocked(mAlarmBatches, batch);
+ }
+ }
+ }
+
// Return the index of the matching batch, or -1 if none found.
int attemptCoalesceLocked(long whenElapsed, long maxWhen) {
final int N = mAlarmBatches.size();
@@ -794,7 +817,7 @@
}
void rebatchAllAlarmsLocked(boolean doValidate) {
- long start = mStatLogger.getTime();
+ final long start = mStatLogger.getTime();
final int oldCount =
getAlarmCount(mAlarmBatches) + ArrayUtils.size(mPendingWhileIdleAlarms);
final boolean oldHasTick = haveBatchesTimeTickAlarm(mAlarmBatches)
@@ -837,6 +860,44 @@
mStatLogger.logDurationStat(Stats.REBATCH_ALL_ALARMS, start);
}
+ /**
+ * Re-orders the alarm batches based on newly evaluated send times based on the current
+ * app-standby buckets
+ * @param targetPackages [Package, User] pairs for which alarms need to be re-evaluated,
+ * null indicates all
+ * @return True if there was any reordering done to the current list.
+ */
+ boolean reorderAlarmsBasedOnStandbyBuckets(ArraySet<Pair<String, Integer>> targetPackages) {
+ final long start = mStatLogger.getTime();
+ final ArrayList<Alarm> rescheduledAlarms = new ArrayList<>();
+
+ for (int batchIndex = mAlarmBatches.size() - 1; batchIndex >= 0; batchIndex--) {
+ final Batch batch = mAlarmBatches.get(batchIndex);
+ for (int alarmIndex = batch.size() - 1; alarmIndex >= 0; alarmIndex--) {
+ final Alarm alarm = batch.get(alarmIndex);
+ final Pair<String, Integer> packageUser =
+ Pair.create(alarm.sourcePackage, UserHandle.getUserId(alarm.creatorUid));
+ if (targetPackages != null && !targetPackages.contains(packageUser)) {
+ continue;
+ }
+ if (adjustDeliveryTimeBasedOnStandbyBucketLocked(alarm)) {
+ batch.remove(alarm);
+ rescheduledAlarms.add(alarm);
+ }
+ }
+ if (batch.size() == 0) {
+ mAlarmBatches.remove(batchIndex);
+ }
+ }
+ for (int i = 0; i < rescheduledAlarms.size(); i++) {
+ final Alarm a = rescheduledAlarms.get(i);
+ insertAndBatchAlarmLocked(a);
+ }
+
+ mStatLogger.logDurationStat(Stats.REORDER_ALARMS_FOR_STANDBY, start);
+ return rescheduledAlarms.size() > 0;
+ }
+
void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) {
a.when = a.origWhen;
long whenElapsed = convertToElapsed(a.when, a.type);
@@ -1442,18 +1503,32 @@
else return mConstants.APP_STANDBY_MIN_DELAYS[0];
}
- private void adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) {
+ /**
+ * Adjusts the alarm delivery time based on the current app standby bucket.
+ * @param alarm The alarm to adjust
+ * @return true if the alarm delivery time was updated.
+ * TODO: Reduce the number of calls to getAppStandbyBucket by batching the calls per
+ * {package, user} pairs
+ */
+ private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) {
if (alarm.alarmClock != null || UserHandle.isCore(alarm.creatorUid)) {
- return;
+ return false;
+ }
+ // TODO: short term fix for b/72816079, remove after a proper fix is in place
+ if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+ return false;
}
if (mAppStandbyParole) {
if (alarm.whenElapsed > alarm.requestedWhenElapsed) {
- // We did throttle this alarm earlier, restore original requirements
+ // We did defer this alarm earlier, restore original requirements
alarm.whenElapsed = alarm.requestedWhenElapsed;
alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed;
}
- return;
+ return true;
}
+ final long oldWhenElapsed = alarm.whenElapsed;
+ final long oldMaxWhenElapsed = alarm.maxWhenElapsed;
+
final String sourcePackage = alarm.sourcePackage;
final int sourceUserId = UserHandle.getUserId(alarm.creatorUid);
final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket(
@@ -1465,8 +1540,14 @@
final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
if (alarm.requestedWhenElapsed < minElapsed) {
alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
+ } else {
+ // app is now eligible to run alarms at the originally requested window.
+ // Restore original requirements in case they were changed earlier.
+ alarm.whenElapsed = alarm.requestedWhenElapsed;
+ alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed;
}
}
+ return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed);
}
private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
@@ -1521,21 +1602,7 @@
}
}
adjustDeliveryTimeBasedOnStandbyBucketLocked(a);
-
- int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0)
- ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed);
- if (whichBatch < 0) {
- Batch batch = new Batch(a);
- addBatchLocked(mAlarmBatches, batch);
- } else {
- Batch batch = mAlarmBatches.get(whichBatch);
- if (batch.add(a)) {
- // The start time of this batch advanced, so batch ordering may
- // have just been broken. Move it to where it now belongs.
- mAlarmBatches.remove(whichBatch);
- addBatchLocked(mAlarmBatches, batch);
- }
- }
+ insertAndBatchAlarmLocked(a);
if (a.alarmClock != null) {
mNextAlarmClockMayChange = true;
@@ -3138,7 +3205,7 @@
final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
pw.print(prefix); pw.print("tag="); pw.println(statsTag);
pw.print(prefix); pw.print("type="); pw.print(type);
- pw.print(" requestedWhenELapsed="); TimeUtils.formatDuration(
+ pw.print(" requestedWhenElapsed="); TimeUtils.formatDuration(
requestedWhenElapsed, nowELAPSED, pw);
pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed,
nowELAPSED, pw);
@@ -3391,28 +3458,19 @@
}
mPendingNonWakeupAlarms.clear();
}
- boolean needRebatch = false;
- final HashSet<String> triggerPackages = new HashSet<>();
- for (int i = triggerList.size() - 1; i >= 0; i--) {
- triggerPackages.add(triggerList.get(i).sourcePackage);
- }
- outer:
- for (int i = 0; i < mAlarmBatches.size(); i++) {
- final Batch batch = mAlarmBatches.get(i);
- for (int j = 0; j < batch.size(); j++) {
- if (triggerPackages.contains(batch.get(j))) {
- needRebatch = true;
- break outer;
- }
+ final ArraySet<Pair<String, Integer>> triggerPackages =
+ new ArraySet<>();
+ for (int i = 0; i < triggerList.size(); i++) {
+ final Alarm a = triggerList.get(i);
+ if (!UserHandle.isCore(a.creatorUid)) {
+ triggerPackages.add(Pair.create(
+ a.sourcePackage, UserHandle.getUserId(a.creatorUid)));
}
}
- if (needRebatch) {
- rebatchAllAlarmsLocked(false);
- } else {
- rescheduleKernelAlarmsLocked();
- updateNextAlarmClockLocked();
- }
deliverAlarmsLocked(triggerList, nowELAPSED);
+ reorderAlarmsBasedOnStandbyBuckets(triggerPackages);
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
}
}
@@ -3518,13 +3576,21 @@
case APP_STANDBY_PAROLE_CHANGED:
synchronized (mLock) {
mAppStandbyParole = (Boolean) msg.obj;
- rebatchAllAlarmsLocked(false);
+ if (reorderAlarmsBasedOnStandbyBuckets(null)) {
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
}
break;
case APP_STANDBY_BUCKET_CHANGED:
synchronized (mLock) {
- rebatchAllAlarmsLocked(false);
+ final ArraySet<Pair<String, Integer>> filterPackages = new ArraySet<>();
+ filterPackages.add(Pair.create((String) msg.obj, msg.arg1));
+ if (reorderAlarmsBasedOnStandbyBuckets(filterPackages)) {
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
}
break;
@@ -3751,7 +3817,8 @@
bucket);
}
mHandler.removeMessages(AlarmHandler.APP_STANDBY_BUCKET_CHANGED);
- mHandler.sendEmptyMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED);
+ mHandler.obtainMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED, userId, -1, packageName)
+ .sendToTarget();
}
@Override
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 2f42585..93f7f1d 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -2970,12 +2970,19 @@
break;
}
- if (!didStart && attribute != null) {
- if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
- || (controlFlags
- & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) {
- res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
- controlFlags, startInputReason);
+ if (!didStart) {
+ if (attribute != null) {
+ if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
+ || (controlFlags
+ & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) {
+ res = startInputUncheckedLocked(cs, inputContext, missingMethods,
+ attribute,
+ controlFlags, startInputReason);
+ } else {
+ res = InputBindResult.NO_EDITOR;
+ }
+ } else {
+ res = InputBindResult.NULL_EDITOR_INFO;
}
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2f7d4c1..14404f5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -84,6 +84,7 @@
import android.util.EventLog;
import android.util.PrintWriterPrinter;
import android.util.Slog;
+import android.util.StatsLog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -1094,6 +1095,8 @@
active.mNumActive++;
}
r.isForeground = true;
+ StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.userId, r.shortName,
+ StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER);
}
r.postNotification();
if (r.app != null) {
@@ -1109,6 +1112,8 @@
decActiveForegroundAppLocked(smap, r);
}
r.isForeground = false;
+ StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.userId, r.shortName,
+ StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
if (r.app != null) {
mAm.updateLruProcessLocked(r.app, false, null);
updateServiceForegroundLocked(r.app, true);
@@ -2533,7 +2538,10 @@
cancelForegroundNotificationLocked(r);
if (r.isForeground) {
decActiveForegroundAppLocked(smap, r);
+ StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.userId, r.shortName,
+ StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
}
+
r.isForeground = false;
r.foregroundId = 0;
r.foregroundNoti = null;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a3a854d..99f36d0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -778,8 +778,11 @@
long mWaitForNetworkTimeoutMs;
/**
- * Helper class which parses out priority arguments and dumps sections according to their
- * priority. If priority arguments are omitted, function calls the legacy dump command.
+ * Helper class which strips out priority and proto arguments then calls the dump function with
+ * the appropriate arguments. If priority arguments are omitted, function calls the legacy
+ * dump command.
+ * If priority arguments are omitted all sections are dumped, otherwise sections are dumped
+ * according to their priority.
*/
private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
@Override
@@ -791,24 +794,7 @@
@Override
public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
- if (asProto) {
- doDump(fd, pw, new String[0], asProto);
- } else {
- doDump(fd, pw, new String[]{"settings"}, asProto);
- doDump(fd, pw, new String[]{"intents"}, asProto);
- doDump(fd, pw, new String[]{"broadcasts"}, asProto);
- doDump(fd, pw, new String[]{"providers"}, asProto);
- doDump(fd, pw, new String[]{"permissions"}, asProto);
- doDump(fd, pw, new String[]{"services"}, asProto);
- doDump(fd, pw, new String[]{"recents"}, asProto);
- doDump(fd, pw, new String[]{"lastanr"}, asProto);
- doDump(fd, pw, new String[]{"starter"}, asProto);
- doDump(fd, pw, new String[]{"containers"}, asProto);
- if (mAssociations.size() > 0) {
- doDump(fd, pw, new String[]{"associations"}, asProto);
- }
- doDump(fd, pw, new String[]{"processes"}, asProto);
- }
+ doDump(fd, pw, new String[]{"-a", "--normal-priority"}, asProto);
}
@Override
@@ -15504,6 +15490,7 @@
boolean dumpClient = false;
boolean dumpCheckin = false;
boolean dumpCheckinFormat = false;
+ boolean dumpNormalPriority = false;
boolean dumpVisibleStacksOnly = false;
boolean dumpFocusedStackOnly = false;
String dumpPackage = null;
@@ -15536,6 +15523,8 @@
dumpCheckin = dumpCheckinFormat = true;
} else if ("-C".equals(opt)) {
dumpCheckinFormat = true;
+ } else if ("--normal-priority".equals(opt)) {
+ dumpNormalPriority = true;
} else if ("-h".equals(opt)) {
ActivityManagerShellCommand.dumpHelp(pw, true);
return;
@@ -15937,11 +15926,15 @@
pw.println("-------------------------------------------------------------------------------");
}
dumpActivityContainersLocked(pw);
- pw.println();
- if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ // Activities section is dumped as part of the Critical priority dump. Exclude the
+ // section if priority is Normal.
+ if (!dumpNormalPriority){
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ }
+ dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
}
- dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
if (mAssociations.size() > 0) {
pw.println();
if (dumpAll) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 77f6d44..a0f31cd 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1266,7 +1266,7 @@
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent");
int modifiedFlags = flags
| PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS;
- if (intent.isBrowsableWebIntent()
+ if (intent.isWebIntent()
|| (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
modifiedFlags |= PackageManager.MATCH_INSTANT;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1825db8..56d66de 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7296,6 +7296,12 @@
//======================
/** */
public int dispatchFocusChange(AudioFocusInfo afi, int focusChange, IAudioPolicyCallback pcb) {
+ if (afi == null) {
+ throw new IllegalArgumentException("Illegal null AudioFocusInfo");
+ }
+ if (pcb == null) {
+ throw new IllegalArgumentException("Illegal null AudioPolicy callback");
+ }
synchronized (mAudioPolicies) {
if (!mAudioPolicies.containsKey(pcb.asBinder())) {
throw new IllegalStateException("Unregistered AudioPolicy for focus dispatch");
@@ -7304,6 +7310,23 @@
}
}
+ public void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult,
+ IAudioPolicyCallback pcb) {
+ if (afi == null) {
+ throw new IllegalArgumentException("Illegal null AudioFocusInfo");
+ }
+ if (pcb == null) {
+ throw new IllegalArgumentException("Illegal null AudioPolicy callback");
+ }
+ synchronized (mAudioPolicies) {
+ if (!mAudioPolicies.containsKey(pcb.asBinder())) {
+ throw new IllegalStateException("Unregistered AudioPolicy for external focus");
+ }
+ mMediaFocusControl.setFocusRequestResultFromExtPolicy(afi, requestResult);
+ }
+ }
+
+
//======================
// misc
//======================
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index f2ef02f..99f0840 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -241,15 +241,15 @@
void release() {
+ final IBinder srcRef = mSourceRef;
+ final AudioFocusDeathHandler deathHdlr = mDeathHandler;
try {
- if (mSourceRef != null && mDeathHandler != null) {
- mSourceRef.unlinkToDeath(mDeathHandler, 0);
- mDeathHandler = null;
- mFocusDispatcher = null;
+ if (srcRef != null && deathHdlr != null) {
+ srcRef.unlinkToDeath(deathHdlr, 0);
}
- } catch (java.util.NoSuchElementException e) {
- Log.e(TAG, "FocusRequester.release() hit ", e);
- }
+ } catch (java.util.NoSuchElementException e) { }
+ mDeathHandler = null;
+ mFocusDispatcher = null;
}
@Override
@@ -424,7 +424,7 @@
int dispatchFocusChange(int focusChange) {
if (mFocusDispatcher == null) {
- if (MediaFocusControl.DEBUG) { Log.v(TAG, "dispatchFocusChange: no focus dispatcher"); }
+ if (MediaFocusControl.DEBUG) { Log.e(TAG, "dispatchFocusChange: no focus dispatcher"); }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
if (focusChange == AudioManager.AUDIOFOCUS_NONE) {
@@ -445,12 +445,29 @@
try {
mFocusDispatcher.dispatchAudioFocusChange(focusChange, mClientId);
} catch (android.os.RemoteException e) {
- Log.v(TAG, "dispatchFocusChange: error talking to focus listener", e);
+ Log.e(TAG, "dispatchFocusChange: error talking to focus listener " + mClientId, e);
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
+ void dispatchFocusResultFromExtPolicy(int requestResult) {
+ if (mFocusDispatcher == null) {
+ if (MediaFocusControl.DEBUG) {
+ Log.e(TAG, "dispatchFocusResultFromExtPolicy: no focus dispatcher");
+ }
+ }
+ if (DEBUG) {
+ Log.v(TAG, "dispatching result" + requestResult + " to " + mClientId);
+ }
+ try {
+ mFocusDispatcher.dispatchFocusResultFromExtPolicy(requestResult, mClientId);
+ } catch (android.os.RemoteException e) {
+ Log.e(TAG, "dispatchFocusResultFromExtPolicy: error talking to focus listener"
+ + mClientId, e);
+ }
+ }
+
AudioFocusInfo toAudioFocusInfo() {
return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName,
mFocusGainRequest, mFocusLossReceived, mGrantFlags, mSdkTarget);
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 9ddc52a..d023bd7 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -83,6 +83,10 @@
private boolean mRingOrCallActive = false;
+ private final Object mExtFocusChangeLock = new Object();
+ @GuardedBy("mExtFocusChangeLock")
+ private long mExtFocusChangeCounter;
+
protected MediaFocusControl(Context cntxt, PlayerFocusEnforcer pfe) {
mContext = cntxt;
mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -521,7 +525,7 @@
* @param requestResult
* @return true if the external audio focus policy (if any) is handling the focus request
*/
- boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi, int requestResult,
+ boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi,
IAudioFocusDispatcher fd, IBinder cb) {
if (mFocusPolicy == null) {
return false;
@@ -530,6 +534,9 @@
Log.v(TAG, "notifyExtFocusPolicyFocusRequest client="+afi.getClientId()
+ " dispatcher=" + fd);
}
+ synchronized (mExtFocusChangeLock) {
+ afi.setGen(mExtFocusChangeCounter++);
+ }
final FocusRequester existingFr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
if (existingFr != null) {
if (!existingFr.hasSameDispatcher(fd)) {
@@ -538,8 +545,7 @@
mFocusOwnersForFocusPolicy.put(afi.getClientId(),
new FocusRequester(afi, fd, cb, hdlr, this));
}
- } else if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
- || requestResult == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
+ } else {
// new focus (future) focus owner to keep track of
final AudioFocusDeathHandler hdlr = new AudioFocusDeathHandler(cb);
mFocusOwnersForFocusPolicy.put(afi.getClientId(),
@@ -547,12 +553,25 @@
}
try {
//oneway
- mFocusPolicy.notifyAudioFocusRequest(afi, requestResult);
+ mFocusPolicy.notifyAudioFocusRequest(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ return true;
} catch (RemoteException e) {
Log.e(TAG, "Can't call notifyAudioFocusRequest() on IAudioPolicyCallback "
+ mFocusPolicy.asBinder(), e);
}
- return true;
+ return false;
+ }
+
+ void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult) {
+ synchronized (mExtFocusChangeLock) {
+ if (afi.getGen() > mExtFocusChangeCounter) {
+ return;
+ }
+ }
+ final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
+ if (fr != null) {
+ fr.dispatchFocusResultFromExtPolicy(requestResult);
+ }
}
/**
@@ -590,7 +609,12 @@
if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
- final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
+ final FocusRequester fr;
+ if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
+ fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
+ } else {
+ fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
+ }
if (fr == null) {
if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
@@ -710,9 +734,7 @@
boolean focusGrantDelayed = false;
if (!canReassignAudioFocus()) {
if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
- final int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
- notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, result, fd, cb);
- return result;
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
} else {
// request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
// granted right now, so the requester will be inserted in the focus stack
@@ -721,12 +743,11 @@
}
}
- // external focus policy: delay request for focus gain?
- final int resultWithExtPolicy = AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
+ // external focus policy?
if (notifyExtFocusPolicyFocusRequest_syncAf(
- afiForExtPolicy, resultWithExtPolicy, fd, cb)) {
+ afiForExtPolicy, fd, cb)) {
// stop handling focus request here as it is handled by external audio focus policy
- return resultWithExtPolicy;
+ return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY;
}
// handle the potential premature death of the new holder of the focus
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 373d87d..13873e4 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -328,10 +328,12 @@
@Override
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
pw.print("Connectivity: connected=");
- pw.print(mConnected);
+ pw.println(mConnected);
+
pw.print("Tracking ");
pw.print(mTrackedJobs.size());
- pw.println(":");
+ pw.println(" jobs");
+
for (int i = 0; i < mTrackedJobs.size(); i++) {
final JobStatus js = mTrackedJobs.valueAt(i);
if (js.shouldDump(filterUid)) {
@@ -339,7 +341,9 @@
js.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, js.getSourceUid());
- pw.print(": "); pw.print(js.getJob().getRequiredNetwork());
+ pw.print(": ");
+ pw.print(js.getJob().getRequiredNetwork());
+ pw.println();
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertParsingException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertParsingException.java
new file mode 100644
index 0000000..57a3d99
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertParsingException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.server.locksettings.recoverablekeystore.certificate;
+
+/** Exception thrown when parsing errors occur. */
+public class CertParsingException extends Exception {
+
+ public CertParsingException(String message) {
+ super(message);
+ }
+
+ public CertParsingException(Exception cause) {
+ super(cause);
+ }
+
+ public CertParsingException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
new file mode 100644
index 0000000..fea6733
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
@@ -0,0 +1,355 @@
+/*
+ * 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.server.locksettings.recoverablekeystore.certificate;
+
+import static javax.xml.xpath.XPathConstants.NODESET;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathBuilder;
+import java.security.cert.CertPathBuilderException;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertStore;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.PKIXParameters;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/** Utility functions related to parsing and validating public-key certificates. */
+final class CertUtils {
+
+ private static final String CERT_FORMAT = "X.509";
+ private static final String CERT_PATH_ALG = "PKIX";
+ private static final String CERT_STORE_ALG = "Collection";
+ private static final String SIGNATURE_ALG = "SHA256withRSA";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({MUST_EXIST_UNENFORCED, MUST_EXIST_EXACTLY_ONE, MUST_EXIST_AT_LEAST_ONE})
+ @interface MustExist {}
+ static final int MUST_EXIST_UNENFORCED = 0;
+ static final int MUST_EXIST_EXACTLY_ONE = 1;
+ static final int MUST_EXIST_AT_LEAST_ONE = 2;
+
+ private CertUtils() {}
+
+ /**
+ * Decodes a byte array containing an encoded X509 certificate.
+ *
+ * @param certBytes the byte array containing the encoded X509 certificate
+ * @return the decoded X509 certificate
+ * @throws CertParsingException if any parsing error occurs
+ */
+ static X509Certificate decodeCert(byte[] certBytes) throws CertParsingException {
+ return decodeCert(new ByteArrayInputStream(certBytes));
+ }
+
+ /**
+ * Decodes an X509 certificate from an {@code InputStream}.
+ *
+ * @param inStream the input stream containing the encoded X509 certificate
+ * @return the decoded X509 certificate
+ * @throws CertParsingException if any parsing error occurs
+ */
+ static X509Certificate decodeCert(InputStream inStream) throws CertParsingException {
+ CertificateFactory certFactory;
+ try {
+ certFactory = CertificateFactory.getInstance(CERT_FORMAT);
+ } catch (CertificateException e) {
+ // Should not happen, as X.509 is mandatory for all providers.
+ throw new RuntimeException(e);
+ }
+ try {
+ return (X509Certificate) certFactory.generateCertificate(inStream);
+ } catch (CertificateException e) {
+ throw new CertParsingException(e);
+ }
+ }
+
+ /**
+ * Parses a byte array as the content of an XML file, and returns the root node of the XML file.
+ *
+ * @param xmlBytes the byte array that is the XML file content
+ * @return the root node of the XML file
+ * @throws CertParsingException if any parsing error occurs
+ */
+ static Element getXmlRootNode(byte[] xmlBytes) throws CertParsingException {
+ try {
+ Document document =
+ DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder()
+ .parse(new ByteArrayInputStream(xmlBytes));
+ document.getDocumentElement().normalize();
+ return document.getDocumentElement();
+ } catch (SAXException | ParserConfigurationException | IOException e) {
+ throw new CertParsingException(e);
+ }
+ }
+
+ /**
+ * Gets the text contents of certain XML child nodes, given a XML root node and a list of tags
+ * representing the path to locate the child nodes. The whitespaces and newlines in the text
+ * contents are stripped away.
+ *
+ * <p>For example, the list of tags [tag1, tag2, tag3] represents the XML tree like the
+ * following:
+ *
+ * <pre>
+ * <root>
+ * <tag1>
+ * <tag2>
+ * <tag3>abc</tag3>
+ * <tag3>def</tag3>
+ * </tag2>
+ * </tag1>
+ * <root>
+ * </pre>
+ *
+ * @param mustExist whether and how many nodes must exist. If the number of child nodes does not
+ * satisfy the requirement, CertParsingException will be thrown.
+ * @param rootNode the root node that serves as the starting point to locate the child nodes
+ * @param nodeTags the list of tags representing the relative path from the root node
+ * @return a list of strings that are the text contents of the child nodes
+ * @throws CertParsingException if any parsing error occurs
+ */
+ static List<String> getXmlNodeContents(@MustExist int mustExist, Element rootNode,
+ String... nodeTags)
+ throws CertParsingException {
+ String expression = String.join("/", nodeTags);
+
+ XPath xPath = XPathFactory.newInstance().newXPath();
+ NodeList nodeList;
+ try {
+ nodeList = (NodeList) xPath.compile(expression).evaluate(rootNode, NODESET);
+ } catch (XPathExpressionException e) {
+ throw new CertParsingException(e);
+ }
+
+ switch (mustExist) {
+ case MUST_EXIST_UNENFORCED:
+ break;
+
+ case MUST_EXIST_EXACTLY_ONE:
+ if (nodeList.getLength() != 1) {
+ throw new CertParsingException(
+ "The XML file must contain exactly one node with the path "
+ + expression);
+ }
+ break;
+
+ case MUST_EXIST_AT_LEAST_ONE:
+ if (nodeList.getLength() == 0) {
+ throw new CertParsingException(
+ "The XML file must contain at least one node with the path "
+ + expression);
+ }
+ break;
+
+ default:
+ throw new UnsupportedOperationException(
+ "This value of MustExist is not supported: " + mustExist);
+ }
+
+ List<String> result = new ArrayList<>();
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node node = nodeList.item(i);
+ // Remove whitespaces and newlines.
+ result.add(node.getTextContent().replaceAll("\\s", ""));
+ }
+ return result;
+ }
+
+ /**
+ * Decodes a base64-encoded string.
+ *
+ * @param str the base64-encoded string
+ * @return the decoding decoding result
+ * @throws CertParsingException if the input string is not a properly base64-encoded string
+ */
+ static byte[] decodeBase64(String str) throws CertParsingException {
+ try {
+ return Base64.getDecoder().decode(str);
+ } catch (IllegalArgumentException e) {
+ throw new CertParsingException(e);
+ }
+ }
+
+ /**
+ * Verifies a public-key signature that is computed by RSA with SHA256.
+ *
+ * @param signerPublicKey the public key of the original signer
+ * @param signature the public-key signature
+ * @param signedBytes the bytes that have been signed
+ * @throws CertValidationException if the signature verification fails
+ */
+ static void verifyRsaSha256Signature(
+ PublicKey signerPublicKey, byte[] signature, byte[] signedBytes)
+ throws CertValidationException {
+ Signature verifier;
+ try {
+ verifier = Signature.getInstance(SIGNATURE_ALG);
+ } catch (NoSuchAlgorithmException e) {
+ // Should not happen, as SHA256withRSA is mandatory for all providers.
+ throw new RuntimeException(e);
+ }
+ try {
+ verifier.initVerify(signerPublicKey);
+ verifier.update(signedBytes);
+ if (!verifier.verify(signature)) {
+ throw new CertValidationException("The signature is invalid");
+ }
+ } catch (InvalidKeyException | SignatureException e) {
+ throw new CertValidationException(e);
+ }
+ }
+
+ /**
+ * Validates a leaf certificate, and returns the certificate path if the certificate is valid.
+ * If the given validation date is null, the current date will be used.
+ *
+ * @param validationDate the date for which the validity of the certificate should be
+ * determined
+ * @param trustedRoot the certificate of the trusted root CA
+ * @param intermediateCerts the list of certificates of possible intermediate CAs
+ * @param leafCert the leaf certificate that is to be validated
+ * @return the certificate path if the leaf cert is valid
+ * @throws CertValidationException if {@code leafCert} is invalid (e.g., is expired, or has
+ * invalid signature)
+ */
+ static CertPath validateCert(
+ @Nullable Date validationDate,
+ X509Certificate trustedRoot,
+ List<X509Certificate> intermediateCerts,
+ X509Certificate leafCert)
+ throws CertValidationException {
+ PKIXParameters pkixParams =
+ buildPkixParams(validationDate, trustedRoot, intermediateCerts, leafCert);
+ CertPath certPath = buildCertPath(pkixParams);
+
+ CertPathValidator certPathValidator;
+ try {
+ certPathValidator = CertPathValidator.getInstance(CERT_PATH_ALG);
+ } catch (NoSuchAlgorithmException e) {
+ // Should not happen, as PKIX is mandatory for all providers.
+ throw new RuntimeException(e);
+ }
+ try {
+ certPathValidator.validate(certPath, pkixParams);
+ } catch (CertPathValidatorException | InvalidAlgorithmParameterException e) {
+ throw new CertValidationException(e);
+ }
+ return certPath;
+ }
+
+ @VisibleForTesting
+ static CertPath buildCertPath(PKIXParameters pkixParams) throws CertValidationException {
+ CertPathBuilder certPathBuilder;
+ try {
+ certPathBuilder = CertPathBuilder.getInstance(CERT_PATH_ALG);
+ } catch (NoSuchAlgorithmException e) {
+ // Should not happen, as PKIX is mandatory for all providers.
+ throw new RuntimeException(e);
+ }
+ try {
+ return certPathBuilder.build(pkixParams).getCertPath();
+ } catch (CertPathBuilderException | InvalidAlgorithmParameterException e) {
+ throw new CertValidationException(e);
+ }
+ }
+
+ @VisibleForTesting
+ static PKIXParameters buildPkixParams(
+ @Nullable Date validationDate,
+ X509Certificate trustedRoot,
+ List<X509Certificate> intermediateCerts,
+ X509Certificate leafCert)
+ throws CertValidationException {
+ // Create a TrustAnchor from the trusted root certificate.
+ Set<TrustAnchor> trustedAnchors = new HashSet<>();
+ trustedAnchors.add(new TrustAnchor(trustedRoot, null));
+
+ // Create a CertStore from the list of intermediate certificates.
+ List<X509Certificate> certs = new ArrayList<>(intermediateCerts);
+ certs.add(leafCert);
+ CertStore certStore;
+ try {
+ certStore =
+ CertStore.getInstance(CERT_STORE_ALG, new CollectionCertStoreParameters(certs));
+ } catch (NoSuchAlgorithmException e) {
+ // Should not happen, as Collection is mandatory for all providers.
+ throw new RuntimeException(e);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new CertValidationException(e);
+ }
+
+ // Create a CertSelector from the leaf certificate.
+ X509CertSelector certSelector = new X509CertSelector();
+ certSelector.setCertificate(leafCert);
+
+ // Build a PKIXParameters from TrustAnchor, CertStore, and CertSelector.
+ PKIXBuilderParameters pkixParams;
+ try {
+ pkixParams = new PKIXBuilderParameters(trustedAnchors, certSelector);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new CertValidationException(e);
+ }
+ pkixParams.addCertStore(certStore);
+
+ // If validationDate is null, the current time will be used.
+ pkixParams.setDate(validationDate);
+ pkixParams.setRevocationEnabled(false);
+
+ return pkixParams;
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertValidationException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertValidationException.java
new file mode 100644
index 0000000..99a9ff7
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertValidationException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.server.locksettings.recoverablekeystore.certificate;
+
+/** Exception thrown when validation or verification fails. */
+public class CertValidationException extends Exception {
+
+ public CertValidationException(String message) {
+ super(message);
+ }
+
+ public CertValidationException(Exception cause) {
+ super(cause);
+ }
+
+ public CertValidationException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java
new file mode 100644
index 0000000..c62a31e
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java
@@ -0,0 +1,178 @@
+/*
+ * 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.server.locksettings.recoverablekeystore.certificate;
+
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.security.SecureRandom;
+import java.security.cert.CertPath;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import org.w3c.dom.Element;
+
+/**
+ * Parses and holds the XML file containing the list of THM public-key certificates and related
+ * metadata.
+ */
+public final class CertXml {
+
+ private static final String METADATA_NODE_TAG = "metadata";
+ private static final String METADATA_SERIAL_NODE_TAG = "serial";
+ private static final String METADATA_REFRESH_INTERVAL_NODE_TAG = "refresh-interval";
+ private static final String ENDPOINT_CERT_LIST_TAG = "endpoints";
+ private static final String ENDPOINT_CERT_ITEM_TAG = "cert";
+ private static final String INTERMEDIATE_CERT_LIST_TAG = "intermediates";
+ private static final String INTERMEDIATE_CERT_ITEM_TAG = "cert";
+
+ private final long serial;
+ private final long refreshInterval;
+ private final List<X509Certificate> intermediateCerts;
+ private final List<X509Certificate> endpointCerts;
+
+ private CertXml(
+ long serial,
+ long refreshInterval,
+ List<X509Certificate> intermediateCerts,
+ List<X509Certificate> endpointCerts) {
+ this.serial = serial;
+ this.refreshInterval = refreshInterval;
+ this.intermediateCerts = intermediateCerts;
+ this.endpointCerts = endpointCerts;
+ }
+
+ /** Gets the serial number of the XML file containing public-key certificates. */
+ public long getSerial() {
+ return serial;
+ }
+
+ /**
+ * Gets the refresh interval in the XML file containing public-key certificates. The refresh
+ * interval denotes the number of seconds that the client should follow to contact the server to
+ * refresh the XML file.
+ */
+ public long getRefreshInterval() {
+ return refreshInterval;
+ }
+
+ @VisibleForTesting
+ List<X509Certificate> getAllIntermediateCerts() {
+ return intermediateCerts;
+ }
+
+ @VisibleForTesting
+ List<X509Certificate> getAllEndpointCerts() {
+ return endpointCerts;
+ }
+
+ /**
+ * Chooses a random endpoint certificate from the XML file, validates the chosen certificate,
+ * and returns the certificate path including the chosen certificate if it is valid.
+ *
+ * @param trustedRoot the trusted root certificate
+ * @return the certificate path including the chosen certificate if the certificate is valid
+ * @throws CertValidationException if the chosen certificate cannot be validated based on the
+ * trusted root certificate
+ */
+ public CertPath getRandomEndpointCert(X509Certificate trustedRoot)
+ throws CertValidationException {
+ return getEndpointCert(
+ new SecureRandom().nextInt(this.endpointCerts.size()),
+ /*validationDate=*/ null,
+ trustedRoot);
+ }
+
+ @VisibleForTesting
+ CertPath getEndpointCert(
+ int index, @Nullable Date validationDate, X509Certificate trustedRoot)
+ throws CertValidationException {
+ X509Certificate chosenCert = endpointCerts.get(index);
+ return CertUtils.validateCert(validationDate, trustedRoot, intermediateCerts, chosenCert);
+ }
+
+ /**
+ * Parses a byte array as the content of the XML file containing a list of endpoint
+ * certificates.
+ *
+ * @param bytes the bytes of the XML file
+ * @return a {@code CertXml} instance that contains the parsing result
+ * @throws CertParsingException if any parsing error occurs
+ */
+ public static CertXml parse(byte[] bytes) throws CertParsingException {
+ Element rootNode = CertUtils.getXmlRootNode(bytes);
+ return new CertXml(
+ parseSerial(rootNode),
+ parseRefreshInterval(rootNode),
+ parseIntermediateCerts(rootNode),
+ parseEndpointCerts(rootNode));
+ }
+
+ private static long parseSerial(Element rootNode) throws CertParsingException {
+ List<String> contents =
+ CertUtils.getXmlNodeContents(
+ CertUtils.MUST_EXIST_EXACTLY_ONE,
+ rootNode,
+ METADATA_NODE_TAG,
+ METADATA_SERIAL_NODE_TAG);
+ return Long.parseLong(contents.get(0));
+ }
+
+ private static long parseRefreshInterval(Element rootNode) throws CertParsingException {
+ List<String> contents =
+ CertUtils.getXmlNodeContents(
+ CertUtils.MUST_EXIST_EXACTLY_ONE,
+ rootNode,
+ METADATA_NODE_TAG,
+ METADATA_REFRESH_INTERVAL_NODE_TAG);
+ return Long.parseLong(contents.get(0));
+ }
+
+ private static List<X509Certificate> parseIntermediateCerts(Element rootNode)
+ throws CertParsingException {
+ List<String> contents =
+ CertUtils.getXmlNodeContents(
+ CertUtils.MUST_EXIST_UNENFORCED,
+ rootNode,
+ INTERMEDIATE_CERT_LIST_TAG,
+ INTERMEDIATE_CERT_ITEM_TAG);
+ List<X509Certificate> res = new ArrayList<>();
+ for (String content : contents) {
+ res.add(CertUtils.decodeCert(CertUtils.decodeBase64(content)));
+ }
+ return Collections.unmodifiableList(res);
+ }
+
+ private static List<X509Certificate> parseEndpointCerts(Element rootNode)
+ throws CertParsingException {
+ List<String> contents =
+ CertUtils.getXmlNodeContents(
+ CertUtils.MUST_EXIST_AT_LEAST_ONE,
+ rootNode,
+ ENDPOINT_CERT_LIST_TAG,
+ ENDPOINT_CERT_ITEM_TAG);
+ List<X509Certificate> res = new ArrayList<>();
+ for (String content : contents) {
+ res.add(CertUtils.decodeCert(CertUtils.decodeBase64(content)));
+ }
+ return Collections.unmodifiableList(res);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java
new file mode 100644
index 0000000..e75be85
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java
@@ -0,0 +1,121 @@
+/*
+ * 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.server.locksettings.recoverablekeystore.certificate;
+
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import org.w3c.dom.Element;
+
+/**
+ * Parses and holds the XML file containing the signature of the XML file containing the list of THM
+ * public-key certificates.
+ */
+public final class SigXml {
+
+ private static final String INTERMEDIATE_CERT_LIST_TAG = "intermediates";
+ private static final String INTERMEDIATE_CERT_ITEM_TAG = "cert";
+ private static final String SIGNER_CERT_NODE_TAG = "certificate";
+ private static final String SIGNATURE_NODE_TAG = "value";
+
+ private final List<X509Certificate> intermediateCerts;
+ private final X509Certificate signerCert;
+ private final byte[] signature;
+
+ private SigXml(
+ List<X509Certificate> intermediateCerts, X509Certificate signerCert, byte[] signature) {
+ this.intermediateCerts = intermediateCerts;
+ this.signerCert = signerCert;
+ this.signature = signature;
+ }
+
+ /**
+ * Verifies the signature contained in this XML file against a trusted root certificate and the
+ * binary content of another file. The signer's public-key certificate and possible intermediate
+ * CA certificates are included in this XML file, and will be validated against the trusted root
+ * certificate.
+ *
+ * @param trustedRoot the trusted root certificate
+ * @param signedFileBytes the original file content that has been signed
+ * @throws CertValidationException if the signature verification fails, or the signer's
+ * certificate contained in this XML file cannot be validated
+ * based on the trusted root certificate
+ */
+ public void verifyFileSignature(X509Certificate trustedRoot, byte[] signedFileBytes)
+ throws CertValidationException {
+ verifyFileSignature(trustedRoot, signedFileBytes, /*validationDate=*/ null);
+ }
+
+ @VisibleForTesting
+ void verifyFileSignature(
+ X509Certificate trustedRoot, byte[] signedFileBytes, @Nullable Date validationDate)
+ throws CertValidationException {
+ CertUtils.validateCert(validationDate, trustedRoot, intermediateCerts, signerCert);
+ CertUtils.verifyRsaSha256Signature(signerCert.getPublicKey(), signature, signedFileBytes);
+ }
+
+ /**
+ * Parses a byte array as the content of the XML file containing the signature and related
+ * certificates.
+ *
+ * @param bytes the bytes of the XML file
+ * @return a {@code SigXml} instance that contains the parsing result
+ * @throws CertParsingException if any parsing error occurs
+ */
+ public static SigXml parse(byte[] bytes) throws CertParsingException {
+ Element rootNode = CertUtils.getXmlRootNode(bytes);
+ return new SigXml(
+ parseIntermediateCerts(rootNode), parseSignerCert(rootNode),
+ parseFileSignature(rootNode));
+ }
+
+ private static List<X509Certificate> parseIntermediateCerts(Element rootNode)
+ throws CertParsingException {
+ List<String> contents =
+ CertUtils.getXmlNodeContents(
+ CertUtils.MUST_EXIST_UNENFORCED,
+ rootNode,
+ INTERMEDIATE_CERT_LIST_TAG,
+ INTERMEDIATE_CERT_ITEM_TAG);
+ List<X509Certificate> res = new ArrayList<>();
+ for (String content : contents) {
+ res.add(CertUtils.decodeCert(CertUtils.decodeBase64(content)));
+ }
+ return Collections.unmodifiableList(res);
+ }
+
+ private static X509Certificate parseSignerCert(Element rootNode) throws CertParsingException {
+ List<String> contents =
+ CertUtils.getXmlNodeContents(
+ CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, SIGNER_CERT_NODE_TAG);
+ return CertUtils.decodeCert(CertUtils.decodeBase64(contents.get(0)));
+ }
+
+ private static byte[] parseFileSignature(Element rootNode) throws CertParsingException {
+ List<String> contents =
+ CertUtils.getXmlNodeContents(
+ CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, SIGNATURE_NODE_TAG);
+ return CertUtils.decodeBase64(contents.get(0));
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index fd435f9..b7842d5 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -376,9 +376,7 @@
protected void upgradeXml(final int xmlVersion, final int userId) {}
private void loadAllowedComponentsFromSettings() {
-
- UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- for (UserInfo user : userManager.getUsers()) {
+ for (UserInfo user : mUm.getUsers()) {
final ContentResolver cr = mContext.getContentResolver();
addApprovedList(Settings.Secure.getStringForUser(
cr,
@@ -482,7 +480,9 @@
for (int i = 0; i < allowedByType.size(); i++) {
final ArraySet<String> allowed = allowedByType.valueAt(i);
allowedPackages.addAll(
- allowed.stream().map(this::getPackageName).collect(Collectors.toList()));
+ allowed.stream().map(this::getPackageName).
+ filter(value -> !TextUtils.isEmpty(value))
+ .collect(Collectors.toList()));
}
return allowedPackages;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e7eed03..ada002c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -25,7 +25,9 @@
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.NotificationListenerService
.HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
@@ -88,6 +90,7 @@
import android.companion.ICompanionDeviceManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -115,6 +118,7 @@
import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -206,6 +210,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
@@ -288,6 +293,7 @@
private ICompanionDeviceManager mCompanionManager;
private AccessibilityManager mAccessibilityManager;
private IDeviceIdleController mDeviceIdleController;
+ private IBinder mPermissionOwner;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -524,7 +530,7 @@
} catch (FileNotFoundException e) {
// No data yet
// Load default managed services approvals
- readDefaultApprovedServices(UserHandle.USER_SYSTEM);
+ readDefaultApprovedServices(USER_SYSTEM);
} catch (IOException e) {
Log.wtf(TAG, "Unable to read notification policy", e);
} catch (NumberFormatException e) {
@@ -974,7 +980,7 @@
final int enabled = mPackageManager.getApplicationEnabledSetting(
pkgName,
changeUserId != UserHandle.USER_ALL ? changeUserId :
- UserHandle.USER_SYSTEM);
+ USER_SYSTEM);
if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
cancelNotifications = false;
@@ -1061,6 +1067,7 @@
}
} else if (action.equals(Intent.ACTION_USER_REMOVED)) {
final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+ mUserProfiles.updateCache(context);
mZenModeHelper.onUserRemoved(user);
mRankingHelper.onUserRemoved(user);
mListeners.onUserRemoved(user);
@@ -1268,7 +1275,7 @@
NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
NotificationUsageStats usageStats, AtomicFile policyFile,
- ActivityManager activityManager, GroupHelper groupHelper) {
+ ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am) {
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -1276,7 +1283,7 @@
mAccessibilityManager =
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
- mAm = ActivityManager.getService();
+ mAm = am;
mPackageManager = packageManager;
mPackageManagerClient = packageManagerClient;
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
@@ -1287,6 +1294,11 @@
mActivityManager = activityManager;
mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+ try {
+ mPermissionOwner = mAm.newUriPermissionOwner("notification");
+ } catch (RemoteException e) {
+ Slog.w(TAG, "AM dead", e);
+ }
mHandler = new WorkerHandler(looper);
mRankingThread.start();
@@ -1415,7 +1427,7 @@
null, snoozeHelper, new NotificationUsageStats(getContext()),
new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"),
(ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE),
- getGroupHelper());
+ getGroupHelper(), ActivityManager.getService());
// register for various Intents
IntentFilter filter = new IntentFilter();
@@ -1749,7 +1761,7 @@
protected void reportSeen(NotificationRecord r) {
final int userId = r.sbn.getUserId();
mAppUsageStats.reportEvent(r.sbn.getPackageName(),
- userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM
+ userId == UserHandle.USER_ALL ? USER_SYSTEM
: userId,
UsageEvents.Event.NOTIFICATION_SEEN);
}
@@ -2894,7 +2906,7 @@
checkCallerIsSystem();
if (DBG) Slog.d(TAG, "getBackupPayload u=" + user);
//TODO: http://b/22388012
- if (user != UserHandle.USER_SYSTEM) {
+ if (user != USER_SYSTEM) {
Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
return null;
}
@@ -2920,7 +2932,7 @@
return;
}
//TODO: http://b/22388012
- if (user != UserHandle.USER_SYSTEM) {
+ if (user != USER_SYSTEM) {
Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
return;
}
@@ -3678,7 +3690,7 @@
sbn.getNotification().flags =
(r.mOriginalFlags & ~Notification.FLAG_FOREGROUND_SERVICE);
mRankingHelper.sort(mNotificationList);
- mListeners.notifyPostedLocked(sbn, sbn /* oldSbn */);
+ mListeners.notifyPostedLocked(r, sbn /* oldSbn */);
}
};
@@ -3707,7 +3719,7 @@
try {
final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
+ (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, notification);
int canColorize = mPackageManagerClient.checkPermission(
@@ -4126,6 +4138,8 @@
// Make sure we don't lose the foreground service state.
notification.flags |=
old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
+ // revoke uri permissions for changed uris
+ revokeUriPermissions(r, old);
r.isUpdate = true;
}
@@ -4147,7 +4161,7 @@
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
- mListeners.notifyPostedLocked(n, oldSbn);
+ mListeners.notifyPostedLocked(r, oldSbn);
if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) {
mHandler.post(new Runnable() {
@Override
@@ -4912,6 +4926,9 @@
r.recordDismissalSurface(NotificationStats.DISMISSAL_OTHER);
}
+ // Revoke permissions
+ revokeUriPermissions(null, r);
+
// tell the app
if (sendDelete) {
if (r.getNotification().deleteIntent != null) {
@@ -5009,6 +5026,30 @@
r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now), listenerName);
}
+ void revokeUriPermissions(NotificationRecord newRecord, NotificationRecord oldRecord) {
+ Set<Uri> oldUris = oldRecord.getNotificationUris();
+ Set<Uri> newUris = newRecord == null ? new HashSet<>() : newRecord.getNotificationUris();
+ oldUris.removeAll(newUris);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ for (Uri uri : oldUris) {
+ if (uri != null) {
+ int notiUserId = oldRecord.getUserId();
+ int sourceUserId = notiUserId == USER_ALL ? USER_SYSTEM
+ : ContentProvider.getUserIdFromUri(uri, notiUserId);
+ uri = ContentProvider.getUriWithoutUserId(uri);
+ mAm.revokeUriPermissionFromOwner(mPermissionOwner,
+ uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId);
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Count not revoke uri permissions", e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
* and none of the {@code mustNotHaveFlags}.
@@ -5851,10 +5892,13 @@
* but isn't anymore.
*/
@GuardedBy("mNotificationLock")
- public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
+ public void notifyPostedLocked(NotificationRecord r, StatusBarNotification oldSbn) {
// Lazily initialized snapshots of the notification.
+ StatusBarNotification sbn = r.sbn;
TrimCache trimCache = new TrimCache(sbn);
+ Set<Uri> uris = r.getNotificationUris();
+
for (final ManagedServiceInfo info : getServices()) {
boolean sbnVisible = isVisibleToListener(sbn, info);
boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
@@ -5877,6 +5921,9 @@
continue;
}
+ grantUriPermissions(uris, sbn.getUserId(), info.component.getPackageName(),
+ info.userid);
+
final StatusBarNotification sbnToPost = trimCache.ForListener(info);
mHandler.post(new Runnable() {
@Override
@@ -5887,6 +5934,28 @@
}
}
+ private void grantUriPermissions(Set<Uri> uris, int notiUserId, String listenerPkg,
+ int listenerUserId) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ for (Uri uri : uris) {
+ if (uri != null) {
+ int sourceUserId = notiUserId == USER_ALL ? USER_SYSTEM
+ : ContentProvider.getUserIdFromUri(uri, notiUserId);
+ uri = ContentProvider.getUriWithoutUserId(uri);
+ mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(),
+ listenerPkg,
+ uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId,
+ listenerUserId == USER_ALL ? USER_SYSTEM : listenerUserId);
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Count not grant uri permission to " + listenerPkg, e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* asynchronously notify all listeners about a removed notification
*/
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 23b9743..1ad8c74 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -38,6 +38,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.Adjustment;
@@ -47,6 +48,7 @@
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
@@ -64,6 +66,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Holds data about notifications that should not be shared with the
@@ -929,6 +932,42 @@
mStats.setViewedSettings();
}
+ public Set<Uri> getNotificationUris() {
+ Notification notification = getNotification();
+ Set<Uri> uris = new ArraySet<>();
+
+ if (notification.sound != null) {
+ uris.add(notification.sound);
+ }
+ if (notification.getChannelId() != null) {
+ NotificationChannel channel = getChannel();
+ if (channel != null && channel.getSound() != null) {
+ uris.add(channel.getSound());
+ }
+ }
+ if (notification.extras.containsKey(Notification.EXTRA_AUDIO_CONTENTS_URI)) {
+ uris.add(notification.extras.getParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI));
+ }
+ if (notification.extras.containsKey(Notification.EXTRA_BACKGROUND_IMAGE_URI)) {
+ uris.add(notification.extras.getParcelable(Notification.EXTRA_BACKGROUND_IMAGE_URI));
+ }
+ if (Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) {
+ Parcelable[] newMessages =
+ notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+ List<Notification.MessagingStyle.Message> messages
+ = Notification.MessagingStyle.Message.getMessagesFromBundleArray(newMessages);
+ Parcelable[] histMessages =
+ notification.extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
+ messages.addAll(
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages));
+ for (Notification.MessagingStyle.Message message : messages) {
+ uris.add(message.getDataUri());
+ }
+ }
+
+ return uris;
+ }
+
public LogMaker getLogMaker(long now) {
if (mLogMaker == null) {
// initialize fields that only change on update (so a new record)
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 7600e81..6e02db7 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -20,6 +20,8 @@
import static android.content.om.OverlayInfo.STATE_ENABLED;
import static android.content.om.OverlayInfo.STATE_MISSING_TARGET;
import static android.content.om.OverlayInfo.STATE_NO_IDMAP;
+import static android.content.om.OverlayInfo.STATE_OVERLAY_UPGRADING;
+import static android.content.om.OverlayInfo.STATE_TARGET_UPGRADING;
import static com.android.server.om.OverlayManagerService.DEBUG;
import static com.android.server.om.OverlayManagerService.TAG;
@@ -50,6 +52,10 @@
* @see OverlayManagerService
*/
final class OverlayManagerServiceImpl {
+ // Flags to use in conjunction with updateState.
+ private static final int FLAG_TARGET_IS_UPGRADING = 1<<0;
+ private static final int FLAG_OVERLAY_IS_UPGRADING = 1<<1;
+
private final PackageManagerHelper mPackageManager;
private final IdmapManager mIdmapManager;
private final OverlayManagerSettings mSettings;
@@ -123,9 +129,7 @@
}
try {
- final PackageInfo targetPackage =
- mPackageManager.getPackageInfo(overlayPackage.overlayTarget, newUserId);
- updateState(targetPackage, overlayPackage, newUserId);
+ updateState(overlayPackage.overlayTarget, overlayPackage.packageName, newUserId, 0);
} catch (OverlayManagerSettings.BadKeyException e) {
Slog.e(TAG, "failed to update settings", e);
mSettings.remove(overlayPackage.packageName, newUserId);
@@ -168,8 +172,7 @@
Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId);
}
- final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) {
+ if (updateAllOverlaysForTarget(packageName, userId, 0)) {
mListener.onOverlaysChanged(packageName, userId);
}
}
@@ -179,18 +182,18 @@
Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId);
}
- final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) {
+ if (updateAllOverlaysForTarget(packageName, userId, 0)) {
mListener.onOverlaysChanged(packageName, userId);
}
}
void onTargetPackageUpgrading(@NonNull final String packageName, final int userId) {
if (DEBUG) {
- Slog.d(TAG, "onTargetPackageUpgrading packageName=" + packageName + " userId=" + userId);
+ Slog.d(TAG, "onTargetPackageUpgrading packageName=" + packageName + " userId="
+ + userId);
}
- if (updateAllOverlaysForTarget(packageName, userId, null)) {
+ if (updateAllOverlaysForTarget(packageName, userId, FLAG_TARGET_IS_UPGRADING)) {
mListener.onOverlaysChanged(packageName, userId);
}
}
@@ -200,8 +203,7 @@
Slog.d(TAG, "onTargetPackageUpgraded packageName=" + packageName + " userId=" + userId);
}
- final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) {
+ if (updateAllOverlaysForTarget(packageName, userId, 0)) {
mListener.onOverlaysChanged(packageName, userId);
}
}
@@ -211,7 +213,7 @@
Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId);
}
- if (updateAllOverlaysForTarget(packageName, userId, null)) {
+ if (updateAllOverlaysForTarget(packageName, userId, 0)) {
mListener.onOverlaysChanged(packageName, userId);
}
}
@@ -219,20 +221,21 @@
/**
* Returns true if the settings were modified for this target.
*/
- private boolean updateAllOverlaysForTarget(@NonNull final String packageName, final int userId,
- @Nullable final PackageInfo targetPackage) {
+ private boolean updateAllOverlaysForTarget(@NonNull final String targetPackageName,
+ final int userId, final int flags) {
boolean modified = false;
- final List<OverlayInfo> ois = mSettings.getOverlaysForTarget(packageName, userId);
+ final List<OverlayInfo> ois = mSettings.getOverlaysForTarget(targetPackageName, userId);
final int N = ois.size();
for (int i = 0; i < N; i++) {
final OverlayInfo oi = ois.get(i);
- final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName, userId);
+ final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName,
+ userId);
if (overlayPackage == null) {
modified |= mSettings.remove(oi.packageName, oi.userId);
removeIdmapIfPossible(oi);
} else {
try {
- modified |= updateState(targetPackage, overlayPackage, userId);
+ modified |= updateState(targetPackageName, oi.packageName, userId, flags);
} catch (OverlayManagerSettings.BadKeyException e) {
Slog.e(TAG, "failed to update settings", e);
modified |= mSettings.remove(oi.packageName, userId);
@@ -254,14 +257,11 @@
return;
}
- final PackageInfo targetPackage =
- mPackageManager.getPackageInfo(overlayPackage.overlayTarget, userId);
-
mSettings.init(packageName, userId, overlayPackage.overlayTarget,
overlayPackage.applicationInfo.getBaseCodePath(),
overlayPackage.isStaticOverlayPackage(), overlayPackage.overlayPriority);
try {
- if (updateState(targetPackage, overlayPackage, userId)) {
+ if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) {
mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
}
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -271,15 +271,64 @@
}
void onOverlayPackageChanged(@NonNull final String packageName, final int userId) {
- Slog.wtf(TAG, "onOverlayPackageChanged called, but only pre-installed overlays supported");
+ if (DEBUG) {
+ Slog.d(TAG, "onOverlayPackageChanged packageName=" + packageName + " userId=" + userId);
+ }
+
+ try {
+ final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
+ if (updateState(oi.targetPackageName, packageName, userId, 0)) {
+ mListener.onOverlaysChanged(oi.targetPackageName, userId);
+ }
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ Slog.e(TAG, "failed to update settings", e);
+ }
}
void onOverlayPackageUpgrading(@NonNull final String packageName, final int userId) {
- Slog.wtf(TAG, "onOverlayPackageUpgrading called, but only pre-installed overlays supported");
+ if (DEBUG) {
+ Slog.d(TAG, "onOverlayPackageUpgrading packageName=" + packageName + " userId="
+ + userId);
+ }
+
+ try {
+ final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
+ if (updateState(oi.targetPackageName, packageName, userId, FLAG_OVERLAY_IS_UPGRADING)) {
+ removeIdmapIfPossible(oi);
+ mListener.onOverlaysChanged(oi.targetPackageName, userId);
+ }
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ Slog.e(TAG, "failed to update settings", e);
+ }
}
void onOverlayPackageUpgraded(@NonNull final String packageName, final int userId) {
- Slog.wtf(TAG, "onOverlayPackageUpgraded called, but only pre-installed overlays supported");
+ if (DEBUG) {
+ Slog.d(TAG, "onOverlayPackageUpgraded packageName=" + packageName + " userId="
+ + userId);
+ }
+
+ final PackageInfo pkg = mPackageManager.getPackageInfo(packageName, userId);
+ if (pkg == null) {
+ Slog.w(TAG, "overlay package " + packageName + " was upgraded, but couldn't be found");
+ onOverlayPackageRemoved(packageName, userId);
+ return;
+ }
+
+ try {
+ final OverlayInfo oldOi = mSettings.getOverlayInfo(packageName, userId);
+ if (!oldOi.targetPackageName.equals(pkg.overlayTarget)) {
+ mSettings.init(packageName, userId, pkg.overlayTarget,
+ pkg.applicationInfo.getBaseCodePath(), pkg.isStaticOverlayPackage(),
+ pkg.overlayPriority);
+ }
+
+ if (updateState(pkg.overlayTarget, packageName, userId, 0)) {
+ mListener.onOverlaysChanged(pkg.overlayTarget, userId);
+ }
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ Slog.e(TAG, "failed to update settings", e);
+ }
}
void onOverlayPackageRemoved(@NonNull final String packageName, final int userId) {
@@ -333,10 +382,8 @@
try {
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
- final PackageInfo targetPackage =
- mPackageManager.getPackageInfo(oi.targetPackageName, userId);
boolean modified = mSettings.setEnabled(packageName, userId, enable);
- modified |= updateState(targetPackage, overlayPackage, userId);
+ modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0);
if (modified) {
mListener.onOverlaysChanged(oi.targetPackageName, userId);
@@ -349,7 +396,8 @@
boolean setEnabledExclusive(@NonNull final String packageName, final int userId) {
if (DEBUG) {
- Slog.d(TAG, String.format("setEnabledExclusive packageName=%s userId=%d", packageName, userId));
+ Slog.d(TAG, String.format("setEnabledExclusive packageName=%s userId=%d", packageName,
+ userId));
}
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
@@ -359,10 +407,9 @@
try {
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
- final PackageInfo targetPackage =
- mPackageManager.getPackageInfo(oi.targetPackageName, userId);
+ final String targetPackageName = oi.targetPackageName;
- List<OverlayInfo> allOverlays = getOverlayInfosForTarget(oi.targetPackageName, userId);
+ List<OverlayInfo> allOverlays = getOverlayInfosForTarget(targetPackageName, userId);
boolean modified = false;
@@ -384,15 +431,15 @@
// Disable the overlay.
modified |= mSettings.setEnabled(disabledOverlayPackageName, userId, false);
- modified |= updateState(targetPackage, disabledOverlayPackageInfo, userId);
+ modified |= updateState(targetPackageName, disabledOverlayPackageName, userId, 0);
}
// Enable the selected overlay.
modified |= mSettings.setEnabled(packageName, userId, true);
- modified |= updateState(targetPackage, overlayPackage, userId);
+ modified |= updateState(targetPackageName, packageName, userId, 0);
if (modified) {
- mListener.onOverlaysChanged(oi.targetPackageName, userId);
+ mListener.onOverlaysChanged(targetPackageName, userId);
}
return true;
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -477,7 +524,8 @@
List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName,
final int userId) {
- final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName, userId);
+ final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
+ userId);
final List<String> paths = new ArrayList<>(overlays.size());
final int N = overlays.size();
for (int i = 0; i < N; i++) {
@@ -492,36 +540,59 @@
/**
* Returns true if the settings/state was modified, false otherwise.
*/
- private boolean updateState(@Nullable final PackageInfo targetPackage,
- @NonNull final PackageInfo overlayPackage, final int userId)
+ private boolean updateState(@NonNull final String targetPackageName,
+ @NonNull final String overlayPackageName, final int userId, final int flags)
throws OverlayManagerSettings.BadKeyException {
+
+ final PackageInfo targetPackage = mPackageManager.getPackageInfo(targetPackageName, userId);
+ final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
+ userId);
+
// Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
- if (targetPackage != null &&
- !("android".equals(targetPackage.packageName)
+ if (targetPackage != null && overlayPackage != null &&
+ !("android".equals(targetPackageName)
&& overlayPackage.isStaticOverlayPackage())) {
mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
}
- boolean modified = mSettings.setBaseCodePath(overlayPackage.packageName, userId,
- overlayPackage.applicationInfo.getBaseCodePath());
+ boolean modified = false;
+ if (overlayPackage != null) {
+ modified |= mSettings.setBaseCodePath(overlayPackageName, userId,
+ overlayPackage.applicationInfo.getBaseCodePath());
+ }
- final int currentState = mSettings.getState(overlayPackage.packageName, userId);
- final int newState = calculateNewState(targetPackage, overlayPackage, userId);
+ final @OverlayInfo.State int currentState = mSettings.getState(overlayPackageName, userId);
+ final @OverlayInfo.State int newState = calculateNewState(targetPackage, overlayPackage,
+ userId, flags);
if (currentState != newState) {
if (DEBUG) {
Slog.d(TAG, String.format("%s:%d: %s -> %s",
- overlayPackage.packageName, userId,
+ overlayPackageName, userId,
OverlayInfo.stateToString(currentState),
OverlayInfo.stateToString(newState)));
}
- modified |= mSettings.setState(overlayPackage.packageName, userId, newState);
+ modified |= mSettings.setState(overlayPackageName, userId, newState);
}
return modified;
}
- private int calculateNewState(@Nullable final PackageInfo targetPackage,
- @NonNull final PackageInfo overlayPackage, final int userId)
+ private @OverlayInfo.State int calculateNewState(@Nullable final PackageInfo targetPackage,
+ @Nullable final PackageInfo overlayPackage, final int userId, final int flags)
throws OverlayManagerSettings.BadKeyException {
+
+ if ((flags & FLAG_TARGET_IS_UPGRADING) != 0) {
+ return STATE_TARGET_UPGRADING;
+ }
+
+ if ((flags & FLAG_OVERLAY_IS_UPGRADING) != 0) {
+ return STATE_OVERLAY_UPGRADING;
+ }
+
+ // assert expectation on overlay package: can only be null if the flags are used
+ if (DEBUG && overlayPackage == null) {
+ throw new IllegalArgumentException("null overlay package not compatible with no flags");
+ }
+
if (targetPackage == null) {
return STATE_MISSING_TARGET;
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 17b38de..a80cae4 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -145,7 +145,8 @@
return mItems.get(idx).setEnabled(enable);
}
- int getState(@NonNull final String packageName, final int userId) throws BadKeyException {
+ @OverlayInfo.State int getState(@NonNull final String packageName, final int userId)
+ throws BadKeyException {
final int idx = select(packageName, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
@@ -156,8 +157,8 @@
/**
* Returns true if the settings were modified, false if they remain the same.
*/
- boolean setState(@NonNull final String packageName, final int userId, final int state)
- throws BadKeyException {
+ boolean setState(@NonNull final String packageName, final int userId,
+ final @OverlayInfo.State int state) throws BadKeyException {
final int idx = select(packageName, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
@@ -413,7 +414,7 @@
private final String mPackageName;
private final String mTargetPackageName;
private String mBaseCodePath;
- private int mState;
+ private @OverlayInfo.State int mState;
private boolean mIsEnabled;
private OverlayInfo mCache;
private boolean mIsStatic;
@@ -421,7 +422,7 @@
SettingsItem(@NonNull final String packageName, final int userId,
@NonNull final String targetPackageName, @NonNull final String baseCodePath,
- final int state, final boolean isEnabled, final boolean isStatic,
+ final @OverlayInfo.State int state, final boolean isEnabled, final boolean isStatic,
final int priority) {
mPackageName = packageName;
mUserId = userId;
@@ -462,11 +463,11 @@
return false;
}
- private int getState() {
+ private @OverlayInfo.State int getState() {
return mState;
}
- private boolean setState(final int state) {
+ private boolean setState(final @OverlayInfo.State int state) {
if (mState != state) {
mState = state;
invalidateCache();
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index af446ba..6e898bb 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -377,7 +377,7 @@
failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL);
failureIntent.setLaunchToken(token);
ArrayList<AuxiliaryResolveInfo.AuxiliaryFilter> filters = null;
- boolean isWebIntent = origIntent.isBrowsableWebIntent();
+ boolean isWebIntent = origIntent.isWebIntent();
for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) {
if (shaPrefix.length > 0 && instantAppResolveInfo.shouldLetInstallerDecide()) {
Slog.e(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest"
@@ -448,7 +448,7 @@
instantAppInfo.getIntentFilters();
if (instantAppFilters == null || instantAppFilters.isEmpty()) {
// No filters on web intent; no matches, 2nd phase unnecessary.
- if (origIntent.isBrowsableWebIntent()) {
+ if (origIntent.isWebIntent()) {
return null;
}
// No filters; we need to start phase two
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 940d19f..2816bbd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2746,12 +2746,6 @@
mSettings.getDisabledSystemPkgLPr(ps.name);
if (disabledPs.codePath == null || !disabledPs.codePath.exists()
|| disabledPs.pkg == null) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Possibly deleted app: " + ps.dumpState_temp()
- + "; path: " + (disabledPs.codePath == null ? "<<NULL>>":disabledPs.codePath)
- + "; pkg: " + (disabledPs.pkg==null?"<<NULL>>":disabledPs.pkg.toString()));
-}
possiblyDeletedUpdatedSystemApps.add(ps.name);
}
}
@@ -2803,10 +2797,6 @@
for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
mSettings.removeDisabledSystemPackageLPw(deletedAppName);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "remove update; name: " + deletedAppName + ", exists? " + (deletedPkg != null));
-}
final String msg;
if (deletedPkg == null) {
// should have found an update, but, we didn't; remove everything
@@ -5982,7 +5972,7 @@
if (!skipPackageCheck && intent.getPackage() != null) {
return false;
}
- if (!intent.isBrowsableWebIntent()) {
+ if (!intent.isWebIntent()) {
// for non web intents, we should not resolve externally if an app already exists to
// handle it or if the caller didn't explicitly request it.
if ((resolvedActivities != null && resolvedActivities.size() != 0)
@@ -6693,7 +6683,7 @@
ai.packageName, ai.versionCode, null /* splitName */);
}
}
- if (intent.isBrowsableWebIntent() && auxiliaryResponse == null) {
+ if (intent.isWebIntent() && auxiliaryResponse == null) {
return result;
}
final PackageSetting ps = mSettings.mPackages.get(mInstantAppInstallerActivity.packageName);
@@ -8534,8 +8524,6 @@
return false;
}
- // Temporary to catch potential issues with refactoring
- private static boolean REFACTOR_DEBUG = true;
/**
* Adds a new package to the internal data structures during platform initialization.
* <p>After adding, the package is known to the system and available for querying.
@@ -8576,10 +8564,6 @@
synchronized (mPackages) {
renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
final String realPkgName = getRealPackageName(pkg, renamedPkgName);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Add pkg: " + pkg.packageName + (realPkgName==null?"":", realName: " + realPkgName));
-}
if (realPkgName != null) {
ensurePackageRenamed(pkg, renamedPkgName);
}
@@ -8594,12 +8578,6 @@
if (DEBUG_INSTALL && isSystemPkgUpdated) {
Slog.d(TAG, "updatedPkg = " + disabledPkgSetting);
}
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "SSP? " + scanSystemPartition
- + ", exists? " + pkgAlreadyExists + (pkgAlreadyExists?" "+pkgSetting.toString():"")
- + ", upgraded? " + isSystemPkgUpdated + (isSystemPkgUpdated?" "+disabledPkgSetting.toString():""));
-}
final SharedUserSetting sharedUserSetting = (pkg.mSharedUserId != null)
? mSettings.getSharedUserLPw(pkg.mSharedUserId,
@@ -8611,12 +8589,6 @@
Log.d(TAG, "Shared UserID " + pkg.mSharedUserId
+ " (uid=" + sharedUserSetting.userId + "):"
+ " packages=" + sharedUserSetting.packages);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Shared UserID " + pkg.mSharedUserId
- + " (uid=" + sharedUserSetting.userId + "):"
- + " packages=" + sharedUserSetting.packages);
-}
}
if (scanSystemPartition) {
@@ -8625,10 +8597,6 @@
// version on /data, cycle through all of its children packages and
// remove children that are no longer defined.
if (isSystemPkgUpdated) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Disable child packages");
-}
final int scannedChildCount = (pkg.childPackages != null)
? pkg.childPackages.size() : 0;
final int disabledChildCount = disabledPkgSetting.childPackageNames != null
@@ -8640,19 +8608,11 @@
for (int j = 0; j < scannedChildCount; j++) {
PackageParser.Package childPkg = pkg.childPackages.get(j);
if (childPkg.packageName.equals(disabledChildPackageName)) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Ignore " + disabledChildPackageName);
-}
disabledPackageAvailable = true;
break;
}
}
if (!disabledPackageAvailable) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Disable " + disabledChildPackageName);
-}
mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName);
}
}
@@ -8661,44 +8621,17 @@
disabledPkgSetting /* pkgSetting */, null /* disabledPkgSetting */,
null /* originalPkgSetting */, null, parseFlags, scanFlags,
(pkg == mPlatformPackage), user);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Scan disabled system package");
-Slog.e("TODD",
- "Pre: " + request.pkgSetting.dumpState_temp());
-}
-final ScanResult result =
scanPackageOnlyLI(request, mFactoryTest, -1L);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Post: " + (result.success?result.pkgSetting.dumpState_temp():"FAILED scan"));
-}
}
}
}
final boolean newPkgChangedPaths =
pkgAlreadyExists && !pkgSetting.codePathString.equals(pkg.codePath);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "paths changed? " + newPkgChangedPaths
- + "; old: " + pkg.codePath
- + ", new: " + (pkgSetting==null?"<<NULL>>":pkgSetting.codePathString));
-}
final boolean newPkgVersionGreater =
pkgAlreadyExists && pkg.getLongVersionCode() > pkgSetting.versionCode;
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "version greater? " + newPkgVersionGreater
- + "; old: " + pkg.getLongVersionCode()
- + ", new: " + (pkgSetting==null?"<<NULL>>":pkgSetting.versionCode));
-}
final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated
&& newPkgChangedPaths && newPkgVersionGreater;
-if (REFACTOR_DEBUG) {
- Slog.e("TODD",
- "system better? " + isSystemPkgBetter);
-}
if (isSystemPkgBetter) {
// The version of the application on /system is greater than the version on
// /data. Switch back to the application on /system.
@@ -8714,13 +8647,6 @@
+ " name: " + pkgSetting.name
+ "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
+ "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "System package changed;"
- + " name: " + pkgSetting.name
- + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
- + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
-}
final InstallArgs args = createInstallArgsForExisting(
packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString,
@@ -8732,10 +8658,6 @@
}
if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "THROW exception; system pkg version not good enough");
-}
// The version of the application on the /system partition is less than or
// equal to the version on the /data partition. Throw an exception and use
// the application already installed on the /data partition.
@@ -8766,11 +8688,6 @@
logCriticalInfo(Log.WARN,
"System package signature mismatch;"
+ " name: " + pkgSetting.name);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "System package signature mismatch;"
- + " name: " + pkgSetting.name);
-}
try (PackageFreezer freezer = freezePackage(pkg.packageName,
"scanPackageInternalLI")) {
deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null);
@@ -8785,13 +8702,6 @@
+ " name: " + pkgSetting.name
+ "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
+ "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "System package enabled;"
- + " name: " + pkgSetting.name
- + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
- + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
-}
InstallArgs args = createInstallArgsForExisting(
packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString,
pkgSetting.resourcePathString, getAppDexInstructionSets(pkgSetting));
@@ -8808,35 +8718,13 @@
+ " name: " + pkgSetting.name
+ "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode
+ "; new: " + pkg.codePath + " @ " + pkg.codePath);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "System package disabled;"
- + " name: " + pkgSetting.name
- + "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode
- + "; new: " + pkg.codePath + " @ " + pkg.codePath);
-}
}
}
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Scan package");
-Slog.e("TODD",
- "Pre: " + (pkgSetting==null?"<<NONE>>":pkgSetting.dumpState_temp()));
-}
final PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags
| SCAN_UPDATE_SIGNATURE, currentTime, user);
-if (REFACTOR_DEBUG) {
-pkgSetting = mSettings.getPackageLPr(pkg.packageName);
-Slog.e("TODD",
- "Post: " + (pkgSetting==null?"<<NONE>>":pkgSetting.dumpState_temp()));
-}
if (shouldHideSystemApp) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Disable package: " + pkg.packageName);
-}
synchronized (mPackages) {
mSettings.disableSystemPackageLPw(pkg.packageName, true);
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 3e2bd4a..ea05b74 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -97,35 +97,6 @@
+ " " + name + "/" + appId + "}";
}
- // Temporary to catch potential issues with refactoring
- public String dumpState_temp() {
- String flags = "";
- flags += ((pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : "");
- flags += ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : "");
- if ("".equals(flags)) {
- flags = "-";
- }
- String privFlags = "";
- privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : "");
- privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : "");
- privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : "");
- if ("".equals(privFlags)) {
- privFlags = "-";
- }
- return "PackageSetting{"
- + Integer.toHexString(System.identityHashCode(this))
- + " " + name + (realName == null ? "" : "("+realName+")") + "/" + appId + (sharedUser==null?"":" u:" + sharedUser.name+"("+sharedUserId+")")
- + ", ver:" + versionCode
- + ", path: " + codePath
- + ", pABI: " + primaryCpuAbiString
- + ", sABI: " + secondaryCpuAbiString
- + ", oABI: " + cpuAbiOverrideString
- + ", flags: " + flags
- + ", privFlags: " + privFlags
- + ", pkg: " + (pkg == null ? "<<NULL>>" : pkg.dumpState_temp())
- + "}";
- }
-
public void copyFrom(PackageSetting orig) {
super.copyFrom(orig);
doCopy(orig);
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index f498cdd..c280739 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -113,7 +113,6 @@
new StatFs(Environment.getRootDirectory().getAbsolutePath());
private final StatFs mStatFsTemp =
new StatFs(Environment.getDownloadCacheDirectory().getAbsolutePath());
- private final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
public StatsCompanionService(Context context) {
super();
@@ -677,12 +676,13 @@
}
private synchronized BluetoothActivityEnergyInfo pullBluetoothData() {
+ final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
- SynchronousResultReceiver bluetoothReceiver = null;
- bluetoothReceiver = new SynchronousResultReceiver("bluetooth");
+ SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver("bluetooth");
adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
return awaitControllerInfo(bluetoothReceiver);
} else {
+ Slog.e(TAG, "Failed to get bluetooth adapter!");
return null;
}
}
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index b435605..4394a99 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import android.util.ArrayMap;
import android.view.SurfaceControl;
import android.graphics.Rect;
@@ -124,17 +123,30 @@
}
}
- @VisibleForTesting
- ArrayMap<WindowContainer, DimState> mDimLayerUsers = new ArrayMap<>();
-
/**
* The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
* host, some controller of it, or one of the hosts children.
*/
private WindowContainer mHost;
+ private WindowContainer mLastRequestedDimContainer;
+ @VisibleForTesting
+ DimState mDimState;
+
+ private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+
+ @VisibleForTesting
+ interface SurfaceAnimatorStarter {
+ void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
+ AnimationAdapter anim, boolean hidden);
+ }
Dimmer(WindowContainer host) {
+ this(host, SurfaceAnimator::startAnimation);
+ }
+
+ Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
mHost = host;
+ mSurfaceAnimatorStarter = surfaceAnimatorStarter;
}
private SurfaceControl makeDimLayer() {
@@ -146,29 +158,32 @@
}
/**
- * Retreive the DimState for a given child of the host.
+ * Retrieve the DimState, creating one if it doesn't exist.
*/
private DimState getDimState(WindowContainer container) {
- DimState state = mDimLayerUsers.get(container);
- if (state == null) {
+ if (mDimState == null) {
final SurfaceControl ctl = makeDimLayer();
- state = new DimState(ctl);
+ mDimState = new DimState(ctl);
/**
* See documentation on {@link #dimAbove} to understand lifecycle management of Dim's
* via state resetting for Dim's with containers.
*/
if (container == null) {
- state.mDontReset = true;
+ mDimState.mDontReset = true;
}
- mDimLayerUsers.put(container, state);
}
- return state;
+
+ mLastRequestedDimContainer = container;
+ return mDimState;
}
private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer,
float alpha) {
final DimState d = getDimState(container);
if (container != null) {
+ // The dim method is called from WindowState.prepareSurfaces(), which is always called
+ // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+ // relative to the highest Z layer with a dim.
t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
} else {
t.setLayer(d.mDimLayer, Integer.MAX_VALUE);
@@ -237,11 +252,8 @@
* a chance to request dims to continue.
*/
void resetDimStates() {
- for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
- final DimState state = mDimLayerUsers.valueAt(i);
- if (!state.mDontReset) {
- state.mDimming = false;
- }
+ if (mDimState != null && !mDimState.mDontReset) {
+ mDimState.mDimming = false;
}
}
@@ -254,30 +266,25 @@
* @return true if any Dims were updated.
*/
boolean updateDims(SurfaceControl.Transaction t, Rect bounds) {
- boolean didSomething = false;
- for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
- DimState state = mDimLayerUsers.valueAt(i);
- WindowContainer container = mDimLayerUsers.keyAt(i);
-
- // TODO: We want to animate the addition and removal of Dim's instead of immediately
- // acting. When we do this we need to take care to account for the "Replacing Windows"
- // case (and seamless dim transfer).
- if (!state.mDimming) {
- mDimLayerUsers.removeAt(i);
- startDimExit(container, state.mSurfaceAnimator, t);
- } else {
- didSomething = true;
- // TODO: Once we use geometry from hierarchy this falls away.
- t.setSize(state.mDimLayer, bounds.width(), bounds.height());
- t.setPosition(state.mDimLayer, bounds.left, bounds.top);
- if (!state.isVisible) {
- state.isVisible = true;
- t.show(state.mDimLayer);
- startDimEnter(container, state.mSurfaceAnimator, t);
- }
- }
+ if (mDimState == null) {
+ return false;
}
- return didSomething;
+
+ if (!mDimState.mDimming) {
+ startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
+ mDimState = null;
+ return false;
+ } else {
+ // TODO: Once we use geometry from hierarchy this falls away.
+ t.setSize(mDimState.mDimLayer, bounds.width(), bounds.height());
+ t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+ if (!mDimState.isVisible) {
+ mDimState.isVisible = true;
+ t.show(mDimState.mDimLayer);
+ startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
+ }
+ return true;
+ }
}
private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
@@ -292,7 +299,7 @@
private void startAnim(WindowContainer container, SurfaceAnimator animator,
SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
- animator.startAnimation(t, new LocalAnimationAdapter(
+ mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
mHost.mService.mSurfaceAnimationRunner), false /* hidden */);
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 0512a08..83baee1 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -53,7 +53,8 @@
SurfaceControl mLeash;
private final Animatable mAnimatable;
private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
- private final Runnable mAnimationFinishedCallback;
+ @VisibleForTesting
+ final Runnable mAnimationFinishedCallback;
private boolean mAnimationStartDelayed;
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d565a6a..676fb9f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -904,16 +904,9 @@
public static WindowManagerService main(final Context context, final InputManagerService im,
final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore,
WindowManagerPolicy policy) {
- return main(context, im, haveInputMethods, showBootMsgs, onlyCore, policy,
- new SurfaceAnimationRunner());
- }
-
- public static WindowManagerService main(final Context context, final InputManagerService im,
- final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore,
- WindowManagerPolicy policy, SurfaceAnimationRunner surfaceAnimationRunner) {
DisplayThread.getHandler().runWithScissors(() ->
sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
- onlyCore, policy, surfaceAnimationRunner), 0);
+ onlyCore, policy), 0);
return sInstance;
}
@@ -936,7 +929,7 @@
private WindowManagerService(Context context, InputManagerService inputManager,
boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
- WindowManagerPolicy policy, SurfaceAnimationRunner surfaceAnimationRunner) {
+ WindowManagerPolicy policy) {
installLock(this, INDEX_WINDOW);
mContext = context;
mHaveInputMethods = haveInputMethods;
@@ -1063,7 +1056,7 @@
PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
mHoldingScreenWakeLock.setReferenceCounted(false);
- mSurfaceAnimationRunner = surfaceAnimationRunner;
+ mSurfaceAnimationRunner = new SurfaceAnimationRunner();
mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3bee1e8..48d29e3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4480,8 +4480,13 @@
if (!mAnimatingExit && mAppDied) {
mIsDimming = true;
dimmer.dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
- } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
- && !mAnimatingExit && isVisible() && !mWinAnimator.mLastHidden) {
+ } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 && isVisibleNow()
+ && !mWinAnimator.mLastHidden) {
+ // Only show a dim behind when the following is satisfied:
+ // 1. The window has the flag FLAG_DIM_BEHIND
+ // 2. The WindowToken is not hidden so dims aren't shown when the window is exiting.
+ // 3. The WS is considered visible according to the isVisible() method
+ // 4. The WSA is not hidden.
mIsDimming = true;
dimmer.dimBelow(getPendingTransaction(), this, mAttrs.dimAmount);
}
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/invalid-cert-1-no-begin-end.pem b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/invalid-cert-1-no-begin-end.pem
new file mode 100644
index 0000000..b5d513c
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/invalid-cert-1-no-begin-end.pem
@@ -0,0 +1,49 @@
+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
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/invalid-cert-2-empty-block.pem b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/invalid-cert-2-empty-block.pem
new file mode 100644
index 0000000..c412709
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/invalid-cert-2-empty-block.pem
@@ -0,0 +1,2 @@
+-----BEGIN CERTIFICATE-----
+-----END CERTIFICATE-----
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/invalid-cert-3-invalid-key.pem b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/invalid-cert-3-invalid-key.pem
new file mode 100644
index 0000000..9137b16
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/invalid-cert-3-invalid-key.pem
@@ -0,0 +1,50 @@
+-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/valid-cert-multiple-blocks.pem b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/valid-cert-multiple-blocks.pem
new file mode 100644
index 0000000..e3abeaa
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/valid-cert-multiple-blocks.pem
@@ -0,0 +1,70 @@
+-----BEGIN CERTIFICATE-----
+MIIJJzCCBQ6gAwIBAgIJAM7fBGeQ1wBkMA0GCSqGSIb3DQEBDQUAMCAxHjAcBgNV
+BAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDAeFw0xODAxMTEwNjQ5MzNaFw0zODAx
+MDYwNjQ5MzNaMCAxHjAcBgNVBAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDCCBCIw
+DQYJKoZIhvcNAQEBBQADggQPADCCBAoCggQBCcFv05M1seLPYIW7oFivh7u5otCt
+Mm7ryq0UjpbTQPcQxJQbYzcUQF7CbPYTdWDid4EuO8Zec03ownsduFhKud6H3yIQ
+4ueGiqCBoG1D6+N8fF9R8awTmAsbNg63VInx6IwcBZnjFlsIIftOFxqIJBYhiKhN
+JhPtF1i9dn+N5HjEKmkJO3pXhYhRXMp7OwL/epxzhBXFYT7aDg9pnaM6C+4hmhQ/
+0q2oyzYtAqFmrEenbtI6G47SzMc+shNTuYLtq21j/Z3uA3RwB9Szfu99F66tlgTX
+v7K7YS573hN3TQY/+nkLfFy/oF2LQRYvKHF+Nv0BHzQLzqDEYBaILcMf3i2Ce/b7
+wZjitLqFAI1swqGzgH/QpB3OrX51M/B7UCF2nB7Pa8knu4kBDGkz2Q41jAL0W/qt
+j43VwJDW0Y98OuqQiCqJrTrGdv7b/phnVVBvFrtIjYMfyK34jy5VLXctV5CSkWj5
+3ul3mvGFHJD+6nneDR4PUkmYN0khT4t/RqnQlwYE0a6Erq1+Rof6/DoWSzeBLBYV
+JaHhRy9mrudR/VcQynLKty6Zst4Lyh6aPMHcpTwGZbG+4mXnWeTaLEnGvivldksT
+XOxipcO/fXJfDss4b0glGzP3GD0+H5EZB9coYzNT47QZd9drxHdrLxtPoi+MeqkG
+gCdyFyBZO8G2k/JuyziT6hy+50VXJnl6Ujxj7MVUYAsISHsHgqETDsukQbbKvTKg
+3gxPVNN/vKWwyh7KLcFIaOEoPOgStkmVsqrXm7YLE6Bvzm8nu4rwJeAF9Yseg9BE
+Y86TRRmAI7fW4eDEPnxgCUUvcYSAh5mcayIyIr0KTuXkevwYbVRHMVmy9DaqzsP8
+YFXIqFvDXRCFSy/gMkoNb9ZoqdkmjZ+VBsjAKI+u/Haf6pgdpGZbVGKEFmaVHCkr
+tPp/gy4kE4qmd/SIaccG8o6Eb9X9fbqTTDZv34kcGgxOvBJVIaNHprTjgvYEnRaD
+KTlmZoCUmBlHzvbf68YWBmIz0K8vYPdx9r98LiUgpbTHtKZIYrJnbgPnbC9icP24
+2ksB4yaTx1QWc14vTNv1lUtv4zJEmaaoynNlETJFf/Tz0QKJxtT+l/BIAz8kEJMA
+cKsfoTx9OTtfuL85pXbCgxbKKmKn6RzxUCzSzgMboC0z6W8Zxy2gLIhqMm8AXAF7
+salwrRirV4lWsM9MOhVEgfjcv/qmQSYr1ARrwwegHRqxPA3qh11kfq5YSFU7W7+f
+JrWH6VuLZ0B1fj2+lsoMNekFA1ULD8DK7aAFIh9Y1y4Jt//xMuOPcD5PWNGFmUk7
+oPewiIUMLjXSWcgrQVYbZEDW/vooMJoo47Vg1fQPehejbONE1nBIaeRVhJcCAwEA
+AaNjMGEwHQYDVR0OBBYEFNd7oYeSi7hSGimRpTZaHLQy6+zRMB8GA1UdIwQYMBaA
+FNd7oYeSi7hSGimRpTZaHLQy6+zRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgGGMA0GCSqGSIb3DQEBDQUAA4IEAgABny011veuPplTcNeyLeKhWRn/y9VM
+QEyhaLlsC1Di35WN8owjj4+gugLlDdtEc/pvzgk+ZkAuplQkrsU907dvwkDPb4rW
+ZB5hjbr9yVyEHK1bHh7RSUkJrB3NRwqH1lyWz/LfaVfbV4CtaaERhThzZCp/MweO
+Tivg2XpSluy5s8lEEbMC/dGuIsBMitX3XLlbYa2ND3aHZLo6X9yQMFfTCjgwYG2n
+eDYupnvcLINlrlJYcrYSrIvoQn9XfsnjU3AXz+jc6xLrO3EtXDhi9e+QTfcnvRsg
+l/Hj9SZr1w1L1PPJo+KjsRavVvzaHXlBAvvUtEojJrkR3j+b5zvQB6dgVrM0zUhM
+Q9zRp5R/xqHeZ/0TTQe9kEa8QuRzuRIkK5Wbh76Eix3S+2uTsbj462nk4E0oPR8p
+iYopS4ZEFEfrKW14HOph9ZscI4l/HfDmTNfgpyFl62UrvzVBnoz+sbhTgbPHPcCX
+OUrhmpz9I5oBkyEAZYunSvzY/9SXUsz6psXHJmVzLQcne/YQTtpWzV/wGD7cjjDl
+bfzsmGCfZ8jqPBoicl5IUVdyZsJgufEZHXxKZQ7wL7R6jKrj/GtCDey1Wr2QT8VX
+5JTk9cJQFjgjDWaAyCBpGEaQvYJcaOxk2D+Wap5ax8nUfW/99vVFA0EJKsSVVzw7
+daRty0UpfZsx2Sfzpg0mymmgB8+NY6t68dL5C/xxAv5mEQ8wGJmP45iQpo5T6LVV
+MrktLf5eIzxlALQIW/AgpSH9JKCqpItdxfisAIIs9e8XHbVJJA0Jde7rtAj+TUY0
+h00xSqyfSSbpcDJ9lIoSZOJvFQdWOxB8c3vZZGGhMuRFm06sUHvcHjo8KwnbqyOx
+DGjeqt6YWty6WcNin0WciR33vGHIzwVNxNnmuY308bNsMvY9jsmd37hdmmwnmQge
+7AIa7TMPjaKm0vV/1ztFSODWCI2K7klmL2MtOJMGfqUeOfjPANbS3lMJBAH9qxLM
+7Kng+nfqVtt+NG9MxcTbP80FkBa/6JxGgjjsiwDmhr2MTCYOK/eD+WZikMOieyvH
+m2vgxYCdWrhaGfc3t6oQ2YO+mXI7e6d3F3a90UUYkBIgje9zu0RLxnBBhuoRyGwv
+uQAlqgMDBZIzTO0Vnwew7KRLdzLhWbiikhi81q6Lg62aWjbdF6Ue6AVXch+dqmr+
+9aVt0Y6ETTS77nrQyglyLKIeNx6cEHDjETXlPYGbCAlrdKAdTA4ngnBZnzGQ/8zg
+tP9zvIJVA6cuOAn8GFEsrb7GN20QSDwyJWrYi6f+m64D9rOK4Jz4t+lEfjcfJeM/
+UcNlhmATcMHXWPCoKkOfll4PBc/Wigv1xYw70RZ4pai07LzJxNHYhvpE3Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDCDCB8aADAgECAgYBYOlweDswDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi
+R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTEwODE1
+NTBaFw0yMDAxMTIwODE1NTBaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW
+YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu
+tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl
+4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBABZALhC9j3hpZ0AgN0tsqAP2Ix21
+tNOcvo/aFJuSFanOM4DZbycZEYAo5rorvuFu7eXETBKDGnI5xreNAoQsaj/dyCHu
+HKIn5P7yCmKvG2sV2TQ5go+0xV2x8BhTrtUWLeHvUbM3fXipa3NrordbA8MgzXwr
+GR1Y1FuMOn5n4kiuHJ2sQTbDdzSQSK5VpH+6rjARlfOCyLUX0u8UKRRH81qhIQWb
+UFMp9q1CVfiLP2O3CdDdpZXCysdflIb62TWnma+I8jqMryyxrMVs9kpfa8zkX9qe
+33Vxp+QaQTqQ07/7KYVw869MeFn+bXeHnjUhqGY6S8M71vrTMG3M5p8Sq9LmV8Y5
+7YB5uqKap2Inf0FOuJS7h7nVVzU/kOFkepaQVHyScwTPuuXNgpQg8XZnN/AWfRwJ
+hf5zE6vXXTHMzQA1mY2eEhxGfpryv7LH8pvfcyTakdBlw8aMJjKdre8xLLGZeVCa
+79plkfYD0rMrxtRHCGyTKGzUcx/B9kYJK5qBgJiDJLKF3XwGbAs/F8CyEPihjvj4
+M2EoeyhmHWKLYsps6+uTksJ+PxZU14M7672K2y8BdulyfkZIhili118XnRykKkMf
+JLQJKMqZx5O0B9bF8yQdcGKEGEwMQt5ENdH8HeiwLm4QS3VzFXYetgUPCM5lPDIp
+BuwwuQxvQDF4pmQd
+-----END CERTIFICATE-----
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/valid-cert.pem b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/valid-cert.pem
new file mode 100644
index 0000000..5c16a1f
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/pem/valid-cert.pem
@@ -0,0 +1,204 @@
+-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ ce:df:04:67:90:d7:00:64
+ Signature Algorithm: sha512WithRSAEncryption
+ Issuer: CN = Google CryptAuthVault
+ Validity
+ Not Before: Jan 11 06:49:33 2018 GMT
+ Not After : Jan 6 06:49:33 2038 GMT
+ Subject: CN = Google CryptAuthVault
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (8196 bit)
+ Modulus:
+ 09:c1:6f:d3:93:35:b1:e2:cf:60:85:bb:a0:58:af:
+ 87:bb:b9:a2:d0:ad:32:6e:eb:ca:ad:14:8e:96:d3:
+ 40:f7:10:c4:94:1b:63:37:14:40:5e:c2:6c:f6:13:
+ 75:60:e2:77:81:2e:3b:c6:5e:73:4d:e8:c2:7b:1d:
+ b8:58:4a:b9:de:87:df:22:10:e2:e7:86:8a:a0:81:
+ a0:6d:43:eb:e3:7c:7c:5f:51:f1:ac:13:98:0b:1b:
+ 36:0e:b7:54:89:f1:e8:8c:1c:05:99:e3:16:5b:08:
+ 21:fb:4e:17:1a:88:24:16:21:88:a8:4d:26:13:ed:
+ 17:58:bd:76:7f:8d:e4:78:c4:2a:69:09:3b:7a:57:
+ 85:88:51:5c:ca:7b:3b:02:ff:7a:9c:73:84:15:c5:
+ 61:3e:da:0e:0f:69:9d:a3:3a:0b:ee:21:9a:14:3f:
+ d2:ad:a8:cb:36:2d:02:a1:66:ac:47:a7:6e:d2:3a:
+ 1b:8e:d2:cc:c7:3e:b2:13:53:b9:82:ed:ab:6d:63:
+ fd:9d:ee:03:74:70:07:d4:b3:7e:ef:7d:17:ae:ad:
+ 96:04:d7:bf:b2:bb:61:2e:7b:de:13:77:4d:06:3f:
+ fa:79:0b:7c:5c:bf:a0:5d:8b:41:16:2f:28:71:7e:
+ 36:fd:01:1f:34:0b:ce:a0:c4:60:16:88:2d:c3:1f:
+ de:2d:82:7b:f6:fb:c1:98:e2:b4:ba:85:00:8d:6c:
+ c2:a1:b3:80:7f:d0:a4:1d:ce:ad:7e:75:33:f0:7b:
+ 50:21:76:9c:1e:cf:6b:c9:27:bb:89:01:0c:69:33:
+ d9:0e:35:8c:02:f4:5b:fa:ad:8f:8d:d5:c0:90:d6:
+ d1:8f:7c:3a:ea:90:88:2a:89:ad:3a:c6:76:fe:db:
+ fe:98:67:55:50:6f:16:bb:48:8d:83:1f:c8:ad:f8:
+ 8f:2e:55:2d:77:2d:57:90:92:91:68:f9:de:e9:77:
+ 9a:f1:85:1c:90:fe:ea:79:de:0d:1e:0f:52:49:98:
+ 37:49:21:4f:8b:7f:46:a9:d0:97:06:04:d1:ae:84:
+ ae:ad:7e:46:87:fa:fc:3a:16:4b:37:81:2c:16:15:
+ 25:a1:e1:47:2f:66:ae:e7:51:fd:57:10:ca:72:ca:
+ b7:2e:99:b2:de:0b:ca:1e:9a:3c:c1:dc:a5:3c:06:
+ 65:b1:be:e2:65:e7:59:e4:da:2c:49:c6:be:2b:e5:
+ 76:4b:13:5c:ec:62:a5:c3:bf:7d:72:5f:0e:cb:38:
+ 6f:48:25:1b:33:f7:18:3d:3e:1f:91:19:07:d7:28:
+ 63:33:53:e3:b4:19:77:d7:6b:c4:77:6b:2f:1b:4f:
+ a2:2f:8c:7a:a9:06:80:27:72:17:20:59:3b:c1:b6:
+ 93:f2:6e:cb:38:93:ea:1c:be:e7:45:57:26:79:7a:
+ 52:3c:63:ec:c5:54:60:0b:08:48:7b:07:82:a1:13:
+ 0e:cb:a4:41:b6:ca:bd:32:a0:de:0c:4f:54:d3:7f:
+ bc:a5:b0:ca:1e:ca:2d:c1:48:68:e1:28:3c:e8:12:
+ b6:49:95:b2:aa:d7:9b:b6:0b:13:a0:6f:ce:6f:27:
+ bb:8a:f0:25:e0:05:f5:8b:1e:83:d0:44:63:ce:93:
+ 45:19:80:23:b7:d6:e1:e0:c4:3e:7c:60:09:45:2f:
+ 71:84:80:87:99:9c:6b:22:32:22:bd:0a:4e:e5:e4:
+ 7a:fc:18:6d:54:47:31:59:b2:f4:36:aa:ce:c3:fc:
+ 60:55:c8:a8:5b:c3:5d:10:85:4b:2f:e0:32:4a:0d:
+ 6f:d6:68:a9:d9:26:8d:9f:95:06:c8:c0:28:8f:ae:
+ fc:76:9f:ea:98:1d:a4:66:5b:54:62:84:16:66:95:
+ 1c:29:2b:b4:fa:7f:83:2e:24:13:8a:a6:77:f4:88:
+ 69:c7:06:f2:8e:84:6f:d5:fd:7d:ba:93:4c:36:6f:
+ df:89:1c:1a:0c:4e:bc:12:55:21:a3:47:a6:b4:e3:
+ 82:f6:04:9d:16:83:29:39:66:66:80:94:98:19:47:
+ ce:f6:df:eb:c6:16:06:62:33:d0:af:2f:60:f7:71:
+ f6:bf:7c:2e:25:20:a5:b4:c7:b4:a6:48:62:b2:67:
+ 6e:03:e7:6c:2f:62:70:fd:b8:da:4b:01:e3:26:93:
+ c7:54:16:73:5e:2f:4c:db:f5:95:4b:6f:e3:32:44:
+ 99:a6:a8:ca:73:65:11:32:45:7f:f4:f3:d1:02:89:
+ c6:d4:fe:97:f0:48:03:3f:24:10:93:00:70:ab:1f:
+ a1:3c:7d:39:3b:5f:b8:bf:39:a5:76:c2:83:16:ca:
+ 2a:62:a7:e9:1c:f1:50:2c:d2:ce:03:1b:a0:2d:33:
+ e9:6f:19:c7:2d:a0:2c:88:6a:32:6f:00:5c:01:7b:
+ b1:a9:70:ad:18:ab:57:89:56:b0:cf:4c:3a:15:44:
+ 81:f8:dc:bf:fa:a6:41:26:2b:d4:04:6b:c3:07:a0:
+ 1d:1a:b1:3c:0d:ea:87:5d:64:7e:ae:58:48:55:3b:
+ 5b:bf:9f:26:b5:87:e9:5b:8b:67:40:75:7e:3d:be:
+ 96:ca:0c:35:e9:05:03:55:0b:0f:c0:ca:ed:a0:05:
+ 22:1f:58:d7:2e:09:b7:ff:f1:32:e3:8f:70:3e:4f:
+ 58:d1:85:99:49:3b:a0:f7:b0:88:85:0c:2e:35:d2:
+ 59:c8:2b:41:56:1b:64:40:d6:fe:fa:28:30:9a:28:
+ e3:b5:60:d5:f4:0f:7a:17:a3:6c:e3:44:d6:70:48:
+ 69:e4:55:84:97
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ D7:7B:A1:87:92:8B:B8:52:1A:29:91:A5:36:5A:1C:B4:32:EB:EC:D1
+ X509v3 Authority Key Identifier:
+ keyid:D7:7B:A1:87:92:8B:B8:52:1A:29:91:A5:36:5A:1C:B4:32:EB:EC:D1
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ Signature Algorithm: sha512WithRSAEncryption
+ 01:9f:2d:35:d6:f7:ae:3e:99:53:70:d7:b2:2d:e2:a1:59:19:
+ ff:cb:d5:4c:40:4c:a1:68:b9:6c:0b:50:e2:df:95:8d:f2:8c:
+ 23:8f:8f:a0:ba:02:e5:0d:db:44:73:fa:6f:ce:09:3e:66:40:
+ 2e:a6:54:24:ae:c5:3d:d3:b7:6f:c2:40:cf:6f:8a:d6:64:1e:
+ 61:8d:ba:fd:c9:5c:84:1c:ad:5b:1e:1e:d1:49:49:09:ac:1d:
+ cd:47:0a:87:d6:5c:96:cf:f2:df:69:57:db:57:80:ad:69:a1:
+ 11:85:38:73:64:2a:7f:33:07:8e:4e:2b:e0:d9:7a:52:96:ec:
+ b9:b3:c9:44:11:b3:02:fd:d1:ae:22:c0:4c:8a:d5:f7:5c:b9:
+ 5b:61:ad:8d:0f:76:87:64:ba:3a:5f:dc:90:30:57:d3:0a:38:
+ 30:60:6d:a7:78:36:2e:a6:7b:dc:2c:83:65:ae:52:58:72:b6:
+ 12:ac:8b:e8:42:7f:57:7e:c9:e3:53:70:17:cf:e8:dc:eb:12:
+ eb:3b:71:2d:5c:38:62:f5:ef:90:4d:f7:27:bd:1b:20:97:f1:
+ e3:f5:26:6b:d7:0d:4b:d4:f3:c9:a3:e2:a3:b1:16:af:56:fc:
+ da:1d:79:41:02:fb:d4:b4:4a:23:26:b9:11:de:3f:9b:e7:3b:
+ d0:07:a7:60:56:b3:34:cd:48:4c:43:dc:d1:a7:94:7f:c6:a1:
+ de:67:fd:13:4d:07:bd:90:46:bc:42:e4:73:b9:12:24:2b:95:
+ 9b:87:be:84:8b:1d:d2:fb:6b:93:b1:b8:f8:eb:69:e4:e0:4d:
+ 28:3d:1f:29:89:8a:29:4b:86:44:14:47:eb:29:6d:78:1c:ea:
+ 61:f5:9b:1c:23:89:7f:1d:f0:e6:4c:d7:e0:a7:21:65:eb:65:
+ 2b:bf:35:41:9e:8c:fe:b1:b8:53:81:b3:c7:3d:c0:97:39:4a:
+ e1:9a:9c:fd:23:9a:01:93:21:00:65:8b:a7:4a:fc:d8:ff:d4:
+ 97:52:cc:fa:a6:c5:c7:26:65:73:2d:07:27:7b:f6:10:4e:da:
+ 56:cd:5f:f0:18:3e:dc:8e:30:e5:6d:fc:ec:98:60:9f:67:c8:
+ ea:3c:1a:22:72:5e:48:51:57:72:66:c2:60:b9:f1:19:1d:7c:
+ 4a:65:0e:f0:2f:b4:7a:8c:aa:e3:fc:6b:42:0d:ec:b5:5a:bd:
+ 90:4f:c5:57:e4:94:e4:f5:c2:50:16:38:23:0d:66:80:c8:20:
+ 69:18:46:90:bd:82:5c:68:ec:64:d8:3f:96:6a:9e:5a:c7:c9:
+ d4:7d:6f:fd:f6:f5:45:03:41:09:2a:c4:95:57:3c:3b:75:a4:
+ 6d:cb:45:29:7d:9b:31:d9:27:f3:a6:0d:26:ca:69:a0:07:cf:
+ 8d:63:ab:7a:f1:d2:f9:0b:fc:71:02:fe:66:11:0f:30:18:99:
+ 8f:e3:98:90:a6:8e:53:e8:b5:55:32:b9:2d:2d:fe:5e:23:3c:
+ 65:00:b4:08:5b:f0:20:a5:21:fd:24:a0:aa:a4:8b:5d:c5:f8:
+ ac:00:82:2c:f5:ef:17:1d:b5:49:24:0d:09:75:ee:eb:b4:08:
+ fe:4d:46:34:87:4d:31:4a:ac:9f:49:26:e9:70:32:7d:94:8a:
+ 12:64:e2:6f:15:07:56:3b:10:7c:73:7b:d9:64:61:a1:32:e4:
+ 45:9b:4e:ac:50:7b:dc:1e:3a:3c:2b:09:db:ab:23:b1:0c:68:
+ de:aa:de:98:5a:dc:ba:59:c3:62:9f:45:9c:89:1d:f7:bc:61:
+ c8:cf:05:4d:c4:d9:e6:b9:8d:f4:f1:b3:6c:32:f6:3d:8e:c9:
+ 9d:df:b8:5d:9a:6c:27:99:08:1e:ec:02:1a:ed:33:0f:8d:a2:
+ a6:d2:f5:7f:d7:3b:45:48:e0:d6:08:8d:8a:ee:49:66:2f:63:
+ 2d:38:93:06:7e:a5:1e:39:f8:cf:00:d6:d2:de:53:09:04:01:
+ fd:ab:12:cc:ec:a9:e0:fa:77:ea:56:db:7e:34:6f:4c:c5:c4:
+ db:3f:cd:05:90:16:bf:e8:9c:46:82:38:ec:8b:00:e6:86:bd:
+ 8c:4c:26:0e:2b:f7:83:f9:66:62:90:c3:a2:7b:2b:c7:9b:6b:
+ e0:c5:80:9d:5a:b8:5a:19:f7:37:b7:aa:10:d9:83:be:99:72:
+ 3b:7b:a7:77:17:76:bd:d1:45:18:90:12:20:8d:ef:73:bb:44:
+ 4b:c6:70:41:86:ea:11:c8:6c:2f:b9:00:25:aa:03:03:05:92:
+ 33:4c:ed:15:9f:07:b0:ec:a4:4b:77:32:e1:59:b8:a2:92:18:
+ bc:d6:ae:8b:83:ad:9a:5a:36:dd:17:a5:1e:e8:05:57:72:1f:
+ 9d:aa:6a:fe:f5:a5:6d:d1:8e:84:4d:34:bb:ee:7a:d0:ca:09:
+ 72:2c:a2:1e:37:1e:9c:10:70:e3:11:35:e5:3d:81:9b:08:09:
+ 6b:74:a0:1d:4c:0e:27:82:70:59:9f:31:90:ff:cc:e0:b4:ff:
+ 73:bc:82:55:03:a7:2e:38:09:fc:18:51:2c:ad:be:c6:37:6d:
+ 10:48:3c:32:25:6a:d8:8b:a7:fe:9b:ae:03:f6:b3:8a:e0:9c:
+ f8:b7:e9:44:7e:37:1f:25:e3:3f:51:c3:65:86:60:13:70:c1:
+ d7:58:f0:a8:2a:43:9f:96:5e:0f:05:cf:d6:8a:0b:f5:c5:8c:
+ 3b:d1:16:78:a5:a8:b4:ec:bc:c9:c4:d1:d8:86:fa:44:dd
\ No newline at end of file
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-no-endpoint-cert.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-no-endpoint-cert.xml
new file mode 100644
index 0000000..6519f49
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-no-endpoint-cert.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<certificates>
+ <metadata>
+ <serial>
+ 1000
+ </serial>
+ <creation-time>
+ 1515697631
+ </creation-time>
+ <refresh-interval>
+ 2592000
+ </refresh-interval>
+ <previous>
+ <serial>
+ 0
+ </serial>
+ <hash>
+ 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+ </hash>
+ </previous>
+ </metadata>
+ <endpoints>
+ </endpoints>
+</certificates>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-no-refresh-interval.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-no-refresh-interval.xml
new file mode 100644
index 0000000..3da0122
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-no-refresh-interval.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<certificates>
+ <metadata>
+ <serial>
+ 1000
+ </serial>
+ <creation-time>
+ 1515697631
+ </creation-time>
+ <previous>
+ <serial>
+ 0
+ </serial>
+ <hash>
+ 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+ </hash>
+ </previous>
+ </metadata>
+ <endpoints>
+ <cert>
+ MIIDCDCB8aADAgECAgYBYOlweDswDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi
+ R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTEwODE1
+ NTBaFw0yMDAxMTIwODE1NTBaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW
+ YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu
+ tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl
+ 4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBABZALhC9j3hpZ0AgN0tsqAP2Ix21
+ tNOcvo/aFJuSFanOM4DZbycZEYAo5rorvuFu7eXETBKDGnI5xreNAoQsaj/dyCHu
+ HKIn5P7yCmKvG2sV2TQ5go+0xV2x8BhTrtUWLeHvUbM3fXipa3NrordbA8MgzXwr
+ GR1Y1FuMOn5n4kiuHJ2sQTbDdzSQSK5VpH+6rjARlfOCyLUX0u8UKRRH81qhIQWb
+ UFMp9q1CVfiLP2O3CdDdpZXCysdflIb62TWnma+I8jqMryyxrMVs9kpfa8zkX9qe
+ 33Vxp+QaQTqQ07/7KYVw869MeFn+bXeHnjUhqGY6S8M71vrTMG3M5p8Sq9LmV8Y5
+ 7YB5uqKap2Inf0FOuJS7h7nVVzU/kOFkepaQVHyScwTPuuXNgpQg8XZnN/AWfRwJ
+ hf5zE6vXXTHMzQA1mY2eEhxGfpryv7LH8pvfcyTakdBlw8aMJjKdre8xLLGZeVCa
+ 79plkfYD0rMrxtRHCGyTKGzUcx/B9kYJK5qBgJiDJLKF3XwGbAs/F8CyEPihjvj4
+ M2EoeyhmHWKLYsps6+uTksJ+PxZU14M7672K2y8BdulyfkZIhili118XnRykKkMf
+ JLQJKMqZx5O0B9bF8yQdcGKEGEwMQt5ENdH8HeiwLm4QS3VzFXYetgUPCM5lPDIp
+ BuwwuQxvQDF4pmQd
+ </cert>
+ </endpoints>
+</certificates>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-no-serial.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-no-serial.xml
new file mode 100644
index 0000000..4370ff0
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-no-serial.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<certificates>
+ <metadata>
+ <creation-time>
+ 1515697631
+ </creation-time>
+ <refresh-interval>
+ 2592000
+ </refresh-interval>
+ <previous>
+ <serial>
+ 0
+ </serial>
+ <hash>
+ 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+ </hash>
+ </previous>
+ </metadata>
+ <endpoints>
+ <cert>
+ MIIDCDCB8aADAgECAgYBYOlweDswDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi
+ R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTEwODE1
+ NTBaFw0yMDAxMTIwODE1NTBaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW
+ YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu
+ tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl
+ 4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBABZALhC9j3hpZ0AgN0tsqAP2Ix21
+ tNOcvo/aFJuSFanOM4DZbycZEYAo5rorvuFu7eXETBKDGnI5xreNAoQsaj/dyCHu
+ HKIn5P7yCmKvG2sV2TQ5go+0xV2x8BhTrtUWLeHvUbM3fXipa3NrordbA8MgzXwr
+ GR1Y1FuMOn5n4kiuHJ2sQTbDdzSQSK5VpH+6rjARlfOCyLUX0u8UKRRH81qhIQWb
+ UFMp9q1CVfiLP2O3CdDdpZXCysdflIb62TWnma+I8jqMryyxrMVs9kpfa8zkX9qe
+ 33Vxp+QaQTqQ07/7KYVw869MeFn+bXeHnjUhqGY6S8M71vrTMG3M5p8Sq9LmV8Y5
+ 7YB5uqKap2Inf0FOuJS7h7nVVzU/kOFkepaQVHyScwTPuuXNgpQg8XZnN/AWfRwJ
+ hf5zE6vXXTHMzQA1mY2eEhxGfpryv7LH8pvfcyTakdBlw8aMJjKdre8xLLGZeVCa
+ 79plkfYD0rMrxtRHCGyTKGzUcx/B9kYJK5qBgJiDJLKF3XwGbAs/F8CyEPihjvj4
+ M2EoeyhmHWKLYsps6+uTksJ+PxZU14M7672K2y8BdulyfkZIhili118XnRykKkMf
+ JLQJKMqZx5O0B9bF8yQdcGKEGEwMQt5ENdH8HeiwLm4QS3VzFXYetgUPCM5lPDIp
+ BuwwuQxvQDF4pmQd
+ </cert>
+ </endpoints>
+</certificates>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-two-refresh-intervals.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-two-refresh-intervals.xml
new file mode 100644
index 0000000..0f4e8a3
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-two-refresh-intervals.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<certificates>
+ <metadata>
+ <serial>
+ 1000
+ </serial>
+ <creation-time>
+ 1515697631
+ </creation-time>
+ <refresh-interval>
+ 2592000
+ </refresh-interval>
+ <refresh-interval>
+ 2592000
+ </refresh-interval>
+ <previous>
+ <serial>
+ 0
+ </serial>
+ <hash>
+ 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+ </hash>
+ </previous>
+ </metadata>
+ <endpoints>
+ <cert>
+ MIIDCDCB8aADAgECAgYBYOlweDswDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi
+ R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTEwODE1
+ NTBaFw0yMDAxMTIwODE1NTBaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW
+ YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu
+ tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl
+ 4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBABZALhC9j3hpZ0AgN0tsqAP2Ix21
+ tNOcvo/aFJuSFanOM4DZbycZEYAo5rorvuFu7eXETBKDGnI5xreNAoQsaj/dyCHu
+ HKIn5P7yCmKvG2sV2TQ5go+0xV2x8BhTrtUWLeHvUbM3fXipa3NrordbA8MgzXwr
+ GR1Y1FuMOn5n4kiuHJ2sQTbDdzSQSK5VpH+6rjARlfOCyLUX0u8UKRRH81qhIQWb
+ UFMp9q1CVfiLP2O3CdDdpZXCysdflIb62TWnma+I8jqMryyxrMVs9kpfa8zkX9qe
+ 33Vxp+QaQTqQ07/7KYVw869MeFn+bXeHnjUhqGY6S8M71vrTMG3M5p8Sq9LmV8Y5
+ 7YB5uqKap2Inf0FOuJS7h7nVVzU/kOFkepaQVHyScwTPuuXNgpQg8XZnN/AWfRwJ
+ hf5zE6vXXTHMzQA1mY2eEhxGfpryv7LH8pvfcyTakdBlw8aMJjKdre8xLLGZeVCa
+ 79plkfYD0rMrxtRHCGyTKGzUcx/B9kYJK5qBgJiDJLKF3XwGbAs/F8CyEPihjvj4
+ M2EoeyhmHWKLYsps6+uTksJ+PxZU14M7672K2y8BdulyfkZIhili118XnRykKkMf
+ JLQJKMqZx5O0B9bF8yQdcGKEGEwMQt5ENdH8HeiwLm4QS3VzFXYetgUPCM5lPDIp
+ BuwwuQxvQDF4pmQd
+ </cert>
+ </endpoints>
+</certificates>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-two-serials.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-two-serials.xml
new file mode 100644
index 0000000..a2685aa
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-cert-file-two-serials.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<certificates>
+ <metadata>
+ <serial>
+ 1000
+ </serial>
+ <serial>
+ 1000
+ </serial>
+ <creation-time>
+ 1515697631
+ </creation-time>
+ <refresh-interval>
+ 2592000
+ </refresh-interval>
+ <previous>
+ <serial>
+ 0
+ </serial>
+ <hash>
+ 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+ </hash>
+ </previous>
+ </metadata>
+ <endpoints>
+ <cert>
+ MIIDCDCB8aADAgECAgYBYOlweDswDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi
+ R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTEwODE1
+ NTBaFw0yMDAxMTIwODE1NTBaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW
+ YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu
+ tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl
+ 4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBABZALhC9j3hpZ0AgN0tsqAP2Ix21
+ tNOcvo/aFJuSFanOM4DZbycZEYAo5rorvuFu7eXETBKDGnI5xreNAoQsaj/dyCHu
+ HKIn5P7yCmKvG2sV2TQ5go+0xV2x8BhTrtUWLeHvUbM3fXipa3NrordbA8MgzXwr
+ GR1Y1FuMOn5n4kiuHJ2sQTbDdzSQSK5VpH+6rjARlfOCyLUX0u8UKRRH81qhIQWb
+ UFMp9q1CVfiLP2O3CdDdpZXCysdflIb62TWnma+I8jqMryyxrMVs9kpfa8zkX9qe
+ 33Vxp+QaQTqQ07/7KYVw869MeFn+bXeHnjUhqGY6S8M71vrTMG3M5p8Sq9LmV8Y5
+ 7YB5uqKap2Inf0FOuJS7h7nVVzU/kOFkepaQVHyScwTPuuXNgpQg8XZnN/AWfRwJ
+ hf5zE6vXXTHMzQA1mY2eEhxGfpryv7LH8pvfcyTakdBlw8aMJjKdre8xLLGZeVCa
+ 79plkfYD0rMrxtRHCGyTKGzUcx/B9kYJK5qBgJiDJLKF3XwGbAs/F8CyEPihjvj4
+ M2EoeyhmHWKLYsps6+uTksJ+PxZU14M7672K2y8BdulyfkZIhili118XnRykKkMf
+ JLQJKMqZx5O0B9bF8yQdcGKEGEwMQt5ENdH8HeiwLm4QS3VzFXYetgUPCM5lPDIp
+ BuwwuQxvQDF4pmQd
+ </cert>
+ </endpoints>
+</certificates>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-no-signature.sig.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-no-signature.sig.xml
new file mode 100644
index 0000000..5dc8ffa
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-no-signature.sig.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<signature>
+ <certificate>
+ MIIDCDCB8aADAgECAgYBYOlweDswDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi
+ R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTEwODE1
+ NTBaFw0yMDAxMTIwODE1NTBaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW
+ YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu
+ tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl
+ 4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBABZALhC9j3hpZ0AgN0tsqAP2Ix21
+ tNOcvo/aFJuSFanOM4DZbycZEYAo5rorvuFu7eXETBKDGnI5xreNAoQsaj/dyCHu
+ HKIn5P7yCmKvG2sV2TQ5go+0xV2x8BhTrtUWLeHvUbM3fXipa3NrordbA8MgzXwr
+ GR1Y1FuMOn5n4kiuHJ2sQTbDdzSQSK5VpH+6rjARlfOCyLUX0u8UKRRH81qhIQWb
+ UFMp9q1CVfiLP2O3CdDdpZXCysdflIb62TWnma+I8jqMryyxrMVs9kpfa8zkX9qe
+ 33Vxp+QaQTqQ07/7KYVw869MeFn+bXeHnjUhqGY6S8M71vrTMG3M5p8Sq9LmV8Y5
+ 7YB5uqKap2Inf0FOuJS7h7nVVzU/kOFkepaQVHyScwTPuuXNgpQg8XZnN/AWfRwJ
+ hf5zE6vXXTHMzQA1mY2eEhxGfpryv7LH8pvfcyTakdBlw8aMJjKdre8xLLGZeVCa
+ 79plkfYD0rMrxtRHCGyTKGzUcx/B9kYJK5qBgJiDJLKF3XwGbAs/F8CyEPihjvj4
+ M2EoeyhmHWKLYsps6+uTksJ+PxZU14M7672K2y8BdulyfkZIhili118XnRykKkMf
+ JLQJKMqZx5O0B9bF8yQdcGKEGEwMQt5ENdH8HeiwLm4QS3VzFXYetgUPCM5lPDIp
+ BuwwuQxvQDF4pmQd
+ </certificate>
+</signature>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-no-signer-cert.sig.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-no-signer-cert.sig.xml
new file mode 100644
index 0000000..aa81295
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-no-signer-cert.sig.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<signature>
+ <value>
+ VEVTVA==
+ </value>
+</signature>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-two-signatures.sig.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-two-signatures.sig.xml
new file mode 100644
index 0000000..09d0f44
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-two-signatures.sig.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<signature>
+ <certificate>
+ MIIDCDCB8aADAgECAgYBYOlweDswDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi
+ R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTEwODE1
+ NTBaFw0yMDAxMTIwODE1NTBaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW
+ YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu
+ tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl
+ 4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBABZALhC9j3hpZ0AgN0tsqAP2Ix21
+ tNOcvo/aFJuSFanOM4DZbycZEYAo5rorvuFu7eXETBKDGnI5xreNAoQsaj/dyCHu
+ HKIn5P7yCmKvG2sV2TQ5go+0xV2x8BhTrtUWLeHvUbM3fXipa3NrordbA8MgzXwr
+ GR1Y1FuMOn5n4kiuHJ2sQTbDdzSQSK5VpH+6rjARlfOCyLUX0u8UKRRH81qhIQWb
+ UFMp9q1CVfiLP2O3CdDdpZXCysdflIb62TWnma+I8jqMryyxrMVs9kpfa8zkX9qe
+ 33Vxp+QaQTqQ07/7KYVw869MeFn+bXeHnjUhqGY6S8M71vrTMG3M5p8Sq9LmV8Y5
+ 7YB5uqKap2Inf0FOuJS7h7nVVzU/kOFkepaQVHyScwTPuuXNgpQg8XZnN/AWfRwJ
+ hf5zE6vXXTHMzQA1mY2eEhxGfpryv7LH8pvfcyTakdBlw8aMJjKdre8xLLGZeVCa
+ 79plkfYD0rMrxtRHCGyTKGzUcx/B9kYJK5qBgJiDJLKF3XwGbAs/F8CyEPihjvj4
+ M2EoeyhmHWKLYsps6+uTksJ+PxZU14M7672K2y8BdulyfkZIhili118XnRykKkMf
+ JLQJKMqZx5O0B9bF8yQdcGKEGEwMQt5ENdH8HeiwLm4QS3VzFXYetgUPCM5lPDIp
+ BuwwuQxvQDF4pmQd
+ </certificate>
+ <value>
+ VEVTVA==
+ </value>
+ <value>
+ VEVTVA==
+ </value>
+</signature>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-two-signer-certs.sig.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-two-signer-certs.sig.xml
new file mode 100644
index 0000000..44e8993
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/invalid-sig-file-two-signer-certs.sig.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<signature>
+ <certificate>
+ signer certificate 1
+ </certificate>
+ <certificate>
+ signer certificate 2
+ </certificate>
+ <value>
+ VEVTVA==
+ </value>
+</signature>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-cert-file-no-intermediates.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-cert-file-no-intermediates.xml
new file mode 100644
index 0000000..e59bf36
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-cert-file-no-intermediates.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<certificates>
+ <metadata>
+ <serial>
+ 1000
+ </serial>
+ <creation-time>
+ 1515697631
+ </creation-time>
+ <refresh-interval>
+ 2592000
+ </refresh-interval>
+ <previous>
+ <serial>
+ 0
+ </serial>
+ <hash>
+ 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+ </hash>
+ </previous>
+ </metadata>
+ <endpoints>
+ <cert>
+ MIIDCDCB8aADAgECAgYBYOlweDswDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi
+ R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTEwODE1
+ NTBaFw0yMDAxMTIwODE1NTBaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW
+ YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu
+ tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl
+ 4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBABZALhC9j3hpZ0AgN0tsqAP2Ix21
+ tNOcvo/aFJuSFanOM4DZbycZEYAo5rorvuFu7eXETBKDGnI5xreNAoQsaj/dyCHu
+ HKIn5P7yCmKvG2sV2TQ5go+0xV2x8BhTrtUWLeHvUbM3fXipa3NrordbA8MgzXwr
+ GR1Y1FuMOn5n4kiuHJ2sQTbDdzSQSK5VpH+6rjARlfOCyLUX0u8UKRRH81qhIQWb
+ UFMp9q1CVfiLP2O3CdDdpZXCysdflIb62TWnma+I8jqMryyxrMVs9kpfa8zkX9qe
+ 33Vxp+QaQTqQ07/7KYVw869MeFn+bXeHnjUhqGY6S8M71vrTMG3M5p8Sq9LmV8Y5
+ 7YB5uqKap2Inf0FOuJS7h7nVVzU/kOFkepaQVHyScwTPuuXNgpQg8XZnN/AWfRwJ
+ hf5zE6vXXTHMzQA1mY2eEhxGfpryv7LH8pvfcyTakdBlw8aMJjKdre8xLLGZeVCa
+ 79plkfYD0rMrxtRHCGyTKGzUcx/B9kYJK5qBgJiDJLKF3XwGbAs/F8CyEPihjvj4
+ M2EoeyhmHWKLYsps6+uTksJ+PxZU14M7672K2y8BdulyfkZIhili118XnRykKkMf
+ JLQJKMqZx5O0B9bF8yQdcGKEGEwMQt5ENdH8HeiwLm4QS3VzFXYetgUPCM5lPDIp
+ BuwwuQxvQDF4pmQd
+ </cert>
+ </endpoints>
+</certificates>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-cert-file.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-cert-file.xml
new file mode 100644
index 0000000..be4893f
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-cert-file.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<certificates>
+ <metadata>
+ <serial>
+ 1000
+ </serial>
+ <creation-time>
+ 1515697631
+ </creation-time>
+ <refresh-interval>
+ 2592000
+ </refresh-interval>
+ <previous>
+ <serial>
+ 0
+ </serial>
+ <hash>
+ 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+ </hash>
+ </previous>
+ </metadata>
+ <intermediates>
+ <cert>
+ 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
+ +a0fa5B3pxc8BN0LsHA0stT/Y2o=
+ </cert>
+ <cert>
+ MIIESTCCAjGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwiR29v
+ Z2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTIwMDM4MDNa
+ Fw0yMzAxMTEwMDM4MDNaMDoxODA2BgNVBAMML0dvb2dsZSBDcnlwdEF1dGhWYXVs
+ dCBJbnRlcm1lZGlhdGUgSW50ZXJtZWRpYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOC
+ AQ8AMIIBCgKCAQEA0v3bN3MwKifDAkF1524XzuaxYtk1sQKUlAlNngh+Qv4RjCkX
+ TECi7po8LeNsY+hWxmW3XZ22RBphe/yP4YcOdbqlIjZYNx3b75hCSJCadOkdW+Z9
+ f6+tKsHgeUja6r9r2TThzileImAvjXShe7GZYW8csPv6HaEVRXQlu8fGAZf8skmJ
+ EMfJx84//WeULdVz94beDhi9YAf4gLfmOayQcdWhDcMYI39knJcRny1ffRGgb1Hf
+ lE+3/a3aGFeODaxfkPaGRxEhzhZ/JDBiNgUAH/u7C5nxqa2WOu5e0wq3S0TndIOE
+ hmnwCE2GvxADFQst+rSsOn4EHit70hv4CfrMRQIDAQABo2YwZDAdBgNVHQ4EFgQU
+ 0dKv4xTaEmdwHyox3tY8H8XVDGIwHwYDVR0jBBgwFoAURPUcR0+1NxD9b3Y0czWD
+ K1OxLHgwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI
+ hvcNAQELBQADggIBAJaArqLlJ2SLQ8JRwanD6LPlqQxucQ+x/LztTQpzPrsFfJds
+ E/sDZr2rXhDpz/bifIdj/DCbQ6/3w+t5JdFjT8GjXLgz1kCa/4W409FgSTgy1ENn
+ AMUU6pFIbOq/Qy/71uOTrrFWYTN5Vk+RBGxx5iDfHjDYraudi4VlcNkal4OyM98n
+ N3qp9cZD0RtWxMhvq6ahgmf9cTbEw6+l8yf/bogGLBYXXYeOoO5Q134AxrrgfthE
+ tvyKwJkT/l3OFKRcaHrebs+V1z5gPs7zWOyO5n2Z1SAmcOGfTfKMZWwp3Hi3OTr2
+ gB3LUYKyQVhC70dka3X+IbnFg5YfzJtX6YGnHlnI1SufOkEpGQDfcc0UQAWg/lgb
+ RkfMFD9tuJomBhyqv1YaxLN8yL4ZTRU0KCvvC5I5+X/zt9kBjnHlBOdYtknZT5jz
+ 7+mjqWdpmWoAjeV5+CgIzG2k7JAm6rQuE1ZQNF0wAYxPret4NHPJFqfD5gGhdrYw
+ pEUxkcwHERA/E1CkpyqUy/Hd3kqHvnEDqzFcxBdUdmOgnbpI2nAZdEpfxmA5+M1n
+ UoxQ8ZWAZH+Mdlkw/Hx5hVjGjz8snD4QN25pj/wT+V6AR5OmYb8yfsQb2S8a8yDp
+ HzcIHW+dEWpX2boirOsrdI16kNtxYqtG7c5qWBPJy5Zjkvh9qbnfT/RQx10g
+ </cert>
+ </intermediates>
+ <endpoints>
+ <cert>
+ MIIDCDCB8aADAgECAgYBYOlweDswDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi
+ R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTEwODE1
+ NTBaFw0yMDAxMTIwODE1NTBaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW
+ YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu
+ tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl
+ 4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBABZALhC9j3hpZ0AgN0tsqAP2Ix21
+ tNOcvo/aFJuSFanOM4DZbycZEYAo5rorvuFu7eXETBKDGnI5xreNAoQsaj/dyCHu
+ HKIn5P7yCmKvG2sV2TQ5go+0xV2x8BhTrtUWLeHvUbM3fXipa3NrordbA8MgzXwr
+ GR1Y1FuMOn5n4kiuHJ2sQTbDdzSQSK5VpH+6rjARlfOCyLUX0u8UKRRH81qhIQWb
+ UFMp9q1CVfiLP2O3CdDdpZXCysdflIb62TWnma+I8jqMryyxrMVs9kpfa8zkX9qe
+ 33Vxp+QaQTqQ07/7KYVw869MeFn+bXeHnjUhqGY6S8M71vrTMG3M5p8Sq9LmV8Y5
+ 7YB5uqKap2Inf0FOuJS7h7nVVzU/kOFkepaQVHyScwTPuuXNgpQg8XZnN/AWfRwJ
+ hf5zE6vXXTHMzQA1mY2eEhxGfpryv7LH8pvfcyTakdBlw8aMJjKdre8xLLGZeVCa
+ 79plkfYD0rMrxtRHCGyTKGzUcx/B9kYJK5qBgJiDJLKF3XwGbAs/F8CyEPihjvj4
+ M2EoeyhmHWKLYsps6+uTksJ+PxZU14M7672K2y8BdulyfkZIhili118XnRykKkMf
+ JLQJKMqZx5O0B9bF8yQdcGKEGEwMQt5ENdH8HeiwLm4QS3VzFXYetgUPCM5lPDIp
+ BuwwuQxvQDF4pmQd
+ </cert>
+ <cert>
+ MIICrDCCAZSgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwOjE4MDYGA1UEAwwvR29v
+ Z2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZSBJbnRlcm1lZGlhdGUwHhcN
+ MTgwMTEyMDEwMzA5WhcNMTkwMTEyMDEwMzA5WjArMSkwJwYDVQQDDCBHb29nbGUg
+ Q3J5cHRBdXRoVmF1bHQgSW5zdGFuY2UgMjBZMBMGByqGSM49AgEGCCqGSM49AwEH
+ A0IABGhmBQyWdjsXKJRbkW4iIrvt6iqhX5t2XGt/vZS9CoOl0fs+EvJXo4kgrnx8
+ /8SGxz3pwRkFhY943QYy6a1gv/2jgZUwgZIwCQYDVR0TBAIwADAdBgNVHQ4EFgQU
+ xFmLyxUS2JHKURBtewBKRP6kQBgwVgYDVR0jBE8wTYAU0dKv4xTaEmdwHyox3tY8
+ H8XVDGKhMaQvMC0xKzApBgNVBAMMIkdvb2dsZSBDcnlwdEF1dGhWYXVsdCBJbnRl
+ cm1lZGlhdGWCAhAAMA4GA1UdDwEB/wQEAwIDCDANBgkqhkiG9w0BAQsFAAOCAQEA
+ EJWpl7HU6LxukLqhw2tVZr7IRrKIucRk+RKaaiMx1Hx2jsTTskiJRiZas/xoPSqX
+ z1K5DVgI486i7HyqnWkGH5xVzCsv+rya5FOSTS3jVtgtoA4HFEqeeAcDowPDqVw3
+ yFTA55ukZnzVaPLpDfPqkhzWiuLQ/4fI6YCmOnWB8KtHTMdyGsDSAkpoxpok++NJ
+ Lu79BoBLe2ucjN383lTlieLxmrmHjF9ryYSQczcm0v6irMOMxEovw5iT4LHiEhbm
+ DfOPW909fe/s+K3TGZ3Q6U77x8g5k9dVovMgA4pFwtREtknFjeK1wXR3/eXGcP3W
+ 0bMX1yTWYJQFWCG3DFoC5w==
+ </cert>
+ <cert>
+ MIIDkjCCAXqgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwiR29v
+ Z2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTIwMDU2MjNa
+ Fw0yMDA3MTMwMDU2MjNaMCsxKTAnBgNVBAMMIEdvb2dsZSBDcnlwdEF1dGhWYXVs
+ dCBJbnN0YW5jZSAxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEimGC4KEks4JL
+ 83DNAAGCA5sKqqxqUPgV8gxgsijlMPL0TOtyJhJ2vSFIULEa0coVFbu+fAdxt3CV
+ DbzD0vWAmaOBiDCBhTAJBgNVHRMEAjAAMB0GA1UdDgQWBBTVeDueShnq1LCqkCFL
+ MAYtpxkCuDBJBgNVHSMEQjBAgBRE9RxHT7U3EP1vdjRzNYMrU7EseKEkpCIwIDEe
+ MBwGA1UEAwwVR29vZ2xlIENyeXB0QXV0aFZhdWx0ggIQATAOBgNVHQ8BAf8EBAMC
+ AwgwDQYJKoZIhvcNAQELBQADggIBADUZCjfBwBL9SSZDkMwE3n08schTBAgZCCrv
+ XOQVPGrfbFUcMv1mT4e8a5zxE98HsCS6K4HB40RtTXkmt6nuN+NyBrAmZJrBCqvG
+ IYtGsBLLEnojgWuSpQIBQeQy9it3RFdSR/1FIPssrWUB5KrtRvd+07+Mo7tI91jE
+ EunOrocu46g6p/OKSIZ7UmwZzczn2CJsrxuNPgqdlza3ytb+TTm536ZHnqaefSZD
+ rrruNneTXoqjC2OFn/OVLHQ1ee/vrHiX70P8p1V09cccDiwMCIZskNACYgWRdLYU
+ F5aYGueoFajrb4zmMoy8DK/4lh1EsfWMsrsQK6whmPidzgz37nb3rPpiHTdxu1Fc
+ 2XM9QV3Jfj2U5FMOZTcDha7W7kb++gSnQEPTM0+Zu6lTJmcZgK4RJ7lmIfK/eVbJ
+ 6V/wplOzXSxO3jBb2LNhLbhkUzcg68EIEPxaBpXYVOU2tSo2FMgi7/YLwQLorc6Q
+ h1pUZep8T8SLpvI02GvsB8TroFr2tCvCe5A1VxBQDx9IE7nEd2N30XxqReFk8Y82
+ xZMUOA4DL33NI45KWjhcawm0tzAPFfKjta/zYvnf7rwwE4r2PVuOXdet5eN8zBje
+ yJbEpjCemADujcwtYQ8hScyj/eCT2KNbZ9ibY2yrZEEuRQanq8CLAbpvSZYIHVhg
+ Clar7+38
+ </cert>
+ </endpoints>
+</certificates>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-sig-file-no-intermediates.sig.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-sig-file-no-intermediates.sig.xml
new file mode 100644
index 0000000..9c0adcd
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-sig-file-no-intermediates.sig.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<signature>
+ <certificate>
+ MIIDCDCB8aADAgECAgYBYOlweDswDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi
+ R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTEwODE1
+ NTBaFw0yMDAxMTIwODE1NTBaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW
+ YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu
+ tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl
+ 4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBABZALhC9j3hpZ0AgN0tsqAP2Ix21
+ tNOcvo/aFJuSFanOM4DZbycZEYAo5rorvuFu7eXETBKDGnI5xreNAoQsaj/dyCHu
+ HKIn5P7yCmKvG2sV2TQ5go+0xV2x8BhTrtUWLeHvUbM3fXipa3NrordbA8MgzXwr
+ GR1Y1FuMOn5n4kiuHJ2sQTbDdzSQSK5VpH+6rjARlfOCyLUX0u8UKRRH81qhIQWb
+ UFMp9q1CVfiLP2O3CdDdpZXCysdflIb62TWnma+I8jqMryyxrMVs9kpfa8zkX9qe
+ 33Vxp+QaQTqQ07/7KYVw869MeFn+bXeHnjUhqGY6S8M71vrTMG3M5p8Sq9LmV8Y5
+ 7YB5uqKap2Inf0FOuJS7h7nVVzU/kOFkepaQVHyScwTPuuXNgpQg8XZnN/AWfRwJ
+ hf5zE6vXXTHMzQA1mY2eEhxGfpryv7LH8pvfcyTakdBlw8aMJjKdre8xLLGZeVCa
+ 79plkfYD0rMrxtRHCGyTKGzUcx/B9kYJK5qBgJiDJLKF3XwGbAs/F8CyEPihjvj4
+ M2EoeyhmHWKLYsps6+uTksJ+PxZU14M7672K2y8BdulyfkZIhili118XnRykKkMf
+ JLQJKMqZx5O0B9bF8yQdcGKEGEwMQt5ENdH8HeiwLm4QS3VzFXYetgUPCM5lPDIp
+ BuwwuQxvQDF4pmQd
+ </certificate>
+ <value>
+ VEVTVA==
+ </value>
+</signature>
diff --git a/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-sig-file.sig.xml b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-sig-file.sig.xml
new file mode 100644
index 0000000..f94b109
--- /dev/null
+++ b/services/tests/servicestests/assets/KeyStoreRecoveryControllerTest/xml/valid-sig-file.sig.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<signature>
+ <intermediates>
+ <cert>
+ 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
+ +a0fa5B3pxc8BN0LsHA0stT/Y2o=
+ </cert>
+ </intermediates>
+ <certificate>
+ MIIESTCCAjGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwiR29v
+ Z2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTIwMDM4MDNa
+ Fw0yMzAxMTEwMDM4MDNaMDoxODA2BgNVBAMML0dvb2dsZSBDcnlwdEF1dGhWYXVs
+ dCBJbnRlcm1lZGlhdGUgSW50ZXJtZWRpYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOC
+ AQ8AMIIBCgKCAQEA0v3bN3MwKifDAkF1524XzuaxYtk1sQKUlAlNngh+Qv4RjCkX
+ TECi7po8LeNsY+hWxmW3XZ22RBphe/yP4YcOdbqlIjZYNx3b75hCSJCadOkdW+Z9
+ f6+tKsHgeUja6r9r2TThzileImAvjXShe7GZYW8csPv6HaEVRXQlu8fGAZf8skmJ
+ EMfJx84//WeULdVz94beDhi9YAf4gLfmOayQcdWhDcMYI39knJcRny1ffRGgb1Hf
+ lE+3/a3aGFeODaxfkPaGRxEhzhZ/JDBiNgUAH/u7C5nxqa2WOu5e0wq3S0TndIOE
+ hmnwCE2GvxADFQst+rSsOn4EHit70hv4CfrMRQIDAQABo2YwZDAdBgNVHQ4EFgQU
+ 0dKv4xTaEmdwHyox3tY8H8XVDGIwHwYDVR0jBBgwFoAURPUcR0+1NxD9b3Y0czWD
+ K1OxLHgwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI
+ hvcNAQELBQADggIBAJaArqLlJ2SLQ8JRwanD6LPlqQxucQ+x/LztTQpzPrsFfJds
+ E/sDZr2rXhDpz/bifIdj/DCbQ6/3w+t5JdFjT8GjXLgz1kCa/4W409FgSTgy1ENn
+ AMUU6pFIbOq/Qy/71uOTrrFWYTN5Vk+RBGxx5iDfHjDYraudi4VlcNkal4OyM98n
+ N3qp9cZD0RtWxMhvq6ahgmf9cTbEw6+l8yf/bogGLBYXXYeOoO5Q134AxrrgfthE
+ tvyKwJkT/l3OFKRcaHrebs+V1z5gPs7zWOyO5n2Z1SAmcOGfTfKMZWwp3Hi3OTr2
+ gB3LUYKyQVhC70dka3X+IbnFg5YfzJtX6YGnHlnI1SufOkEpGQDfcc0UQAWg/lgb
+ RkfMFD9tuJomBhyqv1YaxLN8yL4ZTRU0KCvvC5I5+X/zt9kBjnHlBOdYtknZT5jz
+ 7+mjqWdpmWoAjeV5+CgIzG2k7JAm6rQuE1ZQNF0wAYxPret4NHPJFqfD5gGhdrYw
+ pEUxkcwHERA/E1CkpyqUy/Hd3kqHvnEDqzFcxBdUdmOgnbpI2nAZdEpfxmA5+M1n
+ UoxQ8ZWAZH+Mdlkw/Hx5hVjGjz8snD4QN25pj/wT+V6AR5OmYb8yfsQb2S8a8yDp
+ HzcIHW+dEWpX2boirOsrdI16kNtxYqtG7c5qWBPJy5Zjkvh9qbnfT/RQx10g
+ </certificate>
+ <value>
+ zELcMKbEb82mjWdhaV62Po4Tn/fEnHg7TMQzlz3cpDP3uzGKXg4fvCa+yDYrEYqm17uywOfFQpJs
+ pVjoUMINdnYogO44mul+E+m/klzSQN3GbmvYDtKpFsSGqsymyFSg8Bv2LeDLx2Pisc3sLUhxwKN4
+ 8B6MwfZ3qUnRa5/ySk0bzEYYiRWsYR5oY7vK0kAI5c4Oi77E0W440FaEhnT7WxucFUnAhZbOSwXA
+ apE39BXu6ZbAPpTSc4f+uMErF7cRGbIODcAQJko6yjliBmvnCxj0ct7qzwUpgJojy/G5DLaVx3MF
+ Xnee9peYCrKFWpB+Z/7io0/Fqs/fDF7U25BsXA==
+ </value>
+</signature>
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java
new file mode 100644
index 0000000..d08dab4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java
@@ -0,0 +1,356 @@
+/*
+ * 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.server.locksettings.recoverablekeystore.certificate;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.io.InputStream;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.cert.CertPath;
+import java.security.cert.X509Certificate;
+import java.security.spec.ECGenParameterSpec;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.w3c.dom.Element;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class CertUtilsTest {
+
+ private static final String XML_STR = ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ + "<!-- comment 1 -->"
+ + "<root>\n\n\n\r\r\r"
+ + " <node1>\r\n\r\n"
+ + " node1-1</node1>"
+ + " <!-- comment 2 -->"
+ + " <node1>node1-2"
+ + " \n\r\n\r</node1>"
+ + " <node2>"
+ + " <node1> node2-node1-1</node1>"
+ + " <node1>node2-node1-2 </node1>"
+ + " <!-- comment 3 -->"
+ + " <node1> node2-node1-3 </node1>"
+ + " </node2>"
+ + "</root>";
+
+ private static final String SIGNED_STR = "abcdefg\n";
+ private static final String SIGNATURE_BASE64 = ""
+ + "KxBt9B3pwL3/59SrjTJTpuhc9JRxLOUNwNr3J4EEdXj5BqkYOUeXIOjyBGp8XaOnmuW8WmBxhko3"
+ + "yTR3/M9x0/pJuKDgqQSSFG+I56O/IAri7DmMBfY8QqcgiF8RaR86G7mWXUIdu8ixEtpKa//T4bN7"
+ + "c8Txvt96ApAcW0wJDihfCqDEXyi56pFCp+qEZuL4fS8iZtZTUkvxim1tb2/IsZ9OyDd9BWxp+JTs"
+ + "zihzH6xqnUCa1aELSUZnU8OzWGeuKpVDQDbDMtQpcxJ9o+6L6wO5vmQutZAulgw5gRPGhYWVs8+0"
+ + "ATdNEbv8TSomkXkZ3/lMYnmPXKmaHxcP4330DA==";
+ private static final PublicKey SIGNER_PUBLIC_KEY = TestData.INTERMEDIATE_CA_2.getPublicKey();
+
+ @Test
+ public void decodeCert_readPemFile_succeeds_singleBlock() throws Exception {
+ InputStream f = TestData.openTestFile("pem/valid-cert.pem");
+ X509Certificate cert = CertUtils.decodeCert(f);
+ assertThat(cert).isEqualTo(TestData.ROOT_CA_TRUSTED);
+ }
+
+ @Test
+ public void decodeCert_readPemFile_succeeds_multipleBlocks() throws Exception {
+ InputStream in = TestData.openTestFile("pem/valid-cert-multiple-blocks.pem");
+ X509Certificate cert = CertUtils.decodeCert(in);
+ assertThat(cert).isEqualTo(TestData.ROOT_CA_TRUSTED);
+ }
+
+ @Test
+ public void decodeCert_readPemFile_throwsIfNoBeginEndLines() throws Exception {
+ InputStream in = TestData.openTestFile("pem/invalid-cert-1-no-begin-end.pem");
+ assertThrows(CertParsingException.class, () -> CertUtils.decodeCert(in));
+ }
+
+ @Test
+ public void decodeCert_readPemFile_throwsIfEmptyBlock() throws Exception {
+ InputStream in = TestData.openTestFile("pem/invalid-cert-2-empty-block.pem");
+ assertThrows(CertParsingException.class, () -> CertUtils.decodeCert(in));
+ }
+
+ @Test
+ public void decodeCert_readPemFile_throwsIfInvalidCert() throws Exception {
+ InputStream in = TestData.openTestFile("pem/invalid-cert-3-invalid-key.pem");
+ assertThrows(CertParsingException.class, () -> CertUtils.decodeCert(in));
+ }
+
+ @Test
+ public void decodeCert_readBytes_succeeds() throws Exception {
+ X509Certificate cert = CertUtils.decodeCert(TestData.INTERMEDIATE_CA_2.getEncoded());
+ assertThat(cert.getIssuerX500Principal().getName())
+ .isEqualTo("CN=Google CryptAuthVault Intermediate");
+ }
+
+ @Test
+ public void decodeCert_readBytes_throwsIfInvalidCert() throws Exception {
+ byte[] modifiedCertBytes = TestData.INTERMEDIATE_CA_1.getEncoded();
+ modifiedCertBytes[0] ^= (byte) 1;
+ assertThrows(CertParsingException.class, () -> CertUtils.decodeCert(modifiedCertBytes));
+ }
+
+ @Test
+ public void decodeBase64_succeeds() throws Exception {
+ assertThat(CertUtils.decodeBase64("VEVTVA==")).isEqualTo("TEST".getBytes(UTF_8));
+ }
+
+ @Test
+ public void decodeBase64_succeedsIfEmptyInput() throws Exception {
+ assertThat(CertUtils.decodeBase64("")).hasLength(0);
+ }
+
+ @Test
+ public void decodeBase64_throwsIfInvalidInput() throws Exception {
+ assertThrows(CertParsingException.class, () -> CertUtils.decodeBase64("EVTVA=="));
+ }
+
+ @Test
+ public void getXmlRootNode_succeeds() throws Exception {
+ Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8));
+ assertThat(root.getTagName()).isEqualTo("root");
+ }
+
+ @Test
+ public void getXmlRootNode_throwsIfEmptyInput() throws Exception {
+ assertThrows(CertParsingException.class, () -> CertUtils.getXmlRootNode(new byte[0]));
+ }
+
+ @Test
+ public void getXmlNodeContents_singleLevel_succeeds() throws Exception {
+ Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8));
+ assertThat(CertUtils.getXmlNodeContents(CertUtils.MUST_EXIST_UNENFORCED, root, "node1"))
+ .containsExactly("node1-1", "node1-2");
+ }
+
+ @Test
+ public void getXmlNodeContents_multipleLevels_succeeds() throws Exception {
+ Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8));
+ assertThat(CertUtils.getXmlNodeContents(CertUtils.MUST_EXIST_UNENFORCED, root, "node2", "node1"))
+ .containsExactly("node2-node1-1", "node2-node1-2", "node2-node1-3");
+ }
+
+ @Test
+ public void getXmlNodeContents_mustExistFalse_succeedsIfNotExist() throws Exception {
+ Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8));
+ assertThat(
+ CertUtils.getXmlNodeContents(
+ CertUtils.MUST_EXIST_UNENFORCED, root, "node2", "node-not-exist"))
+ .isEmpty();
+ }
+
+ @Test
+ public void getXmlNodeContents_mustExistAtLeastOne_throwsIfNotExist() throws Exception {
+ Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8));
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ CertUtils.getXmlNodeContents(
+ CertUtils.MUST_EXIST_AT_LEAST_ONE, root, "node2",
+ "node-not-exist"));
+ assertThat(expected.getMessage()).contains("must contain at least one");
+ }
+
+ @Test
+ public void getXmlNodeContents_mustExistExactlyOne_throwsIfNotExist() throws Exception {
+ Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8));
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ CertUtils.getXmlNodeContents(
+ CertUtils.MUST_EXIST_EXACTLY_ONE, root, "node-not-exist",
+ "node1"));
+ assertThat(expected.getMessage()).contains("must contain exactly one");
+ }
+
+ @Test
+ public void getXmlNodeContents_mustExistExactlyOne_throwsIfMultipleExist() throws Exception {
+ Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8));
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ CertUtils.getXmlNodeContents(
+ CertUtils.MUST_EXIST_EXACTLY_ONE, root, "node2", "node1"));
+ assertThat(expected.getMessage()).contains("must contain exactly one");
+ }
+
+ @Test
+ public void verifyRsaSha256Signature_succeeds() throws Exception {
+ CertUtils.verifyRsaSha256Signature(
+ SIGNER_PUBLIC_KEY,
+ Base64.getDecoder().decode(SIGNATURE_BASE64),
+ SIGNED_STR.getBytes(UTF_8));
+ }
+
+ @Test
+ public void verifyRsaSha256Signature_throwsIfMismatchSignature() throws Exception {
+ byte[] modifiedBytes = SIGNED_STR.getBytes(UTF_8);
+ modifiedBytes[0] ^= (byte) 1;
+ assertThrows(
+ CertValidationException.class,
+ () ->
+ CertUtils.verifyRsaSha256Signature(
+ SIGNER_PUBLIC_KEY, Base64.getDecoder().decode(SIGNATURE_BASE64),
+ modifiedBytes));
+ }
+
+ @Test
+ public void verifyRsaSha256Signature_throwsIfWrongKeyType() throws Exception {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
+ keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1"));
+ PublicKey publicKey = keyPairGenerator.generateKeyPair().getPublic();
+ assertThrows(
+ CertValidationException.class,
+ () ->
+ CertUtils.verifyRsaSha256Signature(
+ publicKey,
+ Base64.getDecoder().decode(SIGNATURE_BASE64),
+ SIGNED_STR.getBytes(UTF_8)));
+ }
+
+ @Test
+ public void buildCertPath_succeedsWithoutIntermediates() throws Exception {
+ X509Certificate rootCert = TestData.ROOT_CA_TRUSTED;
+ X509Certificate leafCert = TestData.INTERMEDIATE_CA_1;
+ CertPath certPath = CertUtils.buildCertPath(
+ CertUtils.buildPkixParams(
+ TestData.DATE_ALL_CERTS_VALID, rootCert, Collections.emptyList(),
+ leafCert));
+ assertThat(certPath.getCertificates()).containsExactly(
+ TestData.INTERMEDIATE_CA_1).inOrder();
+ }
+
+ @Test
+ public void buildCertPath_succeedsWithIntermediates() throws Exception {
+ X509Certificate rootCert = TestData.ROOT_CA_TRUSTED;
+ List<X509Certificate> intermediateCerts =
+ Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2);
+ X509Certificate leafCert = TestData.LEAF_CERT_2;
+ CertPath certPath =
+ CertUtils.buildCertPath(
+ CertUtils.buildPkixParams(
+ TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts,
+ leafCert));
+ assertThat(certPath.getCertificates())
+ .containsExactly(
+ TestData.LEAF_CERT_2, TestData.INTERMEDIATE_CA_2,
+ TestData.INTERMEDIATE_CA_1)
+ .inOrder();
+ }
+
+ @Test
+ public void buildCertPath_succeedsWithIntermediates_ignoreUnrelatedIntermedateCert()
+ throws Exception {
+ X509Certificate rootCert = TestData.ROOT_CA_TRUSTED;
+ List<X509Certificate> intermediateCerts =
+ Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2);
+ X509Certificate leafCert = TestData.LEAF_CERT_1;
+ CertPath certPath =
+ CertUtils.buildCertPath(
+ CertUtils.buildPkixParams(
+ TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts,
+ leafCert));
+ assertThat(certPath.getCertificates())
+ .containsExactly(TestData.LEAF_CERT_1, TestData.INTERMEDIATE_CA_1)
+ .inOrder();
+ }
+
+ @Test
+ public void buildCertPath_throwsIfWrongRootCommonName() throws Exception {
+ X509Certificate rootCert = TestData.ROOT_CA_DIFFERENT_COMMON_NAME;
+ List<X509Certificate> intermediateCerts =
+ Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2);
+ X509Certificate leafCert = TestData.LEAF_CERT_1;
+
+ assertThrows(
+ CertValidationException.class,
+ () ->
+ CertUtils.buildCertPath(
+ CertUtils.buildPkixParams(
+ TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts,
+ leafCert)));
+ }
+
+ @Test
+ public void buildCertPath_throwsIfMissingIntermediateCert() throws Exception {
+ X509Certificate rootCert = TestData.ROOT_CA_DIFFERENT_COMMON_NAME;
+ List<X509Certificate> intermediateCerts = Collections.emptyList();
+ X509Certificate leafCert = TestData.LEAF_CERT_1;
+
+ assertThrows(
+ CertValidationException.class,
+ () ->
+ CertUtils.buildCertPath(
+ CertUtils.buildPkixParams(
+ TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts,
+ leafCert)));
+ }
+
+ @Test
+ public void validateCert_succeeds() throws Exception {
+ X509Certificate rootCert = TestData.ROOT_CA_TRUSTED;
+ List<X509Certificate> intermediateCerts =
+ Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2);
+ X509Certificate leafCert = TestData.LEAF_CERT_2;
+ CertUtils.validateCert(TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts,
+ leafCert);
+ }
+
+ @Test
+ public void validateCert_throwsIfExpired() throws Exception {
+ X509Certificate rootCert = TestData.ROOT_CA_TRUSTED;
+ List<X509Certificate> intermediateCerts =
+ Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2);
+ X509Certificate leafCert = TestData.LEAF_CERT_2;
+ assertThrows(
+ CertValidationException.class,
+ () ->
+ CertUtils.validateCert(
+ TestData.DATE_LEAF_CERT_2_EXPIRED, rootCert, intermediateCerts,
+ leafCert));
+ }
+
+ @Test
+ public void validateCert_throwsIfWrongRootWithTheSameCommonName() throws Exception {
+ X509Certificate rootCert = TestData.ROOT_CA_DIFFERENT_KEY;
+ List<X509Certificate> intermediateCerts =
+ Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2);
+ X509Certificate leafCert = TestData.LEAF_CERT_2;
+ assertThrows(
+ CertValidationException.class,
+ () ->
+ CertUtils.validateCert(
+ TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts,
+ leafCert));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java
new file mode 100644
index 0000000..52269d9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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.server.locksettings.recoverablekeystore.certificate;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.security.cert.CertPath;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class CertXmlTest {
+
+ private byte[] certXmlBytes;
+
+ @Before
+ public void setUp() throws Exception {
+ certXmlBytes = TestData.readTestFile("xml/valid-cert-file.xml");
+ }
+
+ @Test
+ public void parse_succeeds() throws Exception {
+ CertXml certXml = CertXml.parse(certXmlBytes);
+ assertThat(certXml.getSerial()).isEqualTo(1000L);
+ assertThat(certXml.getRefreshInterval()).isEqualTo(2592000L);
+ }
+
+ @Test
+ public void parse_succeedsIfNoIntermediateCerts() throws Exception {
+ CertXml certXml =
+ CertXml.parse(TestData.readTestFile("xml/valid-cert-file-no-intermediates.xml"));
+ assertThat(certXml.getAllIntermediateCerts()).isEmpty();
+ }
+
+ @Test
+ public void parse_checkIntermediateCerts() throws Exception {
+ CertXml certXml = CertXml.parse(certXmlBytes);
+ List<X509Certificate> intermediateCerts = certXml.getAllIntermediateCerts();
+ assertThat(intermediateCerts)
+ .containsExactly(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2)
+ .inOrder();
+ }
+
+ @Test
+ public void parse_checkEndpointCerts() throws Exception {
+ CertXml certXml = CertXml.parse(certXmlBytes);
+ List<X509Certificate> endpointCerts = certXml.getAllEndpointCerts();
+ assertThat(endpointCerts).hasSize(3);
+ assertThat(endpointCerts).containsAllOf(TestData.LEAF_CERT_1, TestData.LEAF_CERT_2);
+ }
+
+ @Test
+ public void parse_throwsIfNoEndpointCert() throws Exception {
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ CertXml.parse(
+ TestData.readTestFile(
+ "xml/invalid-cert-file-no-endpoint-cert.xml")));
+ assertThat(expected.getMessage()).contains("at least one");
+ }
+
+ @Test
+ public void parse_throwsIfNoRefreshInterval() throws Exception {
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ CertXml.parse(
+ TestData.readTestFile(
+ "xml/invalid-cert-file-no-refresh-interval.xml")));
+ assertThat(expected.getMessage()).contains("exactly one");
+ }
+
+ @Test
+ public void parse_throwsIfNoSerial() throws Exception {
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ CertXml.parse(
+ TestData.readTestFile(
+ "xml/invalid-cert-file-no-serial.xml")));
+ assertThat(expected.getMessage()).contains("exactly one");
+ }
+
+ @Test
+ public void parse_throwsIfTwoRefreshIntervals() throws Exception {
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ CertXml.parse(
+ TestData.readTestFile(
+ "xml/invalid-cert-file-two-refresh-intervals"
+ + ".xml")));
+ assertThat(expected.getMessage()).contains("exactly one");
+ }
+
+ @Test
+ public void parse_throwsIfTwoSerials() throws Exception {
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ CertXml.parse(
+ TestData.readTestFile(
+ "xml/invalid-cert-file-two-serials.xml")));
+ assertThat(expected.getMessage()).contains("exactly one node");
+ }
+
+ @Test
+ public void parseAndValidateAllCerts_succeeds() throws Exception {
+ CertXml certXml = CertXml.parse(certXmlBytes);
+ for (int index = 0; index < certXml.getAllEndpointCerts().size(); index++) {
+ assertThat(
+ certXml.getEndpointCert(
+ index, TestData.DATE_ALL_CERTS_VALID, TestData.ROOT_CA_TRUSTED))
+ .isNotNull();
+ }
+ }
+
+ @Test
+ public void parseAndValidate_returnsExpectedCertPath() throws Exception {
+ CertXml certXml = CertXml.parse(certXmlBytes);
+ CertPath certPath =
+ certXml.getEndpointCert(
+ /*index=*/ 1, // TestData.LEAF_CERT_2
+ TestData.DATE_ALL_CERTS_VALID,
+ TestData.ROOT_CA_TRUSTED);
+ assertThat(certPath.getCertificates())
+ .containsExactly(
+ TestData.LEAF_CERT_2, TestData.INTERMEDIATE_CA_2,
+ TestData.INTERMEDIATE_CA_1)
+ .inOrder();
+ }
+
+ @Test
+ public void validateCert_throwsIfRootCertWithDifferentCommonName() throws Exception {
+ CertXml certXml = CertXml.parse(certXmlBytes);
+ assertThrows(
+ CertValidationException.class,
+ () ->
+ certXml.getEndpointCert(
+ /*index=*/ 0, // TestData.LEAF_CERT_1
+ TestData.DATE_ALL_CERTS_VALID,
+ TestData.ROOT_CA_DIFFERENT_COMMON_NAME));
+ }
+
+ @Test
+ public void validateCert_throwsIfRootCertWithDifferentPublicKey() throws Exception {
+ CertXml certXml = CertXml.parse(certXmlBytes);
+ assertThrows(
+ CertValidationException.class,
+ () ->
+ certXml.getEndpointCert(
+ /*index=*/ 0, // TestData.LEAF_CERT_1
+ TestData.DATE_ALL_CERTS_VALID,
+ TestData.ROOT_CA_DIFFERENT_KEY));
+ }
+
+ @Test
+ public void validateCert_throwsIfExpired() throws Exception {
+ CertXml certXml = CertXml.parse(certXmlBytes);
+ assertThrows(
+ CertValidationException.class,
+ () ->
+ certXml.getEndpointCert(
+ /*index=*/ 1, // TestData.LEAF_CERT_2
+ TestData.DATE_LEAF_CERT_2_EXPIRED,
+ TestData.ROOT_CA_TRUSTED));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/SigXmlTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/SigXmlTest.java
new file mode 100644
index 0000000..4d87006
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/SigXmlTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.server.locksettings.recoverablekeystore.certificate;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class SigXmlTest {
+
+ private byte[] certXmlBytes;
+ private byte[] sigXmlBytes;
+
+ @Before
+ public void setUp() throws Exception {
+ certXmlBytes = TestData.readTestFile("xml/valid-cert-file.xml");
+ sigXmlBytes = TestData.readTestFile("xml/valid-sig-file.sig.xml");
+ }
+
+ @Test
+ public void parseAndVerifyFileSignature_succeeds() throws Exception {
+ SigXml sigXml = SigXml.parse(sigXmlBytes);
+ sigXml.verifyFileSignature(
+ TestData.ROOT_CA_TRUSTED, certXmlBytes, TestData.DATE_ALL_CERTS_VALID);
+ }
+
+ @Test
+ public void parseAndVerifyFileSignature_throwsIfExpiredCert() throws Exception {
+ SigXml sigXml = SigXml.parse(sigXmlBytes);
+ assertThrows(
+ CertValidationException.class,
+ () ->
+ sigXml.verifyFileSignature(
+ TestData.ROOT_CA_TRUSTED, certXmlBytes,
+ TestData.DATE_INTERMEDIATE_CA_2_EXPIRED));
+ }
+
+ @Test
+ public void parseAndVerifyFileSignature_throwsIfInvalidSignature() throws Exception {
+ SigXml sigXml = SigXml.parse(sigXmlBytes);
+ byte[] modifiedBytes = sigXmlBytes.clone();
+ modifiedBytes[0] ^= (byte) 1; // Flip one bit
+ CertValidationException expected =
+ expectThrows(
+ CertValidationException.class,
+ () ->
+ sigXml.verifyFileSignature(
+ TestData.ROOT_CA_TRUSTED, modifiedBytes,
+ TestData.DATE_ALL_CERTS_VALID));
+ assertThat(expected.getMessage()).contains("signature is invalid");
+ }
+
+ @Test
+ public void parseAndVerifyFileSignature_throwsIfRootCertWithWrongCommonName() throws Exception {
+ SigXml sigXml = SigXml.parse(sigXmlBytes);
+ assertThrows(
+ CertValidationException.class,
+ () ->
+ sigXml.verifyFileSignature(
+ TestData.ROOT_CA_DIFFERENT_COMMON_NAME,
+ certXmlBytes,
+ TestData.DATE_ALL_CERTS_VALID));
+ }
+
+ @Test
+ public void parse_succeedsWithoutIntermediateCerts() throws Exception {
+ SigXml.parse(TestData.readTestFile("xml/valid-sig-file-no-intermediates.sig.xml"));
+ }
+
+ @Test
+ public void parse_throwsIfNoSignerCert() throws Exception {
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ SigXml.parse(
+ TestData.readTestFile(
+ "xml/invalid-sig-file-no-signer-cert.sig.xml")));
+ assertThat(expected.getMessage()).contains("exactly one");
+ }
+
+ @Test
+ public void parse_throwsIfTwoSignerCerts() throws Exception {
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ SigXml.parse(
+ TestData.readTestFile(
+ "xml/invalid-sig-file-two-signer-certs.sig.xml")));
+ assertThat(expected.getMessage()).contains("exactly one");
+ }
+
+ @Test
+ public void parse_throwsIfNoSignatureValue() throws Exception {
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ SigXml.parse(
+ TestData.readTestFile(
+ "xml/invalid-sig-file-no-signature.sig.xml")));
+ assertThat(expected.getMessage()).contains("exactly one");
+ }
+
+ @Test
+ public void parse_throwsIfTwoSignatureValues() throws Exception {
+ CertParsingException expected =
+ expectThrows(
+ CertParsingException.class,
+ () ->
+ SigXml.parse(
+ TestData.readTestFile(
+ "xml/invalid-sig-file-two-signatures.sig.xml")));
+ assertThat(expected.getMessage()).contains("exactly one");
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/TestData.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/TestData.java
new file mode 100644
index 0000000..5eb4166
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/TestData.java
@@ -0,0 +1,307 @@
+/*
+ * 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.server.locksettings.recoverablekeystore.certificate;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+
+import com.google.common.io.ByteStreams;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Date;
+
+/** Constants used by the tests in the folder. */
+public final class TestData {
+
+ private static final String TEST_FILE_FOLDER_NAME = "KeyStoreRecoveryControllerTest";
+
+ private TestData() {}
+
+ // Some test data that is generated by using OpenSSL command line tools.
+ private static final String ROOT_CA_TRUSTED_BASE64 = ""
+ + "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";
+ private static final String ROOT_CA_DIFFERENT_KEY_BASE64 = ""
+ + "MIIFJjCCAw6gAwIBAgIJANpazyIWdcb/MA0GCSqGSIb3DQEBCwUAMCAxHjAcBgNV"
+ + "BAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDAeFw0xODAxMjEwMzI0MjVaFw0zODAx"
+ + "MTYwMzI0MjVaMCAxHjAcBgNVBAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDCCAiIw"
+ + "DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKkAUqkRxdVy9UpI9BjQnylGVPRW"
+ + "aQsCwT2iWJ7fuCnQQon1U9nOyw2R5GYKcA8Zy+4Co++6nzRblYYXJG3Fzsj+kxei"
+ + "pZGmU11djRJDOhHPPe5jSW37Y0czWaj8jx4xvMim18dGYR7fg6SsOOYXA2y5tlvZ"
+ + "xLjvw5qpL62J5bVoAAxjng/Oc2Osu+vpv6M50pUZr0OEiFi59WlwrCCZpf1/80bT"
+ + "j2ebCKKAtTYa6+Q+oMGGxb3imSRpmQPtFcvhUPmAaocUjYM/FeIGNRv14oED/aXz"
+ + "khuPb+QNkXgk9yiokE10IeAk6oNUNDyuiMNIFy67lUrwc45lv9y0s/8fHj9pvKse"
+ + "n3+UOKAuV9atUXLdFKQwnQPt4SOmHPkXoj+5tv32RSvVeYhb0ZOpQPkRhxv4wgs7"
+ + "NldNbKhzVDM9K4M5Q2TrPK1yJKrc5/z0bDzmPOcH4AAXPvSt5PZOs0NlXUJ99BcA"
+ + "KE1sWArUhipz5mx0hxPTNEM9/8bMb//HkbZtx2log1/fc207W/AFd2FICOpRY+Sb"
+ + "CJs9WjstpisppotONvgXxZbZiypGKxpeZOb4s6y3iXtZ0FWXUZrc65b8S06fDVCa"
+ + "ZomNFDWhspHFKyueBgU6cR9K97cDo85Juy6RhnouXxi+XpXPdGFwPqVm1glFYos8"
+ + "5+Scbwwx69RihN1nAgMBAAGjYzBhMB0GA1UdDgQWBBRoosUAVtfHHeMl8/5x1m2m"
+ + "arEOoTAfBgNVHSMEGDAWgBRoosUAVtfHHeMl8/5x1m2marEOoTAPBgNVHRMBAf8E"
+ + "BTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAYbLVQlsi"
+ + "KdzCPsaybSrUtMrmkad8Gy+/QR3J16adxy/2WpxsZ1Su0YA4tFzOSWqt74C0mnUi"
+ + "i+3prW7nyEOwezs+NH0SreF4B7tO1FSj157LoHxR2WmCCwul8FfsMi0x6MvEf40v"
+ + "bMeLrRAA4ysRITua1INb05fzJpczoaW/Q1lBPXConvolnIMvYsLbCZX4/OhQQCLa"
+ + "mJx8mGrt1wcNp7Kvh+3JfuOXw+WGeCuB5sSWBsOUvhfN+8sgyk1Dtq5c7rVKKtqz"
+ + "gqHfCNZ+lYa6Vkii7plIcWYJXXa7DMmozX07mrDqdJZEocv6XCoZAKDlJorB8pQT"
+ + "im47RF60X/FCHSCK2cHbG5M+kF84xwj5fTLztLM1+RlJSJiGk6jhxJRQ+hl0Vkhp"
+ + "+u7UbUDwkUF/CJB3d1Gtfm+QtzFVKe27ClU5YFSKCXRV/K4KnkZqpyG8Py+PUtUf"
+ + "WRahp4hkWFkIoLeTnJwgAFRvp/KCtSW0/trI/vfInzqBk/qWIVhxYB8Qv9DXtKBX"
+ + "3AZ36HM2dxmjef/rpYRphuEN0ZwYdynsGy9dF0SihbR8curSg67sbtYfyw0xURhU"
+ + "Nk99YMy7T0EUYnaki/PIPK/gnJjZTX1FLCyHUR38fDJIWkGB4xr8pSrwmRoPSvNF"
+ + "dFr3YlFWwHWd1gXlwJtzeMSuVgoKVtZGmmk=";
+ private static final String ROOT_CA_DIFFERENT_COMMON_NAME_BASE64 = ""
+ + "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"
+ + "6OAzRvlk6CZWG3obkt3yaB1NhBEw8hiYvX9F";
+ private static final String INTERMEDIATE_CA_1_BASE64 = ""
+ + "MIIHMDCCAxegAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UEAwwVR29v"
+ + "Z2xlIENyeXB0QXV0aFZhdWx0MB4XDTE4MDExMjAwMjM1N1oXDTI4MDExMDAwMjM1"
+ + "N1owLTErMCkGA1UEAwwiR29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0"
+ + "ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALlhoKYuwxaatY9ERDFp"
+ + "iEygSnjy0xzaiF4uCyiTfAuboSi5QwGon3ohf0ufJF02L9lnTMoeBAg+88m8AMgW"
+ + "KFcEupabqlZfA3F/50mMCmnJvBSLXJ+chUdcAVpwcZAsq6ko22ARBxao1wu2qxNe"
+ + "D8eXiiK8DRpTtKy3wOldZJ222v35v9JGOTjORZRrcv7Z8f6I5/cSsTS+WoVk/aBt"
+ + "QqyFkcdw1zqulnlFE2rxNAVgyLlW71WJikYTDtUDeo79LvkDGjVsLo2MfpNxuK+5"
+ + "MuMqzyN5LmzXJmNCEW1O5IIIUAPhgy5s08G+3G644wCEWsAnv2FBWLBn/HmJu6Uq"
+ + "nSM2AaJN56V0tJG/yL2VgTnPJrJypNTKZW3OTCLCaYcTEbKfarxLwVWxvIWSIgkn"
+ + "0q57GYhf7O+x9vvcOUmZwVxZECorIiK4n5AWG/KD3dWI3UGGGpYsDazRngA/bQPu"
+ + "DSzBP9FBVcQt3/DMBG1s6f2Eko5f6aTFcVW9iV7aWLeIq+pQYlbmG42decj+aHLQ"
+ + "COp5KV+Q77y4kFhZQFAQ1mN4crnhuEc1K5SmjAK24zIqWbwM3ly0KSQFc9jAmONg"
+ + "0xu7kAObP3PZk85En12yLLscNmHCWYfOOEvTHf4KX7tjBl4HHp/ur+2Qwgpt9MFB"
+ + "MGqR2cni5OV6gZcRdHaEerjrAgMBAAGjZjBkMB0GA1UdDgQWBBRE9RxHT7U3EP1v"
+ + "djRzNYMrU7EseDAfBgNVHSMEGDAWgBTXe6GHkou4UhopkaU2Why0Muvs0TASBgNV"
+ + "HRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC"
+ + "BAIAAfa7FKkfh4t+G8Sv4n3gUcuEAtmpCsTtHJU67r5eSvR24fDX7sqqSIib9sQ8"
+ + "adA1FtnE3wnC4bEKnIwQLerWTN4i4V9oEKRIT5o5Rr61LyO9R+Prpo6uJVoPujFH"
+ + "GZPdIj/Qs4Xax4a11QD+70+ZmmdL6yfigVDWA4yduFdMg6heHRf3EFCbVBv5qbBS"
+ + "n8+eFKRnBZ/kQdFlYt+G+92Qqyn2uAcER6kZjIfPdnZh44SazLP37W8AkDX30Pmk"
+ + "V0PHVGCDrap44q3maI1m8NAE1jGwwmRzJL953x5XgbVGt0K/3cNoWtKLenwX/G3I"
+ + "akrgvOY5Zl0v3FRDZwGFt9UIBfZDDOGRMXIgIGs/1cvkwWpOT6dyReqDXempiQ1q"
+ + "Yy6J5VsK5WK6gEelUyoACbzgby25V6a79Q1MI7dXmFQfCcX0nAD/AZmM1HkeYgrC"
+ + "uq6fWoPOVMKML2mN90rCzkWxGaLcl5dPfad0O1LrcP48SRE5MXMWyxZZBon+wDIk"
+ + "ascyM/r4fmk4kq64YKdm2wxCDMNArAIcyBkwOaWWfabtSagxJ3qtMtxK0qBUsbLC"
+ + "yMyYpgU1h9c8rEdc4JgeE2LXJzxTKDc3SBOqbuNMlKWjYA+X+SUvVYALrQKAC+5v"
+ + "wdUhLYdAPAksqk/ZoiBjkW35FfvqQMJBY29VnDT1h7/Nxk5gu+goTA9oFIYNrNte"
+ + "+s0my+IUgYhKJBsgh7Mupv+B92GN5b3b440BMHB5QR959Jdq6BAXNUyZLM5fhZQE"
+ + "Jj/rxZFXaqq757kgUhwWBz5TDbYF7GkqTyM4k430xwJKY0AYYEHmv1UYNo5X4G3x"
+ + "SC2LhWC1b9VAykdkHbLs+IA8klxURmLmRiRj1UryhQjjT8h/FvNyPnbT1AKoElix"
+ + "QLnLi8thkJ+tQggO0hISFsIrKNfnn0V6O0VKw9UZsMigsbYG5EbzIXcAyy8Avr9n"
+ + "um7gBBZDt7fWso0+pG1UenJ+PybeuW/azQDLRw1Syz8OwU+ABRLq0JyyAtV7VPY5"
+ + "C9pkKS+bU8nECxr6dMhAbpLBHlKsyb1qtkOt1p7WagEQZFIIc6svc73+L/ET/lWn"
+ + "GBmkVVsCN7Aqyo5aXQWueXP4FUL+6O5+JALqw3qPeQgfnLkh0cUuccNND05QeEiv"
+ + "Zswc/23KJXy1XbdVKT3UP0RAF7DxstbRGQbAT3z+n931e3KhtU28OKjsFtoeq2Dj"
+ + "6STPEXh4rYFWMM8+DrJetAtBqk/i+vBwRA8f7jqIPPep/vEjPqqMOpdSVcoFQ1df"
+ + "JuOZtGfEUjFHnlDr3eGP7KUIEZvhan1zm544dDgPVTXxrY4moi2BhKEY69zRSX6B"
+ + "+a0fa5B3pxc8BN0LsHA0stT/Y2o=";
+ private static final String INTERMEDIATE_CA_2_BASE64 = ""
+ + "MIIESTCCAjGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwiR29v"
+ + "Z2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTIwMDM4MDNa"
+ + "Fw0yMzAxMTEwMDM4MDNaMDoxODA2BgNVBAMML0dvb2dsZSBDcnlwdEF1dGhWYXVs"
+ + "dCBJbnRlcm1lZGlhdGUgSW50ZXJtZWRpYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOC"
+ + "AQ8AMIIBCgKCAQEA0v3bN3MwKifDAkF1524XzuaxYtk1sQKUlAlNngh+Qv4RjCkX"
+ + "TECi7po8LeNsY+hWxmW3XZ22RBphe/yP4YcOdbqlIjZYNx3b75hCSJCadOkdW+Z9"
+ + "f6+tKsHgeUja6r9r2TThzileImAvjXShe7GZYW8csPv6HaEVRXQlu8fGAZf8skmJ"
+ + "EMfJx84//WeULdVz94beDhi9YAf4gLfmOayQcdWhDcMYI39knJcRny1ffRGgb1Hf"
+ + "lE+3/a3aGFeODaxfkPaGRxEhzhZ/JDBiNgUAH/u7C5nxqa2WOu5e0wq3S0TndIOE"
+ + "hmnwCE2GvxADFQst+rSsOn4EHit70hv4CfrMRQIDAQABo2YwZDAdBgNVHQ4EFgQU"
+ + "0dKv4xTaEmdwHyox3tY8H8XVDGIwHwYDVR0jBBgwFoAURPUcR0+1NxD9b3Y0czWD"
+ + "K1OxLHgwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI"
+ + "hvcNAQELBQADggIBAJaArqLlJ2SLQ8JRwanD6LPlqQxucQ+x/LztTQpzPrsFfJds"
+ + "E/sDZr2rXhDpz/bifIdj/DCbQ6/3w+t5JdFjT8GjXLgz1kCa/4W409FgSTgy1ENn"
+ + "AMUU6pFIbOq/Qy/71uOTrrFWYTN5Vk+RBGxx5iDfHjDYraudi4VlcNkal4OyM98n"
+ + "N3qp9cZD0RtWxMhvq6ahgmf9cTbEw6+l8yf/bogGLBYXXYeOoO5Q134AxrrgfthE"
+ + "tvyKwJkT/l3OFKRcaHrebs+V1z5gPs7zWOyO5n2Z1SAmcOGfTfKMZWwp3Hi3OTr2"
+ + "gB3LUYKyQVhC70dka3X+IbnFg5YfzJtX6YGnHlnI1SufOkEpGQDfcc0UQAWg/lgb"
+ + "RkfMFD9tuJomBhyqv1YaxLN8yL4ZTRU0KCvvC5I5+X/zt9kBjnHlBOdYtknZT5jz"
+ + "7+mjqWdpmWoAjeV5+CgIzG2k7JAm6rQuE1ZQNF0wAYxPret4NHPJFqfD5gGhdrYw"
+ + "pEUxkcwHERA/E1CkpyqUy/Hd3kqHvnEDqzFcxBdUdmOgnbpI2nAZdEpfxmA5+M1n"
+ + "UoxQ8ZWAZH+Mdlkw/Hx5hVjGjz8snD4QN25pj/wT+V6AR5OmYb8yfsQb2S8a8yDp"
+ + "HzcIHW+dEWpX2boirOsrdI16kNtxYqtG7c5qWBPJy5Zjkvh9qbnfT/RQx10g";
+ private static final String LEAF_CERT_1_BASE64 = ""
+ + "MIIDCDCB8aADAgECAgYBYOlweDswDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi"
+ + "R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAxMTEwODE1"
+ + "NTBaFw0yMDAxMTIwODE1NTBaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW"
+ + "YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu"
+ + "tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl"
+ + "4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBABZALhC9j3hpZ0AgN0tsqAP2Ix21"
+ + "tNOcvo/aFJuSFanOM4DZbycZEYAo5rorvuFu7eXETBKDGnI5xreNAoQsaj/dyCHu"
+ + "HKIn5P7yCmKvG2sV2TQ5go+0xV2x8BhTrtUWLeHvUbM3fXipa3NrordbA8MgzXwr"
+ + "GR1Y1FuMOn5n4kiuHJ2sQTbDdzSQSK5VpH+6rjARlfOCyLUX0u8UKRRH81qhIQWb"
+ + "UFMp9q1CVfiLP2O3CdDdpZXCysdflIb62TWnma+I8jqMryyxrMVs9kpfa8zkX9qe"
+ + "33Vxp+QaQTqQ07/7KYVw869MeFn+bXeHnjUhqGY6S8M71vrTMG3M5p8Sq9LmV8Y5"
+ + "7YB5uqKap2Inf0FOuJS7h7nVVzU/kOFkepaQVHyScwTPuuXNgpQg8XZnN/AWfRwJ"
+ + "hf5zE6vXXTHMzQA1mY2eEhxGfpryv7LH8pvfcyTakdBlw8aMJjKdre8xLLGZeVCa"
+ + "79plkfYD0rMrxtRHCGyTKGzUcx/B9kYJK5qBgJiDJLKF3XwGbAs/F8CyEPihjvj4"
+ + "M2EoeyhmHWKLYsps6+uTksJ+PxZU14M7672K2y8BdulyfkZIhili118XnRykKkMf"
+ + "JLQJKMqZx5O0B9bF8yQdcGKEGEwMQt5ENdH8HeiwLm4QS3VzFXYetgUPCM5lPDIp"
+ + "BuwwuQxvQDF4pmQd";
+ private static final String LEAF_CERT_2_BASE64 = ""
+ + "MIICrDCCAZSgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwOjE4MDYGA1UEAwwvR29v"
+ + "Z2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZSBJbnRlcm1lZGlhdGUwHhcN"
+ + "MTgwMTEyMDEwMzA5WhcNMTkwMTEyMDEwMzA5WjArMSkwJwYDVQQDDCBHb29nbGUg"
+ + "Q3J5cHRBdXRoVmF1bHQgSW5zdGFuY2UgMjBZMBMGByqGSM49AgEGCCqGSM49AwEH"
+ + "A0IABGhmBQyWdjsXKJRbkW4iIrvt6iqhX5t2XGt/vZS9CoOl0fs+EvJXo4kgrnx8"
+ + "/8SGxz3pwRkFhY943QYy6a1gv/2jgZUwgZIwCQYDVR0TBAIwADAdBgNVHQ4EFgQU"
+ + "xFmLyxUS2JHKURBtewBKRP6kQBgwVgYDVR0jBE8wTYAU0dKv4xTaEmdwHyox3tY8"
+ + "H8XVDGKhMaQvMC0xKzApBgNVBAMMIkdvb2dsZSBDcnlwdEF1dGhWYXVsdCBJbnRl"
+ + "cm1lZGlhdGWCAhAAMA4GA1UdDwEB/wQEAwIDCDANBgkqhkiG9w0BAQsFAAOCAQEA"
+ + "EJWpl7HU6LxukLqhw2tVZr7IRrKIucRk+RKaaiMx1Hx2jsTTskiJRiZas/xoPSqX"
+ + "z1K5DVgI486i7HyqnWkGH5xVzCsv+rya5FOSTS3jVtgtoA4HFEqeeAcDowPDqVw3"
+ + "yFTA55ukZnzVaPLpDfPqkhzWiuLQ/4fI6YCmOnWB8KtHTMdyGsDSAkpoxpok++NJ"
+ + "Lu79BoBLe2ucjN383lTlieLxmrmHjF9ryYSQczcm0v6irMOMxEovw5iT4LHiEhbm"
+ + "DfOPW909fe/s+K3TGZ3Q6U77x8g5k9dVovMgA4pFwtREtknFjeK1wXR3/eXGcP3W"
+ + "0bMX1yTWYJQFWCG3DFoC5w==";
+
+ /** The cert of the root CA. */
+ static final X509Certificate ROOT_CA_TRUSTED = decodeBase64Cert(ROOT_CA_TRUSTED_BASE64);
+ /** This root CA cert has a different Common Name than ROOT_CA_TRUSTED. */
+ static final X509Certificate ROOT_CA_DIFFERENT_COMMON_NAME =
+ decodeBase64Cert(ROOT_CA_DIFFERENT_COMMON_NAME_BASE64);
+ /** This root CA cert has the same CN as ROOT_CA_TRUSTED, but a different public key. */
+ static final X509Certificate ROOT_CA_DIFFERENT_KEY =
+ decodeBase64Cert(ROOT_CA_DIFFERENT_KEY_BASE64);
+ /** This intermediate CA cert is signed by the corresponding private key of ROOT_CA_TRUSTED. */
+ static final X509Certificate INTERMEDIATE_CA_1 = decodeBase64Cert(INTERMEDIATE_CA_1_BASE64);
+ /** This intermediate CA cert is signed by the private key of INTERMEDIATE_CA_1. */
+ static final X509Certificate INTERMEDIATE_CA_2 = decodeBase64Cert(INTERMEDIATE_CA_2_BASE64);
+ /** This leaf cert is signed by the corresponding private key of INTERMEDIATE_CA_1. */
+ static final X509Certificate LEAF_CERT_1 = decodeBase64Cert(LEAF_CERT_1_BASE64);
+ /** This leaf cert is signed by the corresponding private key of INTERMEDIATE_CA_2. */
+ static final X509Certificate LEAF_CERT_2 = decodeBase64Cert(LEAF_CERT_2_BASE64);
+
+ private static X509Certificate decodeBase64Cert(String str) {
+ try {
+ byte[] bytes = Base64.getDecoder().decode(str);
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ static final Date DATE_ALL_CERTS_VALID = new Date(1516406400000L); // Jan 20, 2018
+ static final Date DATE_LEAF_CERT_2_EXPIRED = new Date(1547254989001L); // Jan 12, 2019
+ static final Date DATE_INTERMEDIATE_CA_2_EXPIRED = new Date(1673397483001L); // Jan 11, 2023
+
+ static InputStream openTestFile(String relativePath) throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ return context.getResources().getAssets().open(TEST_FILE_FOLDER_NAME + "/" + relativePath);
+ }
+
+ static byte[] readTestFile(String relativePath) throws Exception {
+ InputStream in = openTestFile(relativePath);
+ return ByteStreams.toByteArray(in);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
index 396fef4..57dd808 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
@@ -18,11 +18,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.graphics.Rect;
@@ -32,24 +30,17 @@
import android.view.SurfaceSession;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.stubbing.Answer;
/**
* Build/Install/Run:
* atest FrameworksServicesTests:com.android.server.wm.DimmerTests;
*/
@Presubmit
-@Ignore("b/72450130")
@RunWith(AndroidJUnit4.class)
public class DimmerTests extends WindowTestsBase {
- public DimmerTests() {
- super(spy(new SurfaceAnimationRunner()));
- }
-
private class TestWindowContainer extends WindowContainer<TestWindowContainer> {
final SurfaceControl mControl = mock(SurfaceControl.class);
final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class);
@@ -71,14 +62,11 @@
private class MockSurfaceBuildingContainer extends WindowContainer<TestWindowContainer> {
final SurfaceSession mSession = new SurfaceSession();
+ final SurfaceControl mHostControl = mock(SurfaceControl.class);
final SurfaceControl.Transaction mHostTransaction = mock(SurfaceControl.Transaction.class);
MockSurfaceBuildingContainer() {
super(sWm);
- mSurfaceControl = sWm.makeSurfaceBuilder(mSession)
- .setName("test surface")
- .setSize(1, 1)
- .build();
}
class MockSurfaceBuilder extends SurfaceControl.Builder {
@@ -88,34 +76,29 @@
@Override
public SurfaceControl build() {
- return spy(sWm.makeSurfaceBuilder(mSession)
- .setName("test surface")
- .setSize(1, 1)
- .build());
+ return mock(SurfaceControl.class);
}
}
@Override
- SurfaceControl.Builder makeSurface() {
- return sWm.makeSurfaceBuilder(mSession)
- .setName("test surface")
- .setSize(1, 1);
- }
-
- @Override
SurfaceControl.Builder makeChildSurface(WindowContainer child) {
return new MockSurfaceBuilder(mSession);
}
@Override
+ public SurfaceControl getSurfaceControl() {
+ return mHostControl;
+ }
+
+ @Override
public SurfaceControl.Transaction getPendingTransaction() {
return mHostTransaction;
}
}
- MockSurfaceBuildingContainer mHost;
- Dimmer mDimmer;
- SurfaceControl.Transaction mTransaction;
+ private MockSurfaceBuildingContainer mHost;
+ private Dimmer mDimmer;
+ private SurfaceControl.Transaction mTransaction;
@Before
public void setUp() throws Exception {
@@ -123,13 +106,9 @@
mHost = new MockSurfaceBuildingContainer();
mTransaction = mock(SurfaceControl.Transaction.class);
- mDimmer = new Dimmer(mHost);
-
- doAnswer((Answer<Void>) invocation -> {
- Runnable runnable = invocation.getArgument(3);
- runnable.run();
- return null;
- }).when(sWm.mSurfaceAnimationRunner).startAnimation(any(), any(), any(), any());
+ mDimmer = new Dimmer(mHost,
+ (surfaceAnimator, t, anim, hidden) -> surfaceAnimator.mAnimationFinishedCallback
+ .run());
}
@Test
@@ -137,7 +116,7 @@
final float alpha = 0.8f;
mDimmer.dimAbove(mTransaction, alpha);
- SurfaceControl dimLayer = getDimLayer(null);
+ SurfaceControl dimLayer = getDimLayer();
assertNotNull("Dimmer should have created a surface", dimLayer);
@@ -149,12 +128,12 @@
public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() throws Exception {
float alpha = 0.8f;
mDimmer.dimAbove(mTransaction, alpha);
- final SurfaceControl firstSurface = getDimLayer(null);
+ final SurfaceControl firstSurface = getDimLayer();
alpha = 0.9f;
mDimmer.dimAbove(mTransaction, alpha);
- assertEquals(firstSurface, getDimLayer(null));
+ assertEquals(firstSurface, getDimLayer());
verify(mTransaction).setAlpha(firstSurface, 0.9f);
}
@@ -167,18 +146,18 @@
Rect bounds = new Rect(0, 0, width, height);
mDimmer.updateDims(mTransaction, bounds);
- verify(mTransaction).setSize(getDimLayer(null), width, height);
- verify(mTransaction).show(getDimLayer(null));
+ verify(mTransaction).setSize(getDimLayer(), width, height);
+ verify(mTransaction).show(getDimLayer());
}
@Test
public void testDimAboveNoChildNotReset() throws Exception {
mDimmer.dimAbove(mTransaction, 0.8f);
- SurfaceControl dimLayer = getDimLayer(null);
+ SurfaceControl dimLayer = getDimLayer();
mDimmer.resetDimStates();
mDimmer.updateDims(mTransaction, new Rect());
- verify(mTransaction).show(getDimLayer(null));
+ verify(mTransaction).show(getDimLayer());
verify(dimLayer, never()).destroy();
}
@@ -189,12 +168,12 @@
final float alpha = 0.8f;
mDimmer.dimAbove(mTransaction, child, alpha);
- SurfaceControl mDimLayer = getDimLayer(child);
+ SurfaceControl dimLayer = getDimLayer();
- assertNotNull("Dimmer should have created a surface", mDimLayer);
+ assertNotNull("Dimmer should have created a surface", dimLayer);
- verify(mTransaction).setAlpha(mDimLayer, alpha);
- verify(mTransaction).setRelativeLayer(mDimLayer, child.mControl, 1);
+ verify(mTransaction).setAlpha(dimLayer, alpha);
+ verify(mTransaction).setRelativeLayer(dimLayer, child.mControl, 1);
}
@Test
@@ -204,12 +183,12 @@
final float alpha = 0.8f;
mDimmer.dimBelow(mTransaction, child, alpha);
- SurfaceControl mDimLayer = getDimLayer(child);
+ SurfaceControl dimLayer = getDimLayer();
- assertNotNull("Dimmer should have created a surface", mDimLayer);
+ assertNotNull("Dimmer should have created a surface", dimLayer);
- verify(mTransaction).setAlpha(mDimLayer, alpha);
- verify(mTransaction).setRelativeLayer(mDimLayer, child.mControl, -1);
+ verify(mTransaction).setAlpha(dimLayer, alpha);
+ verify(mTransaction).setRelativeLayer(dimLayer, child.mControl, -1);
}
@Test
@@ -219,7 +198,7 @@
final float alpha = 0.8f;
mDimmer.dimAbove(mTransaction, child, alpha);
- SurfaceControl dimLayer = getDimLayer(child);
+ SurfaceControl dimLayer = getDimLayer();
mDimmer.resetDimStates();
mDimmer.updateDims(mTransaction, new Rect());
@@ -233,7 +212,7 @@
final float alpha = 0.8f;
mDimmer.dimAbove(mTransaction, child, alpha);
- SurfaceControl dimLayer = getDimLayer(child);
+ SurfaceControl dimLayer = getDimLayer();
mDimmer.resetDimStates();
mDimmer.dimAbove(mTransaction, child, alpha);
@@ -242,7 +221,29 @@
verify(dimLayer, never()).destroy();
}
- private SurfaceControl getDimLayer(WindowContainer windowContainer) {
- return mDimmer.mDimLayerUsers.get(windowContainer).mDimLayer;
+ @Test
+ public void testDimUpdateWhileDimming() throws Exception {
+ Rect bounds = new Rect();
+ TestWindowContainer child = new TestWindowContainer();
+ mHost.addChild(child, 0);
+
+ final float alpha = 0.8f;
+ mDimmer.dimAbove(mTransaction, child, alpha);
+
+ SurfaceControl dimLayer = getDimLayer();
+ bounds.set(0, 0, 10, 10);
+ mDimmer.updateDims(mTransaction, bounds);
+ verify(mTransaction, times(1)).show(dimLayer);
+ verify(mTransaction).setSize(dimLayer, bounds.width(), bounds.height());
+ verify(mTransaction).setPosition(dimLayer, 0, 0);
+
+ bounds.set(10, 10, 30, 30);
+ mDimmer.updateDims(mTransaction, bounds);
+ verify(mTransaction).setSize(dimLayer, bounds.width(), bounds.height());
+ verify(mTransaction).setPosition(dimLayer, 10, 10);
+ }
+
+ private SurfaceControl getDimLayer() {
+ return mDimmer.mDimState.mDimLayer;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 81fd889..000cf38 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -69,11 +69,6 @@
private Runnable mRunnableWhenAddingSplashScreen;
static synchronized WindowManagerService getWindowManagerService(Context context) {
- return getWindowManagerService(context, new SurfaceAnimationRunner());
- }
-
- static synchronized WindowManagerService getWindowManagerService(Context context,
- SurfaceAnimationRunner surfaceAnimationRunner) {
if (sWm == null) {
// We only want to do this once for the test process as we don't want WM to try to
// register a bunch of local services again.
@@ -111,7 +106,7 @@
}
sWm = WindowManagerService.main(context, ims, true, false,
- false, new TestWindowManagerPolicy(), surfaceAnimationRunner);
+ false, new TestWindowManagerPolicy());
}
return sWm;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 7918901..69b1378 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -84,16 +84,6 @@
WindowState mChildAppWindowBelow;
HashSet<WindowState> mCommonWindows;
- private final SurfaceAnimationRunner mSurfaceAnimationRunner;
-
- public WindowTestsBase() {
- this(new SurfaceAnimationRunner());
- }
-
- public WindowTestsBase(SurfaceAnimationRunner surfaceAnimationRunner) {
- mSurfaceAnimationRunner = surfaceAnimationRunner;
- }
-
@Before
public void setUp() throws Exception {
if (!sOneTimeSetupDone) {
@@ -108,7 +98,7 @@
final Context context = InstrumentationRegistry.getTargetContext();
AttributeCache.init(context);
- sWm = TestWindowManagerPolicy.getWindowManagerService(context, mSurfaceAnimationRunner);
+ sWm = TestWindowManagerPolicy.getWindowManagerService(context);
beforeCreateDisplay();
context.getDisplay().getDisplayInfo(mDisplayInfo);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index cd32425..d6f61cd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -49,8 +50,10 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
+import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
@@ -64,9 +67,11 @@
import android.content.pm.ParceledListSlice;
import android.graphics.Color;
import android.media.AudioManager;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings.Secure;
@@ -149,6 +154,10 @@
@Mock private ICompanionDeviceManager mCompanionMgr;
@Mock SnoozeHelper mSnoozeHelper;
@Mock GroupHelper mGroupHelper;
+ @Mock
+ IBinder mPermOwner;
+ @Mock
+ IActivityManager mAm;
// Use a Testable subclass so we can simulate calls from the system without failing.
private static class TestableNotificationManagerService extends NotificationManagerService {
@@ -208,6 +217,7 @@
when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class));
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
+ when(mAm.newUriPermissionOwner(anyString())).thenReturn(mPermOwner);
// write to a test file; the system file isn't readable from tests
mFile = new File(mContext.getCacheDir(), "test.xml");
@@ -238,7 +248,7 @@
mPackageManager, mPackageManagerClient, mockLightsManager,
mListeners, mAssistants, mConditionProviders,
mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
- mGroupHelper);
+ mGroupHelper, mAm);
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;
@@ -592,6 +602,8 @@
mBinderService.getActiveNotifications(PKG);
assertEquals(0, notifs.length);
assertEquals(0, mService.getNotificationRecordCount());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(
+ any(), any(), anyInt(), anyInt());
}
@Test
@@ -610,6 +622,7 @@
ArgumentCaptor<NotificationStats> captor = ArgumentCaptor.forClass(NotificationStats.class);
verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), captor.capture());
assertEquals(NotificationStats.DISMISSAL_OTHER, captor.getValue().getDismissalSurface());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -624,6 +637,7 @@
mBinderService.getActiveNotifications(sbn.getPackageName());
assertEquals(0, notifs.length);
assertEquals(0, mService.getNotificationRecordCount());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -637,6 +651,7 @@
mBinderService.getActiveNotifications(sbn.getPackageName());
assertEquals(0, notifs.length);
assertEquals(0, mService.getNotificationRecordCount());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -658,6 +673,7 @@
ArgumentCaptor<NotificationStats> captor = ArgumentCaptor.forClass(NotificationStats.class);
verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), captor.capture());
assertEquals(NotificationStats.DISMISSAL_OTHER, captor.getValue().getDismissalSurface());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -676,6 +692,7 @@
mBinderService.cancelAllNotifications(PKG, parent.sbn.getUserId());
waitForIdle();
assertEquals(0, mService.getNotificationRecordCount());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -689,6 +706,7 @@
waitForIdle();
assertEquals(0, mService.getNotificationRecordCount());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -2447,4 +2465,64 @@
mBinderService.getBackupPayload(1);
assertEquals(1, mService.countSystemChecks - systemChecks);
}
+
+ @Test
+ public void revokeUriPermissions_update() throws Exception {
+ NotificationChannel c = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+ c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
+ Message message1 = new Message("", 0, "");
+ message1.setData("", Uri.fromParts("old", "", "old stuff"));
+ Message message2 = new Message("", 1, "");
+ message2.setData("", Uri.fromParts("new", "", "new stuff"));
+
+ Notification.Builder nb = new Notification.Builder(mContext, c.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(message1)
+ .addMessage(message2));
+ StatusBarNotification oldSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord oldRecord =
+ new NotificationRecord(mContext, oldSbn, c);
+
+ Notification.Builder nb1 = new Notification.Builder(mContext, c.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.MessagingStyle("").addMessage(message2));
+ StatusBarNotification newSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
+ nb1.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord newRecord =
+ new NotificationRecord(mContext, newSbn, c);
+
+ mService.revokeUriPermissions(newRecord, oldRecord);
+
+ verify(mAm, times(1)).revokeUriPermissionFromOwner(any(), eq(message1.getDataUri()),
+ anyInt(), anyInt());
+ }
+
+ @Test
+ public void revokeUriPermissions_cancel() throws Exception {
+ NotificationChannel c = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+ c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
+ Message message1 = new Message("", 0, "");
+ message1.setData("", Uri.fromParts("old", "", "old stuff"));
+
+ Notification.Builder nb = new Notification.Builder(mContext, c.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(message1));
+ StatusBarNotification oldSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord oldRecord =
+ new NotificationRecord(mContext, oldSbn, c);
+
+ mService.revokeUriPermissions(null, oldRecord);
+
+ verify(mAm, times(1)).revokeUriPermissionFromOwner(any(), eq(message1.getDataUri()),
+ anyInt(), anyInt());
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 36a2a95..3b0fd1f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -654,7 +654,7 @@
public boolean isAppInactive(String packageName, int userId) {
try {
userId = ActivityManager.getService().handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, false, true, "isAppInactive", null);
+ Binder.getCallingUid(), userId, false, false, "isAppInactive", null);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 63f970a..e0b0bbf 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -787,6 +787,10 @@
builder.append(isLong ? " PROPERTY_HAS_CDMA_VOICE_PRIVACY" : " priv");
}
+ if (can(properties, PROPERTY_IS_RTT)) {
+ builder.append(isLong ? " PROPERTY_IS_RTT" : " rtt");
+ }
+
builder.append("]");
return builder.toString();
}
@@ -2646,6 +2650,7 @@
* side of the coll.
*/
public final void sendRttSessionRemotelyTerminated() {
+ unsetRttProperty();
mListeners.forEach((l) -> l.onRttSessionRemotelyTerminated(Connection.this));
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5c290da..03a8f33 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6587,11 +6587,48 @@
* @hide
*/
public void setBasebandVersionForPhone(int phoneId, String version) {
+ setTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_BASEBAND_VERSION, version);
+ }
+
+ /**
+ * Get baseband version for the default phone.
+ *
+ * @return baseband version.
+ * @hide
+ */
+ public String getBasebandVersion() {
+ int phoneId = getPhoneId();
+ return getBasebandVersionForPhone(phoneId);
+ }
+
+ /**
+ * Get baseband version for the default phone using the legacy approach.
+ * This change was added in P, to ensure backward compatiblity.
+ *
+ * @return baseband version.
+ * @hide
+ */
+ private String getBasebandVersionLegacy(int phoneId) {
if (SubscriptionManager.isValidPhoneId(phoneId)) {
String prop = TelephonyProperties.PROPERTY_BASEBAND_VERSION +
((phoneId == 0) ? "" : Integer.toString(phoneId));
- SystemProperties.set(prop, version);
+ return SystemProperties.get(prop);
}
+ return null;
+ }
+
+ /**
+ * Get baseband version by phone id.
+ *
+ * @return baseband version.
+ * @hide
+ */
+ public String getBasebandVersionForPhone(int phoneId) {
+ String version = getBasebandVersionLegacy(phoneId);
+ if (version != null && !version.isEmpty()) {
+ setBasebandVersionForPhone(phoneId, version);
+ }
+ return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_BASEBAND_VERSION, "");
}
/**
diff --git a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
index 7ea9ba3..1f06121 100644
--- a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
+++ b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
@@ -17,16 +17,19 @@
package com.android.frameworks.perftests.amteststestapp;
import android.app.Activity;
-import android.os.Bundle;
+import android.os.Looper;
+import android.os.MessageQueue;
import com.android.frameworks.perftests.am.util.Constants;
import com.android.frameworks.perftests.am.util.Utils;
public class TestActivity extends Activity {
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Utils.sendTime(getIntent(), Constants.TYPE_ACTIVITY_CREATED);
+ protected void onResume() {
+ super.onResume();
+ Looper.myQueue().addIdleHandler(() -> {
+ Utils.sendTime(getIntent(), Constants.TYPE_TARGET_PACKAGE_START);
+ return false;
+ });
}
}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
index c867141..26a8e7b 100644
--- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
@@ -62,7 +62,7 @@
sleep();
}
// make sure Application has run
- timeReceiver.getReceivedTimeNs(Constants.TYPE_ACTIVITY_CREATED);
+ timeReceiver.getReceivedTimeNs(Constants.TYPE_TARGET_PACKAGE_START);
Utils.drainBroadcastQueue();
}
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
index 6528028..f35c2fd 100644
--- a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
@@ -17,7 +17,7 @@
package com.android.frameworks.perftests.am.util;
public class Constants {
- public static final String TYPE_ACTIVITY_CREATED = "activity_create";
+ public static final String TYPE_TARGET_PACKAGE_START = "target_package_start";
public static final String TYPE_BROADCAST_RECEIVE = "broadcast_receive";
public static final String ACTION_BROADCAST_MANIFEST_RECEIVE =
diff --git a/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java b/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java
index d46fff4..a7d5ae8 100644
--- a/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java
+++ b/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java
@@ -99,6 +99,21 @@
}
@Test
+ public void tonal_rangeTest() {
+ Tonal.ConfigParser config = new Tonal.ConfigParser(InstrumentationRegistry.getContext());
+ for (Tonal.TonalPalette palette : config.getTonalPalettes()) {
+ assertTrue("minHue should be >= to 0.", palette.minHue >= 0);
+ assertTrue("maxHue should be <= to 360.", palette.maxHue <= 360);
+
+ assertTrue("S should be >= to 0.", palette.s[0] >= 0);
+ assertTrue("S should be <= to 1.", palette.s[1] <= 1);
+
+ assertTrue("L should be >= to 0.", palette.l[0] >= 0);
+ assertTrue("L should be <= to 1.", palette.l[1] <= 1);
+ }
+ }
+
+ @Test
public void tonal_blacklistTest() {
// Make sure that palette generation will fail.
final Tonal tonal = new Tonal(InstrumentationRegistry.getContext());
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 6e643a3..e7abede 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -318,6 +318,7 @@
// This test has an inherent race condition in it, and cannot be enabled for continuous testing
// or presubmit tests. It is kept for manual runs and documentation purposes.
+ @Ignore
public void verifyThatNotWaitingForIdleCausesRaceConditions() {
// Bring up a network that we can use to send messages to ConnectivityService.
ConditionVariable cv = waitForConnectivityBroadcasts(1);
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 71ca068..f3a78bd 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -611,6 +611,7 @@
/**
* Verify the watchLocalOnlyHotspot call goes to WifiServiceImpl.
*/
+ @Test
public void testWatchLocalOnlyHotspot() throws Exception {
TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver();
diff --git a/wifi/tests/src/android/net/wifi/p2p/WifiP2pManagerTest.java b/wifi/tests/src/android/net/wifi/p2p/WifiP2pManagerTest.java
index 1e8382f..e8e4dc2 100644
--- a/wifi/tests/src/android/net/wifi/p2p/WifiP2pManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/p2p/WifiP2pManagerTest.java
@@ -59,10 +59,10 @@
*/
@Test
public void testChannelFinalize() throws Exception {
- WifiP2pManager.Channel channel = new WifiP2pManager.Channel(mContextMock,
- mTestLooper.getLooper(), null, null, mDut);
-
- leakageDetectorRule.assertUnreleasedResourceCount(channel, 1);
+ try (WifiP2pManager.Channel channel = new WifiP2pManager.Channel(mContextMock,
+ mTestLooper.getLooper(), null, null, mDut)) {
+ leakageDetectorRule.assertUnreleasedResourceCount(channel, 1);
+ }
}
/**