diff options
246 files changed, 7277 insertions, 3330 deletions
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index bab2a859698c..231aaf2ca074 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -232,7 +232,7 @@ public class StaticLayoutPerfTest { while (state.keepRunning()) { state.pauseTiming(); final MeasuredText text = new MeasuredText.Builder( - mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .build(); diff --git a/api/current.txt b/api/current.txt index bbe3c494c955..dee0b0464f26 100644 --- a/api/current.txt +++ b/api/current.txt @@ -13048,6 +13048,8 @@ package android.graphics { method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(int[], int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int[], int, int, android.graphics.Bitmap.Config); + method public static android.graphics.Bitmap createBitmap(android.graphics.Picture); + method public static android.graphics.Bitmap createBitmap(android.graphics.Picture, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createScaledBitmap(android.graphics.Bitmap, int, int, boolean); method public int describeContents(); method public void eraseColor(int); @@ -14105,6 +14107,7 @@ package android.graphics { method public void endRecording(); method public int getHeight(); method public int getWidth(); + method public boolean requiresHardwareAcceleration(); method public deprecated void writeToStream(java.io.OutputStream); } @@ -16486,8 +16489,37 @@ package android.hardware.display { package android.hardware.fingerprint { public class FingerprintDialog { - method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback); - method public void authenticate(android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback); + method public void authenticate(android.hardware.fingerprint.FingerprintDialog.CryptoObject, android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintDialog.AuthenticationCallback); + method public void authenticate(android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintDialog.AuthenticationCallback); + field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 + field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 + field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 + field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1 + field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5 + field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4 + field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 + field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc + field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 + field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 + field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9 + field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb + field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 + field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 + field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 + field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa + field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8 + } + + public static abstract class FingerprintDialog.AuthenticationCallback { + ctor public FingerprintDialog.AuthenticationCallback(); + method public void onAuthenticationError(int, java.lang.CharSequence); + method public void onAuthenticationFailed(); + method public void onAuthenticationHelp(int, java.lang.CharSequence); + method public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintDialog.AuthenticationResult); + } + + public static class FingerprintDialog.AuthenticationResult { + method public android.hardware.fingerprint.FingerprintDialog.CryptoObject getCryptoObject(); } public static class FingerprintDialog.Builder { @@ -16499,10 +16531,19 @@ package android.hardware.fingerprint { method public android.hardware.fingerprint.FingerprintDialog.Builder setTitle(java.lang.CharSequence); } - public class FingerprintManager { - method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, int, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, android.os.Handler); - method public boolean hasEnrolledFingerprints(); - method public boolean isHardwareDetected(); + public static final class FingerprintDialog.CryptoObject { + ctor public FingerprintDialog.CryptoObject(java.security.Signature); + ctor public FingerprintDialog.CryptoObject(javax.crypto.Cipher); + ctor public FingerprintDialog.CryptoObject(javax.crypto.Mac); + method public javax.crypto.Cipher getCipher(); + method public javax.crypto.Mac getMac(); + method public java.security.Signature getSignature(); + } + + public deprecated class FingerprintManager { + method public deprecated void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, int, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, android.os.Handler); + method public deprecated boolean hasEnrolledFingerprints(); + method public deprecated boolean isHardwareDetected(); field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 @@ -16510,9 +16551,11 @@ package android.hardware.fingerprint { field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5 field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4 field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 + field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9 + field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 @@ -21946,6 +21989,7 @@ package android.media { field public static final int ENCODING_DTS = 7; // 0x7 field public static final int ENCODING_DTS_HD = 8; // 0x8 field public static final int ENCODING_E_AC3 = 6; // 0x6 + field public static final int ENCODING_E_AC3_JOC = 18; // 0x12 field public static final int ENCODING_IEC61937 = 13; // 0xd field public static final int ENCODING_INVALID = 0; // 0x0 field public static final int ENCODING_MP3 = 9; // 0x9 @@ -22150,6 +22194,20 @@ package android.media { field public static final android.os.Parcelable.Creator<android.media.AudioPlaybackConfiguration> CREATOR; } + public final class AudioPresentation { + method public java.util.Map<java.util.Locale, java.lang.String> getLabels(); + method public java.util.Locale getLocale(); + method public int getMasteringIndication(); + method public boolean hasAudioDescription(); + method public boolean hasDialogueEnhancement(); + method public boolean hasSpokenSubtitles(); + field public static final int MASTERED_FOR_3D = 3; // 0x3 + field public static final int MASTERED_FOR_HEADPHONE = 4; // 0x4 + field public static final int MASTERED_FOR_STEREO = 1; // 0x1 + field public static final int MASTERED_FOR_SURROUND = 2; // 0x2 + field public static final int MASTERING_NOT_INDICATED = 0; // 0x0 + } + public class AudioRecord implements android.media.AudioRouting { ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException; method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); @@ -22316,6 +22374,7 @@ package android.media { method public int setPlaybackRate(int); method public int setPositionNotificationPeriod(int); method public boolean setPreferredDevice(android.media.AudioDeviceInfo); + method public int setPresentation(android.media.AudioPresentation); method protected deprecated void setState(int); method public deprecated int setStereoVolume(float, float); method public void setStreamEventCallback(java.util.concurrent.Executor, android.media.AudioTrack.StreamEventCallback); @@ -23472,6 +23531,7 @@ package android.media { ctor public MediaExtractor(); method public boolean advance(); method protected void finalize(); + method public java.util.List<android.media.AudioPresentation> getAudioPresentations(int); method public long getCachedDuration(); method public android.media.MediaExtractor.CasInfo getCasInfo(int); method public android.media.DrmInitData getDrmInitData(); diff --git a/api/system-current.txt b/api/system-current.txt index 62cc2a393b20..ad71e7cbb278 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -19,6 +19,7 @@ package android { field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS"; field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; field public static final deprecated java.lang.String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE"; + field public static final java.lang.String BIND_DATA_SERVICE = "android.permission.BIND_DATA_SERVICE"; field public static final java.lang.String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH"; field public static final java.lang.String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE"; field public static final java.lang.String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET"; @@ -1140,6 +1141,15 @@ package android.hardware.camera2.params { package android.hardware.display { + public final class AmbientBrightnessDayStats implements android.os.Parcelable { + method public int describeContents(); + method public float[] getBucketBoundaries(); + method public java.time.LocalDate getLocalDate(); + method public float[] getStats(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.AmbientBrightnessDayStats> CREATOR; + } + public final class BrightnessChangeEvent implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); diff --git a/api/test-current.txt b/api/test-current.txt index 9af80e318c8b..92bf24db7f5a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -298,6 +298,15 @@ package android.hardware.camera2 { package android.hardware.display { + public final class AmbientBrightnessDayStats implements android.os.Parcelable { + method public int describeContents(); + method public float[] getBucketBoundaries(); + method public java.time.LocalDate getLocalDate(); + method public float[] getStats(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.AmbientBrightnessDayStats> CREATOR; + } + public final class BrightnessChangeEvent implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java index f75678b7fa1e..6e0bd3a81d84 100644 --- a/cmds/content/src/com/android/commands/content/Content.java +++ b/cmds/content/src/com/android/commands/content/Content.java @@ -26,6 +26,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.FileUtils; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; @@ -34,6 +35,7 @@ import android.text.TextUtils; import libcore.io.Streams; +import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -583,7 +585,7 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null)) { - Streams.copy(new FileInputStream(fd.getFileDescriptor()), System.out); + FileUtils.copy(fd.getFileDescriptor(), FileDescriptor.out); } } } @@ -596,7 +598,7 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "w", null, null)) { - Streams.copy(System.in, new FileOutputStream(fd.getFileDescriptor())); + FileUtils.copy(FileDescriptor.in, fd.getFileDescriptor()); } } } diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index eabbb96a392e..b0019ac90708 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -38,8 +38,11 @@ statsd_common_src := \ src/external/StatsPuller.cpp \ src/external/StatsCompanionServicePuller.cpp \ src/external/SubsystemSleepStatePuller.cpp \ + src/external/ResourceHealthManagerPuller.cpp \ src/external/CpuTimePerUidPuller.cpp \ src/external/CpuTimePerUidFreqPuller.cpp \ + src/external/KernelUidCpuActiveTimeReader.cpp \ + src/external/KernelUidCpuClusterTimeReader.cpp \ src/external/StatsPullerManagerImpl.cpp \ src/logd/LogEvent.cpp \ src/logd/LogListener.cpp \ @@ -75,7 +78,8 @@ statsd_common_aidl_includes := \ $(LOCAL_PATH)/../../core/java statsd_common_static_libraries := \ - libplatformprotos + libhealthhalutils \ + libplatformprotos \ statsd_common_shared_libraries := \ libbase \ @@ -93,6 +97,7 @@ statsd_common_shared_libraries := \ libhidlbase \ libhidltransport \ libhwbinder \ + android.hardware.health@2.0 \ android.hardware.power@1.0 \ android.hardware.power@1.1 \ libmemunreachable @@ -165,6 +170,7 @@ LOCAL_CFLAGS += \ LOCAL_SRC_FILES := \ $(statsd_common_src) \ + tests/dimension_test.cpp \ tests/AnomalyMonitor_test.cpp \ tests/anomaly/AnomalyTracker_test.cpp \ tests/ConfigManager_test.cpp \ @@ -190,7 +196,8 @@ LOCAL_SRC_FILES := \ tests/e2e/WakelockDuration_e2e_test.cpp \ tests/e2e/MetricConditionLink_e2e_test.cpp \ tests/e2e/Attribution_e2e_test.cpp \ - tests/e2e/GaugeMetric_e2e_test.cpp + tests/e2e/GaugeMetric_e2e_test.cpp \ + tests/e2e/DimensionInCondition_e2e_test.cpp LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 857a6ddad0be..f0eaeff88d34 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -67,12 +67,16 @@ android::hash_t hashDimensionsValue(const DimensionsValue& value) { return hashDimensionsValue(0, value); } +android::hash_t hashMetricDimensionKey(int64_t seed, const MetricDimensionKey& dimensionKey) { + android::hash_t hash = seed; + hash = android::JenkinsHashMix(hash, std::hash<MetricDimensionKey>{}(dimensionKey)); + return JenkinsHashWhiten(hash); +} + using std::string; string HashableDimensionKey::toString() const { - string flattened; - DimensionsValueToString(getDimensionsValue(), &flattened); - return flattened; + return DimensionsValueToString(getDimensionsValue()); } bool EqualsTo(const DimensionsValue& s1, const DimensionsValue& s2) { @@ -162,6 +166,22 @@ bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const { return LessThan(getDimensionsValue(), that.getDimensionsValue()); }; +string MetricDimensionKey::toString() const { + string flattened = mDimensionKeyInWhat.toString(); + flattened += mDimensionKeyInCondition.toString(); + return flattened; +} + +bool MetricDimensionKey::operator==(const MetricDimensionKey& that) const { + return mDimensionKeyInWhat == that.getDimensionKeyInWhat() && + mDimensionKeyInCondition == that.getDimensionKeyInCondition(); +}; + +bool MetricDimensionKey::operator<(const MetricDimensionKey& that) const { + return toString().compare(that.toString()) < 0; +}; + + } // namespace statsd } // namespace os } // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h index 85c317f8cf1f..a31d7a6d85c6 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -41,6 +41,10 @@ public: return mDimensionsValue; } + inline DimensionsValue* getMutableDimensionsValue() { + return &mDimensionsValue; + } + bool operator==(const HashableDimensionKey& that) const; bool operator<(const HashableDimensionKey& that) const; @@ -53,8 +57,52 @@ private: DimensionsValue mDimensionsValue; }; +class MetricDimensionKey { + public: + explicit MetricDimensionKey(const HashableDimensionKey& dimensionKeyInWhat, + const HashableDimensionKey& dimensionKeyInCondition) + : mDimensionKeyInWhat(dimensionKeyInWhat), + mDimensionKeyInCondition(dimensionKeyInCondition) {}; + + MetricDimensionKey(){}; + + MetricDimensionKey(const MetricDimensionKey& that) + : mDimensionKeyInWhat(that.getDimensionKeyInWhat()), + mDimensionKeyInCondition(that.getDimensionKeyInCondition()) {}; + + MetricDimensionKey& operator=(const MetricDimensionKey& from) = default; + + std::string toString() const; + + inline const HashableDimensionKey& getDimensionKeyInWhat() const { + return mDimensionKeyInWhat; + } + + inline const HashableDimensionKey& getDimensionKeyInCondition() const { + return mDimensionKeyInCondition; + } + + bool hasDimensionKeyInCondition() const { + return mDimensionKeyInCondition.getDimensionsValue().has_field(); + } + + bool operator==(const MetricDimensionKey& that) const; + + bool operator<(const MetricDimensionKey& that) const; + + inline const char* c_str() const { + return toString().c_str(); + } + private: + HashableDimensionKey mDimensionKeyInWhat; + HashableDimensionKey mDimensionKeyInCondition; +}; + +bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2); + android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value); android::hash_t hashDimensionsValue(const DimensionsValue& value); +android::hash_t hashMetricDimensionKey(int64_t see, const MetricDimensionKey& dimensionKey); } // namespace statsd } // namespace os @@ -63,6 +111,7 @@ android::hash_t hashDimensionsValue(const DimensionsValue& value); namespace std { using android::os::statsd::HashableDimensionKey; +using android::os::statsd::MetricDimensionKey; template <> struct hash<HashableDimensionKey> { @@ -71,4 +120,14 @@ struct hash<HashableDimensionKey> { } }; -} // namespace std +template <> +struct hash<MetricDimensionKey> { + std::size_t operator()(const MetricDimensionKey& key) const { + android::hash_t hash = hashDimensionsValue( + key.getDimensionKeyInWhat().getDimensionsValue()); + hash = android::JenkinsHashMix(hash, + hashDimensionsValue(key.getDimensionKeyInCondition().getDimensionsValue())); + return android::JenkinsHashWhiten(hash); + } +}; +} // namespace std
\ No newline at end of file diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index c19ff63e2858..7642aafaab43 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -104,7 +104,10 @@ private: FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); - + FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink); }; } // namespace statsd diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index f545bb0738e9..4e4145439e25 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -551,6 +551,12 @@ status_t StatsService::cmd_dump_memory_info(FILE* out) { return NO_ERROR; } +status_t StatsService::cmd_clear_puller_cache(FILE* out) { + mStatsPullerManager.ClearPullerCache(); + fprintf(out, "Puller cached data removed!\n"); + return NO_ERROR; +} + Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version, const vector<String16>& app) { VLOG("StatsService::informAllUidData was called"); diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index be20893994f1..fd3ed1dbed72 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -197,6 +197,11 @@ private: */ status_t cmd_dump_memory_info(FILE* out); + /* + * Clear all puller cached data + */ + status_t cmd_clear_puller_cache(FILE* out); + /** * Update a configuration. */ diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index ded6c4c660be..c84a5b4509b0 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -96,7 +96,7 @@ void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) { } } -void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue, +void AnomalyTracker::addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue, const int64_t& bucketNum) { flushPastBuckets(bucketNum); @@ -147,7 +147,7 @@ void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) { } } -int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key, +int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const { const auto& bucket = mPastBuckets[index(bucketNum)]; if (bucket == nullptr) { @@ -157,7 +157,7 @@ int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key, return itr == bucket->end() ? 0 : itr->second; } -int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const { +int64_t AnomalyTracker::getSumOverPastBuckets(const MetricDimensionKey& key) const { const auto& itr = mSumOverPastBuckets.find(key); if (itr != mSumOverPastBuckets.end()) { return itr->second; @@ -165,7 +165,7 @@ int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) c return 0; } -bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key, +bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const MetricDimensionKey& key, const int64_t& currentBucketValue) { if (currentBucketNum > mMostRecentBucketNum + 1) { // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this. @@ -175,7 +175,7 @@ bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const Hashab && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt(); } -void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key) { +void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key) { // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now. if (isInRefractoryPeriod(timestampNs, key)) { VLOG("Skipping anomaly declaration since within refractory period"); @@ -199,14 +199,14 @@ void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const HashableD StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id()); - // TODO: This should also take in the const HashableDimensionKey& key? + // TODO: This should also take in the const MetricDimensionKey& key? android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(), mConfigKey.GetId(), mAlert.id()); } void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum, - const HashableDimensionKey& key, + const MetricDimensionKey& key, const int64_t& currentBucketValue) { if (detectAnomaly(currBucketNum, key, currentBucketValue)) { declareAnomaly(timestampNs, key); @@ -214,7 +214,7 @@ void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs, } bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs, - const HashableDimensionKey& key) { + const MetricDimensionKey& key) { const auto& it = mRefractoryPeriodEndsSec.find(key); if (it != mRefractoryPeriodEndsSec.end()) { if ((timestampNs / NS_PER_SEC) <= it->second) { @@ -226,7 +226,7 @@ bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs, return false; } -void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) { +void AnomalyTracker::informSubscribers(const MetricDimensionKey& key) { VLOG("informSubscribers called."); if (mSubscriptions.empty()) { ALOGE("Attempt to call with no subscribers."); diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h index 472c02c7ee7f..f01a97f86cf6 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.h +++ b/cmds/statsd/src/anomaly/AnomalyTracker.h @@ -48,19 +48,19 @@ public: // Adds a bucket. // Bucket index starts from 0. void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum); - void addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue, + void addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue, const int64_t& bucketNum); // Returns true if detected anomaly for the existing buckets on one or more dimension keys. - bool detectAnomaly(const int64_t& currBucketNum, const HashableDimensionKey& key, + bool detectAnomaly(const int64_t& currBucketNum, const MetricDimensionKey& key, const int64_t& currentBucketValue); // Informs incidentd about the detected alert. - void declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key); + void declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key); // Detects the alert and informs the incidentd when applicable. void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum, - const HashableDimensionKey& key, + const MetricDimensionKey& key, const int64_t& currentBucketValue); // Init the AnomalyMonitor which is shared across anomaly trackers. @@ -69,10 +69,10 @@ public: } // Helper function to return the sum value of past buckets at given dimension. - int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const; + int64_t getSumOverPastBuckets(const MetricDimensionKey& key) const; // Helper function to return the value for a past bucket. - int64_t getPastBucketValue(const HashableDimensionKey& key, const int64_t& bucketNum) const; + int64_t getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const; // Returns the anomaly threshold. inline int64_t getAnomalyThreshold() const { @@ -81,7 +81,7 @@ public: // Returns the refractory period timestamp (in seconds) for the given key. // If there is no stored refractory period ending timestamp, returns 0. - uint32_t getRefractoryPeriodEndsSec(const HashableDimensionKey& key) const { + uint32_t getRefractoryPeriodEndsSec(const MetricDimensionKey& key) const { const auto& it = mRefractoryPeriodEndsSec.find(key); return it != mRefractoryPeriodEndsSec.end() ? it->second : 0; } @@ -124,7 +124,7 @@ protected: // declared for that dimension) ends, in seconds. Only anomalies that occur after this period // ends will be declared. // Entries may be, but are not guaranteed to be, removed after the period is finished. - unordered_map<HashableDimensionKey, uint32_t> mRefractoryPeriodEndsSec; + unordered_map<MetricDimensionKey, uint32_t> mRefractoryPeriodEndsSec; void flushPastBuckets(const int64_t& currBucketNum); @@ -135,7 +135,7 @@ protected: // and remove any items with value 0. void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket); - bool isInRefractoryPeriod(const uint64_t& timestampNs, const HashableDimensionKey& key); + bool isInRefractoryPeriod(const uint64_t& timestampNs, const MetricDimensionKey& key); // Calculates the corresponding bucket index within the circular array. size_t index(int64_t bucketNum) const; @@ -144,7 +144,7 @@ protected: virtual void resetStorage(); // Informs the subscribers that an anomaly has occurred. - void informSubscribers(const HashableDimensionKey& key); + void informSubscribers(const MetricDimensionKey& key); FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets); FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets); diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp index 7576a38db51d..bbee9fa5358c 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp @@ -37,8 +37,8 @@ void DurationAnomalyTracker::resetStorage() { if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!"); } -void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, - const uint64_t& timestampNs) { +void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const MetricDimensionKey& dimensionKey, + const uint64_t& timestampNs) { auto itr = mAlarms.find(dimensionKey); if (itr == mAlarms.end()) { return; @@ -51,7 +51,7 @@ void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensio } } -void DurationAnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey, +void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey, const uint64_t& timestampNs) { uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC); @@ -66,7 +66,7 @@ void DurationAnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey } } -void DurationAnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) { +void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey) { auto itr = mAlarms.find(dimensionKey); if (itr != mAlarms.end()) { mAlarms.erase(dimensionKey); @@ -77,7 +77,7 @@ void DurationAnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) } void DurationAnomalyTracker::stopAllAlarms() { - std::set<HashableDimensionKey> keys; + std::set<MetricDimensionKey> keys; for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) { keys.insert(itr->first); } @@ -95,7 +95,7 @@ void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs, // seldomly called. The alternative would be having AnomalyAlarms store information about the // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is // rarely ever called. - unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms; + unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> matchedAlarms; for (const auto& kv : mAlarms) { if (firedAlarms.count(kv.second) > 0) { matchedAlarms.insert({kv.first, kv.second}); diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h index 33e55ab850c9..052fdf576289 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h @@ -32,10 +32,10 @@ public: virtual ~DurationAnomalyTracker(); // Starts the alarm at the given timestamp. - void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime); + void startAlarm(const MetricDimensionKey& dimensionKey, const uint64_t& eventTime); // Stops the alarm. - void stopAlarm(const HashableDimensionKey& dimensionKey); + void stopAlarm(const MetricDimensionKey& dimensionKey); // Stop all the alarms owned by this tracker. void stopAllAlarms(); @@ -46,7 +46,7 @@ public: } // Declares the anomaly when the alarm expired given the current timestamp. - void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, + void declareAnomalyIfAlarmExpired(const MetricDimensionKey& dimensionKey, const uint64_t& timestampNs); // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker @@ -59,7 +59,7 @@ public: protected: // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they // are still active. - std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms; + std::unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> mAlarms; // Anomaly alarm monitor. sp<AnomalyMonitor> mAnomalyMonitor; diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 4e570a684a06..e64b631b1738 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -95,11 +95,12 @@ message Atom { AppStartMemoryStateCaptured app_start_memory_state_captured = 55; ShutdownSequenceReported shutdown_sequence_reported = 56; BootSequenceReported boot_sequence_reported = 57; + DaveyOccurred davey_occurred = 58; // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15. } // Pulled events will start at field 10000. - // Next: 10019 + // Next: 10021 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; @@ -120,6 +121,8 @@ message Atom { CpuActiveTime cpu_active_time = 10016; CpuClusterTime cpu_cluster_time = 10017; DiskSpace disk_space = 10018; + RemainingBatteryCapacity remaining_battery_capacity = 10019; + FullBatteryCapacity full_battery_capacity = 10020; } } @@ -721,6 +724,17 @@ message BootSequenceReported { } /** + * Logs the duration of a davey (jank of >=700ms) when it occurs + * + * Logged from: + * frameworks/base/libs/hwui/JankTracker.cpp + */ +message DaveyOccurred { + // Amount of time it took to render the frame. Should be >=700ms. + optional int64 jank_duration_ms = 1; +} + +/** * Logs phone signal strength changes. * * Logged from: @@ -757,8 +771,8 @@ message SettingChanged { // The tag used with the is_default for resetting sets of settings. This is generally null. optional string tag = 5; - // 1 indicates that this setting with tag should be resettable. - optional int32 is_default = 6; + // True if this setting with tag should be resettable. + optional bool is_default = 6; // The user ID associated. Defined in android/os/UserHandle.java optional int32 user = 7; @@ -1105,9 +1119,12 @@ message IsolatedUidChanged { optional int32 isolated_uid = 2; - // 1 denotes we're creating an isolated uid and 0 denotes removal. We expect an isolated uid to - // be removed before if it's used for another parent uid. - optional int32 is_create = 3; + // We expect an isolated uid to be removed before if it's used for another parent uid. + enum Event { + REMOVED = 0; + CREATED = 1; + } + optional Event event = 3; } /** @@ -1410,3 +1427,19 @@ message DiskSpace { // available bytes in download cache or temp directories optional uint64 temp_available_bytes = 3; } + +/** + * Pulls battery coulomb counter, which is the remaining battery charge in uAh. + * Logged from: frameworks/base/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp + */ +message RemainingBatteryCapacity { + optional int32 charge_uAh = 1; +} + +/** + * Pulls battery capacity, which is the battery capacity when full in uAh. + * Logged from: frameworks/base/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp + */ +message FullBatteryCapacity { + optional int32 capacity_uAh = 1; +} diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp index ea6586c25d80..4c20ccb61afe 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp +++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp @@ -78,6 +78,7 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf return false; } + bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers, conditionIdIndexMap, stack); @@ -88,8 +89,10 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf ALOGW("Child initialization success %lld ", (long long)child); } + if (allConditionTrackers[childIndex]->isSliced()) { + setSliced(true); + } mChildren.push_back(childIndex); - mTrackerIndex.insert(childTracker->getLogTrackerIndex().begin(), childTracker->getLogTrackerIndex().end()); } @@ -105,11 +108,15 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf void CombinationConditionTracker::isConditionMet( const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, - vector<ConditionState>& conditionCache) const { + const FieldMatcher& dimensionFields, + vector<ConditionState>& conditionCache, + std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const { + // So far, this is fine as there is at most one child having sliced output. for (const int childIndex : mChildren) { if (conditionCache[childIndex] == ConditionState::kNotEvaluated) { allConditions[childIndex]->isConditionMet(conditionParameters, allConditions, - conditionCache); + dimensionFields, conditionCache, + dimensionsKeySet); } } conditionCache[mIndex] = @@ -127,6 +134,7 @@ void CombinationConditionTracker::evaluateCondition( } for (const int childIndex : mChildren) { + // So far, this is fine as there is at most one child having sliced output. if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) { const sp<ConditionTracker>& child = mAllConditions[childIndex]; child->evaluateCondition(event, eventMatcherValues, mAllConditions, @@ -159,6 +167,24 @@ void CombinationConditionTracker::evaluateCondition( } } +ConditionState CombinationConditionTracker::getMetConditionDimension( + const std::vector<sp<ConditionTracker>>& allConditions, + const FieldMatcher& dimensionFields, + std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const { + vector<ConditionState> conditionCache(allConditions.size(), ConditionState::kNotEvaluated); + // So far, this is fine as there is at most one child having sliced output. + for (const int childIndex : mChildren) { + conditionCache[childIndex] = conditionCache[childIndex] | + allConditions[childIndex]->getMetConditionDimension( + allConditions, dimensionFields, dimensionsKeySet); + } + evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache); + if (conditionCache[mIndex] == ConditionState::kTrue && dimensionsKeySet.empty()) { + dimensionsKeySet.insert(DEFAULT_DIMENSION_KEY); + } + return conditionCache[mIndex]; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h index dfd3837f31f4..0b7f9492d628 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.h +++ b/cmds/statsd/src/condition/CombinationConditionTracker.h @@ -41,12 +41,20 @@ public: std::vector<ConditionState>& conditionCache, std::vector<bool>& changedCache) override; - void isConditionMet(const ConditionKey& conditionParameters, - const std::vector<sp<ConditionTracker>>& allConditions, - std::vector<ConditionState>& conditionCache) const override; + void isConditionMet( + const ConditionKey& conditionParameters, + const std::vector<sp<ConditionTracker>>& allConditions, + const FieldMatcher& dimensionFields, + std::vector<ConditionState>& conditionCache, + std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override; + ConditionState getMetConditionDimension( + const std::vector<sp<ConditionTracker>>& allConditions, + const FieldMatcher& dimensionFields, + std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override; private: LogicalOperation mLogicalOperation; + // Store index of the children Predicates. // We don't store string name of the Children, because we want to get rid of the hash map to // map the name to object. We don't want to store smart pointers to children, because it diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h index 773860f429b1..81abbdb36ee4 100644 --- a/cmds/statsd/src/condition/ConditionTracker.h +++ b/cmds/statsd/src/condition/ConditionTracker.h @@ -24,6 +24,7 @@ #include <log/logprint.h> #include <utils/RefBase.h> +#include <unordered_set> #include <unordered_map> namespace android { @@ -84,10 +85,19 @@ public: // [allConditions]: all condition trackers. This is needed because the condition evaluation is // done recursively // [conditionCache]: the cache holding the condition evaluation values. + // [dimensionsKeySet]: the dimensions where the sliced condition is true. For combination + // condition, it assumes that only one child predicate is sliced. virtual void isConditionMet( const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, - std::vector<ConditionState>& conditionCache) const = 0; + const FieldMatcher& dimensionFields, + std::vector<ConditionState>& conditionCache, + std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const = 0; + + virtual ConditionState getMetConditionDimension( + const std::vector<sp<ConditionTracker>>& allConditions, + const FieldMatcher& dimensionFields, + std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const = 0; // return the list of LogMatchingTracker index that this ConditionTracker uses. virtual const std::set<int>& getLogTrackerIndex() const { @@ -98,6 +108,10 @@ public: mSliced = mSliced | sliced; } + bool isSliced() const { + return mSliced; + } + protected: const int64_t mConditionId; diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp index d99c2ccd1fda..0427700fec91 100644 --- a/cmds/statsd/src/condition/ConditionWizard.cpp +++ b/cmds/statsd/src/condition/ConditionWizard.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ #include "ConditionWizard.h" +#include <unordered_set> namespace android { namespace os { @@ -23,14 +24,26 @@ using std::map; using std::string; using std::vector; -ConditionState ConditionWizard::query(const int index, - const ConditionKey& parameters) { +ConditionState ConditionWizard::query( + const int index, const ConditionKey& parameters, + const FieldMatcher& dimensionFields, + std::unordered_set<HashableDimensionKey> *dimensionKeySet) { + vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated); - mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache); + mAllConditions[index]->isConditionMet( + parameters, mAllConditions, dimensionFields, cache, *dimensionKeySet); return cache[index]; } +ConditionState ConditionWizard::getMetConditionDimension( + const int index, const FieldMatcher& dimensionFields, + std::unordered_set<HashableDimensionKey> *dimensionsKeySet) const { + + return mAllConditions[index]->getMetConditionDimension(mAllConditions, dimensionFields, + *dimensionsKeySet); +} + } // namespace statsd } // namespace os } // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h index 4ff5c07e210f..b38b59ff4cd0 100644 --- a/cmds/statsd/src/condition/ConditionWizard.h +++ b/cmds/statsd/src/condition/ConditionWizard.h @@ -41,7 +41,14 @@ public: // the conditionParameters contains the parameters for it's children SimpleConditionTrackers. virtual ConditionState query( const int conditionIndex, - const ConditionKey& conditionParameters); + const ConditionKey& conditionParameters, + const FieldMatcher& dimensionFields, + std::unordered_set<HashableDimensionKey> *dimensionKeySet); + + virtual ConditionState getMetConditionDimension( + const int index, + const FieldMatcher& dimensionFields, + std::unordered_set<HashableDimensionKey> *dimensionsKeySet) const; private: std::vector<sp<ConditionTracker>> mAllConditions; diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index 5cfc349ea46a..25265d5dabd7 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp +++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp @@ -104,6 +104,9 @@ bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig, vector<bool>& stack) { // SimpleConditionTracker does not have dependency on other conditions, thus we just return // if the initialization was successful. + if (mOutputDimensions.has_field() || mOutputDimensions.child_size() > 0) { + setSliced(true); + } return mInitialized; } @@ -234,11 +237,12 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou conditionChangedCache[mIndex] == true); } -void SimpleConditionTracker::evaluateCondition(const LogEvent& event, - const vector<MatchingState>& eventMatcherValues, - const vector<sp<ConditionTracker>>& mAllConditions, - vector<ConditionState>& conditionCache, - vector<bool>& conditionChangedCache) { +void SimpleConditionTracker::evaluateCondition( + const LogEvent& event, + const vector<MatchingState>& eventMatcherValues, + const vector<sp<ConditionTracker>>& mAllConditions, + vector<ConditionState>& conditionCache, + vector<bool>& conditionChangedCache) { if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { // it has been evaluated. VLOG("Yes, already evaluated, %lld %d", @@ -271,7 +275,7 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, if (mSliced) { // if the condition result is sliced. metrics won't directly get value from the // cache, so just set any value other than kNotEvaluated. - conditionCache[mIndex] = ConditionState::kUnknown; + conditionCache[mIndex] = mInitialValue; } else { const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); if (itr == mSlicedConditionState.end()) { @@ -310,10 +314,8 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, vector<ConditionState> dimensionalConditionCache(conditionCache.size(), ConditionState::kNotEvaluated); vector<bool> dimensionalConditionChangedCache(conditionChangedCache.size(), false); - handleConditionEvent(HashableDimensionKey(outputValue), matchedState == 1, dimensionalConditionCache, dimensionalConditionChangedCache); - OrConditionState(dimensionalConditionCache, &conditionCache); OrBooleanVector(dimensionalConditionChangedCache, &conditionChangedCache); } @@ -323,49 +325,112 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, void SimpleConditionTracker::isConditionMet( const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, - vector<ConditionState>& conditionCache) const { + const FieldMatcher& dimensionFields, + vector<ConditionState>& conditionCache, + std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const { + if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { + // it has been evaluated. + VLOG("Yes, already evaluated, %lld %d", + (long long)mConditionId, conditionCache[mIndex]); + return; + } const auto pair = conditionParameters.find(mConditionId); - if (pair == conditionParameters.end() && mOutputDimensions.child_size() > 0) { - ALOGE("Predicate %lld output has dimension, but it's not specified in the query!", - (long long)mConditionId); - conditionCache[mIndex] = mInitialValue; + if (pair == conditionParameters.end()) { + ConditionState conditionState = ConditionState::kNotEvaluated; + if (dimensionFields.has_field() && dimensionFields.child_size() > 0 && + dimensionFields.field() == mOutputDimensions.field()) { + conditionState = conditionState | getMetConditionDimension( + allConditions, dimensionFields, dimensionsKeySet); + } else { + conditionState = conditionState | mInitialValue; + if (!mSliced) { + const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); + if (itr != mSlicedConditionState.end()) { + ConditionState sliceState = + itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + conditionState = conditionState | sliceState; + } + } + } + conditionCache[mIndex] = conditionState; return; } - std::vector<HashableDimensionKey> defaultKeys = {DEFAULT_DIMENSION_KEY}; + std::vector<HashableDimensionKey> defaultKeys = { DEFAULT_DIMENSION_KEY }; const std::vector<HashableDimensionKey> &keys = (pair == conditionParameters.end()) ? defaultKeys : pair->second; ConditionState conditionState = ConditionState::kNotEvaluated; - for (const auto& key : keys) { + for (size_t i = 0; i < keys.size(); ++i) { + const HashableDimensionKey& key = keys[i]; auto startedCountIt = mSlicedConditionState.find(key); if (startedCountIt != mSlicedConditionState.end()) { - conditionState = conditionState | - (startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse); + ConditionState sliceState = + startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + conditionState = conditionState | sliceState; + if (sliceState == ConditionState::kTrue && dimensionFields.has_field()) { + HashableDimensionKey dimensionKey; + if (getSubDimension(startedCountIt->first.getDimensionsValue(), dimensionFields, + dimensionKey.getMutableDimensionsValue())) { + dimensionsKeySet.insert(dimensionKey); + } + } } else { // For unseen key, check whether the require dimensions are subset of sliced condition // output. - bool seenDimension = false; + conditionState = conditionState | mInitialValue; for (const auto& slice : mSlicedConditionState) { - if (IsSubDimension(slice.first.getDimensionsValue(), - key.getDimensionsValue())) { - seenDimension = true; - conditionState = conditionState | - (slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse); - } - if (conditionState == ConditionState::kTrue) { - break; + ConditionState sliceState = + slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + if (IsSubDimension(slice.first.getDimensionsValue(), key.getDimensionsValue())) { + conditionState = conditionState | sliceState; + if (sliceState == ConditionState::kTrue && dimensionFields.has_field()) { + HashableDimensionKey dimensionKey; + if (getSubDimension(slice.first.getDimensionsValue(), + dimensionFields, dimensionKey.getMutableDimensionsValue())) { + dimensionsKeySet.insert(dimensionKey); + } + } } } - if (!seenDimension) { - conditionState = conditionState | mInitialValue; - } } } conditionCache[mIndex] = conditionState; VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]); } +ConditionState SimpleConditionTracker::getMetConditionDimension( + const std::vector<sp<ConditionTracker>>& allConditions, + const FieldMatcher& dimensionFields, + std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const { + ConditionState conditionState = mInitialValue; + if (!dimensionFields.has_field() || + !mOutputDimensions.has_field() || + dimensionFields.field() != mOutputDimensions.field()) { + const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); + if (itr != mSlicedConditionState.end()) { + ConditionState sliceState = + itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + conditionState = conditionState | sliceState; + } + return conditionState; + } + + for (const auto& slice : mSlicedConditionState) { + ConditionState sliceState = + slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + DimensionsValue dimensionsValue; + conditionState = conditionState | sliceState; + HashableDimensionKey dimensionKey; + if (sliceState == ConditionState::kTrue && + getSubDimension(slice.first.getDimensionsValue(), dimensionFields, + dimensionKey.getMutableDimensionsValue())) { + dimensionsKeySet.insert(dimensionKey); + } + } + return conditionState; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h index 815b445a8c5b..ce9a02d4a795 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.h +++ b/cmds/statsd/src/condition/SimpleConditionTracker.h @@ -48,7 +48,14 @@ public: void isConditionMet(const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, - std::vector<ConditionState>& conditionCache) const override; + const FieldMatcher& dimensionFields, + std::vector<ConditionState>& conditionCache, + std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override; + + ConditionState getMetConditionDimension( + const std::vector<sp<ConditionTracker>>& allConditions, + const FieldMatcher& dimensionFields, + std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override; private: const ConfigKey mConfigKey; @@ -73,7 +80,8 @@ private: void handleStopAll(std::vector<ConditionState>& conditionCache, std::vector<bool>& changedCache); - void handleConditionEvent(const HashableDimensionKey& outputKey, bool matchStart, + void handleConditionEvent(const HashableDimensionKey& outputKey, + bool matchStart, std::vector<ConditionState>& conditionCache, std::vector<bool>& changedCache); diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp index 3b2d480b3ebf..0ab33cfbaea1 100644 --- a/cmds/statsd/src/condition/condition_util.cpp +++ b/cmds/statsd/src/condition/condition_util.cpp @@ -118,6 +118,9 @@ void OrBooleanVector(const std::vector<bool>& ref, vector<bool> * ored) { void getFieldsFromFieldMatcher(const FieldMatcher& matcher, Field* rootField, Field* leafField, std::vector<Field> *allFields) { + if (matcher.has_position()) { + leafField->set_position_index(0); + } if (matcher.child_size() == 0) { allFields->push_back(*rootField); return; diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp index 04445ca0e230..8a2e87128319 100644 --- a/cmds/statsd/src/dimension.cpp +++ b/cmds/statsd/src/dimension.cpp @@ -253,6 +253,9 @@ void buildAttributionFieldMatcher(const int tagId, const Position position, Fiel } void DimensionsValueToString(const DimensionsValue& value, std::string *flattened) { + if (!value.has_field()) { + return; + } *flattened += std::to_string(value.field()); *flattened += ":"; switch (value.value_case()) { @@ -352,6 +355,46 @@ long getLongFromDimenValue(const DimensionsValue& dimensionValue) { } } +bool getSubDimension(const DimensionsValue& dimension, const FieldMatcher& matcher, + DimensionsValue* subDimension) { + if (!matcher.has_field()) { + return false; + } + if (matcher.field() != dimension.field()) { + return false; + } + if (matcher.child_size() <= 0) { + if (dimension.value_case() == DimensionsValue::ValueCase::kValueTuple || + dimension.value_case() == DimensionsValue::ValueCase::VALUE_NOT_SET) { + return false; + } + *subDimension = dimension; + return true; + } else { + if (dimension.value_case() != DimensionsValue::ValueCase::kValueTuple) { + return false; + } + bool found_value = true; + auto value_tuple = dimension.value_tuple(); + subDimension->set_field(dimension.field()); + for (int i = 0; found_value && i < matcher.child_size(); ++i) { + int j = 0; + for (; j < value_tuple.dimensions_value_size(); ++j) { + if (value_tuple.dimensions_value(j).field() == matcher.child(i).field()) { + break; + } + } + if (j < value_tuple.dimensions_value_size()) { + found_value &= getSubDimension(value_tuple.dimensions_value(j), matcher.child(i), + subDimension->mutable_value_tuple()->add_dimensions_value()); + } else { + found_value = false; + } + } + return found_value; + } +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h index e900c5e87227..138c6e9b0160 100644 --- a/cmds/statsd/src/dimension.h +++ b/cmds/statsd/src/dimension.h @@ -63,6 +63,9 @@ bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub // Helper function to get long value from the DimensionsValue proto. long getLongFromDimenValue(const DimensionsValue& dimensionValue); + +bool getSubDimension(const DimensionsValue& dimension, const FieldMatcher& matcher, + DimensionsValue* subDimension); } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp new file mode 100644 index 000000000000..72fb5ffd4b90 --- /dev/null +++ b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp @@ -0,0 +1,101 @@ +/* + * 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. + */ + +#define DEBUG true // STOPSHIP if true +#include "Log.h" + +#include <android/hardware/health/2.0/IHealth.h> +#include <healthhalutils/HealthHalUtils.h> +#include "external/ResourceHealthManagerPuller.h" +#include "external/StatsPuller.h" + +#include "ResourceHealthManagerPuller.h" +#include "logd/LogEvent.h" +#include "statslog.h" + +using android::hardware::hidl_vec; +using android::hardware::health::V2_0::get_health_service; +using android::hardware::health::V2_0::HealthInfo; +using android::hardware::health::V2_0::IHealth; +using android::hardware::health::V2_0::Result; +using android::hardware::Return; +using android::hardware::Void; + +using std::make_shared; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +sp<android::hardware::health::V2_0::IHealth> gHealthHal = nullptr; + +bool getHealthHal() { + if (gHealthHal == nullptr) { + gHealthHal = get_health_service(); + + } + return gHealthHal != nullptr; +} + +ResourceHealthManagerPuller::ResourceHealthManagerPuller(int tagId) : StatsPuller(tagId) { +} + +// TODO: add other health atoms (eg. Temperature). +bool ResourceHealthManagerPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { + if (!getHealthHal()) { + ALOGE("Health Hal not loaded"); + return false; + } + + uint64_t timestamp = time(nullptr) * NS_PER_SEC; + + data->clear(); + bool result_success = true; + + Return<void> ret = gHealthHal->getHealthInfo([&](Result r, HealthInfo v) { + if (r != Result::SUCCESS) { + result_success = false; + return; + } + if (mTagId == android::util::REMAINING_BATTERY_CAPACITY) { + auto ptr = make_shared<LogEvent>(android::util::REMAINING_BATTERY_CAPACITY, timestamp); + ptr->write(v.legacy.batteryChargeCounter); + ptr->init(); + data->push_back(ptr); + } else if (mTagId == android::util::FULL_BATTERY_CAPACITY) { + auto ptr = make_shared<LogEvent>(android::util::FULL_BATTERY_CAPACITY, timestamp); + ptr->write(v.legacy.batteryFullCharge); + ptr->init(); + data->push_back(ptr); + } else { + ALOGE("Unsupported tag in ResourceHealthManagerPuller: %d", mTagId); + } + }); + if (!result_success || !ret.isOk()) { + ALOGE("getHealthHal() failed: health HAL service not available. Description: %s", + ret.description().c_str()); + if (!ret.isOk() && ret.isDeadObject()) { + gHealthHal = nullptr; + } + return false; + } + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/external/ResourceHealthManagerPuller.h b/cmds/statsd/src/external/ResourceHealthManagerPuller.h new file mode 100644 index 000000000000..9b238eaf5f83 --- /dev/null +++ b/cmds/statsd/src/external/ResourceHealthManagerPuller.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#pragma once + +#include <utils/String16.h> +#include "StatsPuller.h" + +namespace android { +namespace os { +namespace statsd { + +/** + * Reads Ihealth.hal + */ +class ResourceHealthManagerPuller : public StatsPuller { +public: + ResourceHealthManagerPuller(int tagId); + bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp index cadc535f2469..da14434737af 100644 --- a/cmds/statsd/src/external/StatsPuller.cpp +++ b/cmds/statsd/src/external/StatsPuller.cpp @@ -59,6 +59,11 @@ bool StatsPuller::Pull(std::vector<std::shared_ptr<LogEvent>>* data) { return ret; } +void StatsPuller::ClearCache() { + lock_guard<std::mutex> lock(mLock); + mCachedData.clear(); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h index 47cc9f01e054..bc7c45f535d1 100644 --- a/cmds/statsd/src/external/StatsPuller.h +++ b/cmds/statsd/src/external/StatsPuller.h @@ -38,6 +38,8 @@ public: bool Pull(std::vector<std::shared_ptr<LogEvent>>* data); + void ClearCache(); + protected: // The atom tag id this puller pulls const int mTagId; diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h index 00a1475c0829..4826d963a3ed 100644 --- a/cmds/statsd/src/external/StatsPullerManager.h +++ b/cmds/statsd/src/external/StatsPullerManager.h @@ -50,10 +50,14 @@ class StatsPullerManager { return mPullerManager.Pull(tagId, data); } - virtual void SetTimeBaseSec(const long timeBaseSec) { + void SetTimeBaseSec(const long timeBaseSec) { mPullerManager.SetTimeBaseSec(timeBaseSec); } + void ClearPullerCache() { + mPullerManager.ClearPullerCache(); + } + private: StatsPullerManagerImpl & mPullerManager = StatsPullerManagerImpl::GetInstance(); diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp index 148c9ae9b249..71b0abe25d28 100644 --- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp +++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp @@ -23,10 +23,11 @@ #include <climits> #include "CpuTimePerUidFreqPuller.h" #include "CpuTimePerUidPuller.h" -#include "SubsystemSleepStatePuller.h" +#include "ResourceHealthManagerPuller.h" #include "StatsCompanionServicePuller.h" #include "StatsPullerManagerImpl.h" #include "StatsService.h" +#include "SubsystemSleepStatePuller.h" #include "logd/LogEvent.h" #include "statslog.h" @@ -83,7 +84,10 @@ StatsPullerManagerImpl::StatsPullerManagerImpl() make_shared<StatsCompanionServicePuller>(android::util::WIFI_ACTIVITY_ENERGY_INFO)}); mPullers.insert({android::util::MODEM_ACTIVITY_INFO, make_shared<StatsCompanionServicePuller>(android::util::MODEM_ACTIVITY_INFO)}); - + mPullers.insert({android::util::REMAINING_BATTERY_CAPACITY, + make_shared<ResourceHealthManagerPuller>(android::util::REMAINING_BATTERY_CAPACITY)}); + mPullers.insert({android::util::FULL_BATTERY_CAPACITY, + make_shared<ResourceHealthManagerPuller>(android::util::FULL_BATTERY_CAPACITY)}); mStatsCompanionService = StatsService::getStatsCompanionService(); } @@ -195,6 +199,12 @@ void StatsPullerManagerImpl::OnAlarmFired() { } } +void StatsPullerManagerImpl::ClearPullerCache() { + for (auto puller : mPullers) { + puller.second->ClearCache(); + } +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.h b/cmds/statsd/src/external/StatsPullerManagerImpl.h index 7c59f6659d1f..fba3ade8c1bb 100644 --- a/cmds/statsd/src/external/StatsPullerManagerImpl.h +++ b/cmds/statsd/src/external/StatsPullerManagerImpl.h @@ -49,6 +49,8 @@ public: void SetTimeBaseSec(long timeBaseSec) {mTimeBaseSec = timeBaseSec;}; + void ClearPullerCache(); + private: StatsPullerManagerImpl(); diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 0455f6a210a9..ae4df3ee92f0 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -21,6 +21,7 @@ #include "guardrail/StatsdStats.h" #include "stats_util.h" #include "stats_log_util.h" +#include "dimension.h" #include <limits.h> #include <stdlib.h> @@ -71,13 +72,15 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric } // TODO: use UidMap if uid->pkg_name is required - mDimensions = metric.dimensions_in_what(); + mDimensionsInWhat = metric.dimensions_in_what(); + mDimensionsInCondition = metric.dimensions_in_condition(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), metric.links().end()); - mConditionSliced = true; } + mConditionSliced = (metric.links().size() > 0)|| + (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0); VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); @@ -99,7 +102,10 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog auto count_metrics = report->mutable_count_metrics(); for (const auto& counter : mPastBuckets) { CountMetricData* metricData = count_metrics->add_data(); - *metricData->mutable_dimensions_in_what() = counter.first.getDimensionsValue(); + *metricData->mutable_dimensions_in_what() = + counter.first.getDimensionKeyInWhat().getDimensionsValue(); + *metricData->mutable_dimensions_in_condition() = + counter.first.getDimensionKeyInCondition().getDimensionsValue(); for (const auto& bucket : counter.second) { CountBucketInfo* bucketInfo = metricData->add_bucket_info(); bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); @@ -123,17 +129,26 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, VLOG("metric %lld dump report now...",(long long)mMetricId); for (const auto& counter : mPastBuckets) { - const HashableDimensionKey& hashableKey = counter.first; - VLOG(" dimension key %s", hashableKey.c_str()); + const MetricDimensionKey& dimensionKey = counter.first; + VLOG(" dimension key %s", dimensionKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); // First fill dimension. - long long dimensionToken = protoOutput->start( + long long dimensionInWhatToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); - protoOutput->end(dimensionToken); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput); + protoOutput->end(dimensionInWhatToken); + + if (dimensionKey.hasDimensionKeyInCondition()) { + long long dimensionInConditionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput); + protoOutput->end(dimensionInConditionToken); + } // Then fill bucket_info (CountBucketInfo). for (const auto& bucket : counter.second) { @@ -166,7 +181,7 @@ void CountMetricProducer::onConditionChangedLocked(const bool conditionMet, mCondition = conditionMet; } -bool CountMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) { +bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) { return false; } @@ -187,7 +202,7 @@ bool CountMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) } void CountMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) { uint64_t eventTimeNs = event.GetTimestampNs(); diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index 061b7a36817c..8659d4773568 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -50,7 +50,7 @@ public: protected: void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; @@ -74,14 +74,14 @@ private: void flushIfNeededLocked(const uint64_t& newEventTime); // TODO: Add a lock to mPastBuckets. - std::unordered_map<HashableDimensionKey, std::vector<CountBucket>> mPastBuckets; + std::unordered_map<MetricDimensionKey, std::vector<CountBucket>> mPastBuckets; // The current bucket. std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>(); static const size_t kBucketSize = sizeof(CountBucket{}); - bool hitGuardRailLocked(const HashableDimensionKey& newKey); + bool hitGuardRailLocked(const MetricDimensionKey& newKey); FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents); FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition); diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 000874cf8f44..efbdae150792 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -21,6 +21,7 @@ #include "guardrail/StatsdStats.h" #include "stats_util.h" #include "stats_log_util.h" +#include "dimension.h" #include <limits.h> #include <stdlib.h> @@ -81,13 +82,15 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat } // TODO: use UidMap if uid->pkg_name is required - mDimensions = metric.dimensions_in_what(); + mDimensionsInWhat = metric.dimensions_in_what(); + mDimensionsInCondition = metric.dimensions_in_condition(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), metric.links().end()); - mConditionSliced = true; } + mConditionSliced = (metric.links().size() > 0)|| + (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0); VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); @@ -113,15 +116,17 @@ sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(const Alert &alert) } unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( - const HashableDimensionKey& eventKey) const { + const MetricDimensionKey& eventKey) const { switch (mAggregationType) { case DurationMetric_AggregationType_SUM: return make_unique<OringDurationTracker>( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, + mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers); case DurationMetric_AggregationType_MAX_SPARSE: return make_unique<MaxDurationTracker>( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, + mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers); } } @@ -129,10 +134,34 @@ unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) { VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); flushIfNeededLocked(eventTime); + // Now for each of the on-going event, check if the condition has changed for them. - for (auto& pair : mCurrentSlicedDuration) { + for (auto& pair : mCurrentSlicedDurationTrackerMap) { pair.second->onSlicedConditionMayChange(eventTime); } + + + std::unordered_set<HashableDimensionKey> conditionDimensionsKeySet; + ConditionState conditionState = mWizard->getMetConditionDimension( + mConditionTrackerIndex, mDimensionsInCondition, &conditionDimensionsKeySet); + + bool condition = (conditionState == ConditionState::kTrue); + for (auto& pair : mCurrentSlicedDurationTrackerMap) { + conditionDimensionsKeySet.erase(pair.first.getDimensionKeyInCondition()); + } + std::unordered_set<MetricDimensionKey> newKeys; + for (const auto& conditionDimensionsKey : conditionDimensionsKeySet) { + for (auto& pair : mCurrentSlicedDurationTrackerMap) { + auto newKey = + MetricDimensionKey(pair.first.getDimensionKeyInWhat(), conditionDimensionsKey); + if (newKeys.find(newKey) == newKeys.end()) { + mCurrentSlicedDurationTrackerMap[newKey] = pair.second->clone(eventTime); + mCurrentSlicedDurationTrackerMap[newKey]->setEventKey(newKey); + mCurrentSlicedDurationTrackerMap[newKey]->onSlicedConditionMayChange(eventTime); + } + newKeys.insert(newKey); + } + } } void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, @@ -142,7 +171,7 @@ void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, flushIfNeededLocked(eventTime); // TODO: need to populate the condition change time from the event which triggers the condition // change, instead of using current time. - for (auto& pair : mCurrentSlicedDuration) { + for (auto& pair : mCurrentSlicedDurationTrackerMap) { pair.second->onConditionChanged(conditionMet, eventTime); } } @@ -155,7 +184,10 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, Stats auto duration_metrics = report->mutable_duration_metrics(); for (const auto& pair : mPastBuckets) { DurationMetricData* metricData = duration_metrics->add_data(); - *metricData->mutable_dimensions_in_what() = pair.first.getDimensionsValue(); + *metricData->mutable_dimensions_in_what() = + pair.first.getDimensionKeyInWhat().getDimensionsValue(); + *metricData->mutable_dimensions_in_condition() = + pair.first.getDimensionKeyInCondition().getDimensionsValue(); for (const auto& bucket : pair.second) { auto bucketInfo = metricData->add_bucket_info(); bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); @@ -179,8 +211,8 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, VLOG("metric %lld dump report now...", (long long)mMetricId); for (const auto& pair : mPastBuckets) { - const HashableDimensionKey& hashableKey = pair.first; - VLOG(" dimension key %s", hashableKey.c_str()); + const MetricDimensionKey& dimensionKey = pair.first; + VLOG(" dimension key %s", dimensionKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); @@ -188,9 +220,18 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, // First fill dimension. long long dimensionToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput); protoOutput->end(dimensionToken); + if (dimensionKey.hasDimensionKeyInCondition()) { + long long dimensionInConditionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput); + protoOutput->end(dimensionInConditionToken); + } + // Then fill bucket_info (DurationBucketInfo). for (const auto& bucket : pair.second) { long long bucketInfoToken = protoOutput->start( @@ -219,10 +260,11 @@ void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) { return; } VLOG("flushing..........."); - for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end();) { + for (auto it = mCurrentSlicedDurationTrackerMap.begin(); + it != mCurrentSlicedDurationTrackerMap.end();) { if (it->second->flushIfNeeded(eventTime, &mPastBuckets)) { VLOG("erase bucket for key %s", it->first.c_str()); - it = mCurrentSlicedDuration.erase(it); + it = mCurrentSlicedDurationTrackerMap.erase(it); } else { ++it; } @@ -234,28 +276,28 @@ void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) { } void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { - if (mCurrentSlicedDuration.size() == 0) { + if (mCurrentSlicedDurationTrackerMap.size() == 0) { return; } fprintf(out, "DurationMetric %lld dimension size %lu\n", (long long)mMetricId, - (unsigned long)mCurrentSlicedDuration.size()); + (unsigned long)mCurrentSlicedDurationTrackerMap.size()); if (verbose) { - for (const auto& slice : mCurrentSlicedDuration) { + for (const auto& slice : mCurrentSlicedDurationTrackerMap) { fprintf(out, "\t%s\n", slice.first.c_str()); slice.second->dumpStates(out, verbose); } } } -bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) { +bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { // the key is not new, we are good. - if (mCurrentSlicedDuration.find(newKey) != mCurrentSlicedDuration.end()) { + if (mCurrentSlicedDurationTrackerMap.find(newKey) != mCurrentSlicedDurationTrackerMap.end()) { return false; } // 1. Report the tuple count if the tuple count > soft limit - if (mCurrentSlicedDuration.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = mCurrentSlicedDuration.size() + 1; + if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { + size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1; StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { @@ -268,27 +310,26 @@ bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newK } void DurationMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys, bool condition, const LogEvent& event) { flushIfNeededLocked(event.GetTimestampNs()); if (matcherIndex == mStopAllIndex) { - for (auto& pair : mCurrentSlicedDuration) { + for (auto& pair : mCurrentSlicedDurationTrackerMap) { pair.second->noteStopAll(event.GetTimestampNs()); } return; } - - if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) { + if (mCurrentSlicedDurationTrackerMap.find(eventKey) == mCurrentSlicedDurationTrackerMap.end()) { if (hitGuardRailLocked(eventKey)) { return; } - mCurrentSlicedDuration[eventKey] = createDurationTracker(eventKey); + mCurrentSlicedDurationTrackerMap[eventKey] = createDurationTracker(eventKey); } - auto it = mCurrentSlicedDuration.find(eventKey); + auto it = mCurrentSlicedDurationTrackerMap.find(eventKey); std::vector<DimensionsValue> values; getDimensionKeys(event, mInternalDimensions, &values); @@ -302,10 +343,11 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked( } else { for (const DimensionsValue& value : values) { if (matcherIndex == mStartIndex) { - it->second->noteStart(HashableDimensionKey(value), condition, - event.GetTimestampNs(), conditionKeys); + it->second->noteStart( + HashableDimensionKey(value), condition, event.GetTimestampNs(), conditionKeys); } else if (matcherIndex == mStopIndex) { - it->second->noteStop(HashableDimensionKey(value), event.GetTimestampNs(), false); + it->second->noteStop( + HashableDimensionKey(value), event.GetTimestampNs(), false); } } } diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index d8cab92a2b84..152e570df467 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -50,7 +50,7 @@ public: protected: void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys, bool condition, const LogEvent& event) override; @@ -92,21 +92,21 @@ private: // Save the past buckets and we can clear when the StatsLogReport is dumped. // TODO: Add a lock to mPastBuckets. - std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>> mPastBuckets; + std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets; // The current bucket. - std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>> - mCurrentSlicedDuration; + std::unordered_map<MetricDimensionKey, std::unique_ptr<DurationTracker>> + mCurrentSlicedDurationTrackerMap; // Helper function to create a duration tracker given the metric aggregation type. std::unique_ptr<DurationTracker> createDurationTracker( - const HashableDimensionKey& eventKey) const; + const MetricDimensionKey& eventKey) const; // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; // Util function to check whether the specified dimension hits the guardrail. - bool hitGuardRailLocked(const HashableDimensionKey& newKey); + bool hitGuardRailLocked(const MetricDimensionKey& newKey); static const size_t kBucketSize = sizeof(DurationBucket{}); diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index 25c86d0d46f1..820d5918a92a 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -128,7 +128,7 @@ void EventMetricProducer::onConditionChangedLocked(const bool conditionMet, } void EventMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) { if (!condition) { diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h index 9da0dd0569d6..935f206017fa 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.h +++ b/cmds/statsd/src/metrics/EventMetricProducer.h @@ -45,7 +45,7 @@ protected: private: void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 1072c5aae6e4..d6cb1891288a 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -82,13 +82,15 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric mFieldFilter = metric.gauge_fields_filter(); // TODO: use UidMap if uid->pkg_name is required - mDimensions = metric.dimensions_in_what(); + mDimensionsInWhat = metric.dimensions_in_what(); + mDimensionsInCondition = metric.dimensions_in_condition(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), metric.links().end()); - mConditionSliced = true; } + mConditionSliced = (metric.links().size() > 0)|| + (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0); // Kicks off the puller immediately. if (mPullTagId != -1 && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { @@ -136,18 +138,27 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS); for (const auto& pair : mPastBuckets) { - const HashableDimensionKey& hashableKey = pair.first; + const MetricDimensionKey& dimensionKey = pair.first; - VLOG(" dimension key %s", hashableKey.c_str()); + VLOG(" dimension key %s", dimensionKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); // First fill dimension. long long dimensionToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput); protoOutput->end(dimensionToken); + if (dimensionKey.hasDimensionKeyInCondition()) { + long long dimensionInConditionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput); + protoOutput->end(dimensionInConditionToken); + } + // Then fill bucket_info (GaugeBucketInfo). for (const auto& bucket : pair.second) { long long bucketInfoToken = protoOutput->start( @@ -248,7 +259,7 @@ void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven } } -bool GaugeMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) { +bool GaugeMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { if (mCurrentSlicedBucket->find(newKey) != mCurrentSlicedBucket->end()) { return false; } @@ -268,7 +279,7 @@ bool GaugeMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) } void GaugeMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) { if (condition == false) { diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index 6c013477af37..86d0ccd241eb 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -44,7 +44,7 @@ struct GaugeBucket { uint64_t mBucketNum; }; -typedef std::unordered_map<HashableDimensionKey, std::vector<GaugeAtom>> +typedef std::unordered_map<MetricDimensionKey, std::vector<GaugeAtom>> DimToGaugeAtomsMap; // This gauge metric producer first register the puller to automatically pull the gauge at the @@ -64,7 +64,7 @@ public: protected: void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; @@ -99,7 +99,7 @@ private: // Save the past buckets and we can clear when the StatsLogReport is dumped. // TODO: Add a lock to mPastBuckets. - std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets; + std::unordered_map<MetricDimensionKey, std::vector<GaugeBucket>> mPastBuckets; // The current bucket. std::shared_ptr<DimToGaugeAtomsMap> mCurrentSlicedBucket; @@ -119,7 +119,7 @@ private: std::shared_ptr<FieldValueMap> getGaugeFields(const LogEvent& event); // Util function to check whether the specified dimension hits the guardrail. - bool hitGuardRailLocked(const HashableDimensionKey& newKey); + bool hitGuardRailLocked(const MetricDimensionKey& newKey); static const size_t kBucketSize = sizeof(GaugeBucket{}); diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index e74924a81fbf..85e655b08f4d 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -15,6 +15,8 @@ */ #include "MetricProducer.h" +#include "dimension.h" + namespace android { namespace os { namespace statsd { @@ -30,29 +32,51 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo bool condition; ConditionKey conditionKey; + + std::unordered_set<HashableDimensionKey> dimensionKeysInCondition; if (mConditionSliced) { for (const auto& link : mConditionLinks) { getDimensionKeysForCondition(event, link, &conditionKey[link.condition()]); } - if (mWizard->query(mConditionTrackerIndex, conditionKey) != ConditionState::kTrue) { - condition = false; - } else { - condition = true; - } + auto conditionState = + mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition, + &dimensionKeysInCondition); + condition = (conditionState == ConditionState::kTrue); } else { condition = mCondition; } - if (mDimensions.has_field() && mDimensions.child_size() > 0) { - vector<DimensionsValue> dimensionValues; - getDimensionKeys(event, mDimensions, &dimensionValues); - for (const DimensionsValue& dimensionValue : dimensionValues) { + vector<DimensionsValue> dimensionInWhatValues; + if (mDimensionsInWhat.has_field() && mDimensionsInWhat.child_size() > 0) { + getDimensionKeys(event, mDimensionsInWhat, &dimensionInWhatValues); + } + + if (dimensionInWhatValues.empty() && dimensionKeysInCondition.empty()) { + onMatchedLogEventInternalLocked( + matcherIndex, DEFAULT_METRIC_DIMENSION_KEY, conditionKey, condition, event); + } else if (dimensionKeysInCondition.empty()) { + for (const DimensionsValue& whatValue : dimensionInWhatValues) { + onMatchedLogEventInternalLocked( + matcherIndex, + MetricDimensionKey(HashableDimensionKey(whatValue), DEFAULT_DIMENSION_KEY), + conditionKey, condition, event); + } + } else if (dimensionInWhatValues.empty()) { + for (const auto& conditionDimensionKey : dimensionKeysInCondition) { onMatchedLogEventInternalLocked( - matcherIndex, HashableDimensionKey(dimensionValue), conditionKey, condition, event); + matcherIndex, + MetricDimensionKey(DEFAULT_DIMENSION_KEY, conditionDimensionKey), + conditionKey, condition, event); } } else { - onMatchedLogEventInternalLocked( - matcherIndex, DEFAULT_DIMENSION_KEY, conditionKey, condition, event); + for (const DimensionsValue& whatValue : dimensionInWhatValues) { + for (const auto& conditionDimensionKey : dimensionKeysInCondition) { + onMatchedLogEventInternalLocked( + matcherIndex, + MetricDimensionKey(HashableDimensionKey(whatValue), conditionDimensionKey), + conditionKey, condition, event); + } + } } } diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 6f33073c633c..3b1498f07632 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -91,6 +91,7 @@ public: std::lock_guard<std::mutex> lock(mMutex); return onDumpReportLocked(dumpTimeNs, protoOutput); } + void onDumpReport(const uint64_t dumpTimeNs, StatsLogReport* report) { std::lock_guard<std::mutex> lock(mMutex); return onDumpReportLocked(dumpTimeNs, report); @@ -156,7 +157,8 @@ protected: int mConditionTrackerIndex; - FieldMatcher mDimensions; // The dimension defined in statsd_config + FieldMatcher mDimensionsInWhat; // The dimensions_in_what defined in statsd_config + FieldMatcher mDimensionsInCondition; // The dimensions_in_condition defined in statsd_config std::vector<MetricConditionLink> mConditionLinks; @@ -178,7 +180,7 @@ protected: * [event]: the log event, just in case the metric needs its data, e.g., EventMetric. */ virtual void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) = 0; diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index d0737de8acf3..636289522780 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -189,13 +189,7 @@ void MetricsManager::onLogEvent(const LogEvent& event) { return; } - if (event.GetTagId() != android::util::APP_HOOK) { - std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex); - if (mAllowedLogSources.find(event.GetUid()) == mAllowedLogSources.end()) { - VLOG("log source %d not on the whitelist", event.GetUid()); - return; - } - } else { // Check that app hook fields are valid. + if (event.GetTagId() == android::util::APP_HOOK) { // Check that app hook fields are valid. // TODO: Find a way to make these checks easier to maintain if the app hooks get changed. // Label is 2nd from last field and must be from [0, 15]. @@ -211,6 +205,21 @@ void MetricsManager::onLogEvent(const LogEvent& event) { VLOG("App hook does not have valid state %ld", apphookState); return; } + } else if (event.GetTagId() == android::util::DAVEY_OCCURRED) { + // Daveys can be logged from any app since they are logged in libs/hwui/JankTracker.cpp. + // Check that the davey duration is reasonable. Max length check is for privacy. + status_t err = NO_ERROR; + long duration = event.GetLong(event.size(), &err); + if (err != NO_ERROR || duration > 100000) { + VLOG("Davey duration is unreasonably long: %ld", duration); + return; + } + } else { + std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex); + if (mAllowedLogSources.find(event.GetUid()) == mAllowedLogSources.end()) { + VLOG("log source %d not on the whitelist", event.GetUid()); + return; + } } int tagId = event.GetTagId(); diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 9cdbafc75fb1..d4b9102d5ddc 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -143,6 +143,10 @@ private: FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); + FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index ae0c673e3490..c9cc7bb9c1d4 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -81,13 +81,15 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric } mBucketSizeNs = bucketSizeMills * 1000000; - mDimensions = metric.dimensions_in_what(); + mDimensionsInWhat = metric.dimensions_in_what(); + mDimensionsInCondition = metric.dimensions_in_condition(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), metric.links().end()); - mConditionSliced = true; } + mConditionSliced = (metric.links().size() > 0)|| + (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0); if (!metric.has_condition() && mPullTagId != -1) { VLOG("Setting up periodic pulling for %d", mPullTagId); @@ -124,7 +126,10 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog auto value_metrics = report->mutable_value_metrics(); for (const auto& pair : mPastBuckets) { ValueMetricData* metricData = value_metrics->add_data(); - *metricData->mutable_dimensions_in_what() = pair.first.getDimensionsValue(); + *metricData->mutable_dimensions_in_what() = + pair.first.getDimensionKeyInWhat().getDimensionsValue(); + *metricData->mutable_dimensions_in_condition() = + pair.first.getDimensionKeyInCondition().getDimensionsValue(); for (const auto& bucket : pair.second) { ValueBucketInfo* bucketInfo = metricData->add_bucket_info(); bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); @@ -146,16 +151,24 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS); for (const auto& pair : mPastBuckets) { - const HashableDimensionKey& hashableKey = pair.first; - VLOG(" dimension key %s", hashableKey.c_str()); + const MetricDimensionKey& dimensionKey = pair.first; + VLOG(" dimension key %s", dimensionKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); // First fill dimension. long long dimensionToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput); protoOutput->end(dimensionToken); + if (dimensionKey.hasDimensionKeyInCondition()) { + long long dimensionInConditionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput); + protoOutput->end(dimensionInConditionToken); + } // Then fill bucket_info (ValueBucketInfo). for (const auto& bucket : pair.second) { @@ -239,7 +252,7 @@ void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven } } -bool ValueMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) { +bool ValueMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { // ===========GuardRail============== // 1. Report the tuple count if the tuple count > soft limit if (mCurrentSlicedBucket.find(newKey) != mCurrentSlicedBucket.end()) { @@ -260,7 +273,7 @@ bool ValueMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) } void ValueMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) { uint64_t eventTimeNs = event.GetTimestampNs(); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 9f750cf419b5..121ec7d18515 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -49,7 +49,7 @@ public: protected: void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; @@ -99,16 +99,16 @@ private: long sum; } Interval; - std::unordered_map<HashableDimensionKey, Interval> mCurrentSlicedBucket; + std::unordered_map<MetricDimensionKey, Interval> mCurrentSlicedBucket; // Save the past buckets and we can clear when the StatsLogReport is dumped. // TODO: Add a lock to mPastBuckets. - std::unordered_map<HashableDimensionKey, std::vector<ValueBucket>> mPastBuckets; + std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>> mPastBuckets; std::shared_ptr<FieldValueMap> getValueFields(const LogEvent& event); // Util function to check whether the specified dimension hits the guardrail. - bool hitGuardRailLocked(const HashableDimensionKey& newKey); + bool hitGuardRailLocked(const MetricDimensionKey& newKey); static const size_t kBucketSize = sizeof(ValueBucket{}); diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index c2d2cea2a1ff..45735a866978 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -60,8 +60,9 @@ struct DurationBucket { class DurationTracker { public: - DurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, bool nesting, + DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, + sp<ConditionWizard> wizard, int conditionIndex, + const FieldMatcher& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers) : mConfigKey(key), @@ -70,6 +71,7 @@ public: mWizard(wizard), mConditionTrackerIndex(conditionIndex), mBucketSizeNs(bucketSizeNs), + mDimensionInCondition(dimensionInCondition), mNested(nesting), mCurrentBucketStartTimeNs(currentBucketStartNs), mDuration(0), @@ -79,6 +81,8 @@ public: virtual ~DurationTracker(){}; + virtual unique_ptr<DurationTracker> clone(const uint64_t eventTime) = 0; + virtual void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime, const ConditionKey& conditionKey) = 0; virtual void noteStop(const HashableDimensionKey& key, const uint64_t eventTime, @@ -92,7 +96,7 @@ public: // events, so that the owner can safely remove the tracker. virtual bool flushIfNeeded( uint64_t timestampNs, - std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) = 0; + std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) = 0; // Predict the anomaly timestamp given the current status. virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, @@ -100,6 +104,10 @@ public: // Dump internal states for debugging virtual void dumpStates(FILE* out, bool verbose) const = 0; + void setEventKey(const MetricDimensionKey& eventKey) { + mEventKey = eventKey; + } + protected: // Starts the anomaly alarm. void startAnomalyAlarm(const uint64_t eventTime) { @@ -150,7 +158,7 @@ protected: const int64_t mTrackerId; - HashableDimensionKey mEventKey; + MetricDimensionKey mEventKey; sp<ConditionWizard> mWizard; @@ -158,6 +166,8 @@ protected: const int64_t mBucketSizeNs; + const FieldMatcher mDimensionInCondition; + const bool mNested; uint64_t mCurrentBucketStartTimeNs; diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 412a0c935766..db7dea4afd04 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -25,13 +25,23 @@ namespace os { namespace statsd { MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, - const HashableDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, bool nesting, + const MetricDimensionKey& eventKey, + sp<ConditionWizard> wizard, int conditionIndex, + const FieldMatcher& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, - bucketSizeNs, conditionSliced, anomalyTrackers) { + : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting, + currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers) { +} + +unique_ptr<DurationTracker> MaxDurationTracker::clone(const uint64_t eventTime) { + auto clonedTracker = make_unique<MaxDurationTracker>(*this); + for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end(); ++it) { + it->second.lastStartTime = eventTime; + it->second.lastDuration = 0; + } + return clonedTracker; } bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { @@ -44,7 +54,7 @@ bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mInfos.size() + 1; StatsdStats::getInstance().noteMetricDimensionSize( - mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()), + mConfigKey, hashMetricDimensionKey(mTrackerId, mEventKey), newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { @@ -149,7 +159,7 @@ void MaxDurationTracker::noteStopAll(const uint64_t eventTime) { } bool MaxDurationTracker::flushIfNeeded( - uint64_t eventTime, unordered_map<HashableDimensionKey, vector<DurationBucket>>* output) { + uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) { if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) { return false; } @@ -236,8 +246,14 @@ void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) { if (pair.second.state == kStopped) { continue; } - bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) == - ConditionState::kTrue; + std::unordered_set<HashableDimensionKey> conditionDimensionKeySet; + ConditionState conditionState = mWizard->query( + mConditionTrackerIndex, pair.second.conditionKeys, mDimensionInCondition, + &conditionDimensionKeySet); + bool conditionMet = (conditionState == ConditionState::kTrue) && + (!mDimensionInCondition.has_field() || + conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) != + conditionDimensionKeySet.end()); VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet); noteConditionChanged(pair.first, conditionMet, timestamp); } diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index 661d1311293a..4d32a0637d56 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -29,10 +29,15 @@ namespace statsd { class MaxDurationTracker : public DurationTracker { public: MaxDurationTracker(const ConfigKey& key, const int64_t& id, - const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard, - int conditionIndex, bool nesting, uint64_t currentBucketStartNs, - uint64_t bucketSizeNs, bool conditionSliced, + const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, + int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting, + uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); + + MaxDurationTracker(const MaxDurationTracker& tracker) = default; + + unique_ptr<DurationTracker> clone(const uint64_t eventTime) override; + void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime, const ConditionKey& conditionKey) override; void noteStop(const HashableDimensionKey& key, const uint64_t eventTime, @@ -41,7 +46,7 @@ public: bool flushIfNeeded( uint64_t timestampNs, - std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) override; + std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override; void onSlicedConditionMayChange(const uint64_t timestamp) override; void onConditionChanged(bool condition, const uint64_t timestamp) override; diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 75d7c0898d78..0feae369b0d5 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -25,17 +25,25 @@ namespace statsd { using std::pair; OringDurationTracker::OringDurationTracker( - const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, + const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, + sp<ConditionWizard> wizard, int conditionIndex, + const FieldMatcher& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, - bucketSizeNs, conditionSliced, anomalyTrackers), + : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting, + currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers), mStarted(), mPaused() { mLastStartTime = 0; } +unique_ptr<DurationTracker> OringDurationTracker::clone(const uint64_t eventTime) { + auto clonedTracker = make_unique<OringDurationTracker>(*this); + clonedTracker->mLastStartTime = eventTime; + clonedTracker->mDuration = 0; + return clonedTracker; +} + bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { // ===========GuardRail============== // 1. Report the tuple count if the tuple count > soft limit @@ -45,7 +53,7 @@ bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mConditionKeyMap.size() + 1; StatsdStats::getInstance().noteMetricDimensionSize( - mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()), + mConfigKey, hashMetricDimensionKey(mTrackerId, mEventKey), newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { @@ -76,7 +84,6 @@ void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condi if (mConditionSliced && mConditionKeyMap.find(key) == mConditionKeyMap.end()) { mConditionKeyMap[key] = conditionKey; } - VLOG("Oring: %s start, condition %d", key.c_str(), condition); } @@ -128,7 +135,7 @@ void OringDurationTracker::noteStopAll(const uint64_t timestamp) { } bool OringDurationTracker::flushIfNeeded( - uint64_t eventTime, unordered_map<HashableDimensionKey, vector<DurationBucket>>* output) { + uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) { if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) { return false; } @@ -184,8 +191,14 @@ void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) ++it; continue; } - if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) != - ConditionState::kTrue) { + std::unordered_set<HashableDimensionKey> conditionDimensionKeySet; + ConditionState conditionState = + mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key], + mDimensionInCondition, &conditionDimensionKeySet); + if (conditionState != ConditionState::kTrue || + (mDimensionInCondition.has_field() && + conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) == + conditionDimensionKeySet.end())) { startedToPaused.push_back(*it); it = mStarted.erase(it); VLOG("Key %s started -> paused", key.c_str()); @@ -210,8 +223,14 @@ void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) ++it; continue; } - if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) == - ConditionState::kTrue) { + std::unordered_set<HashableDimensionKey> conditionDimensionKeySet; + ConditionState conditionState = + mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key], + mDimensionInCondition, &conditionDimensionKeySet); + if (conditionState == ConditionState::kTrue && + (!mDimensionInCondition.has_field() || + conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) + != conditionDimensionKeySet.end())) { pausedToStarted.push_back(*it); it = mPaused.erase(it); VLOG("Key %s paused -> started", key.c_str()); diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index 43469ca9a551..75b5a815de9c 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -28,11 +28,15 @@ namespace statsd { class OringDurationTracker : public DurationTracker { public: OringDurationTracker(const ConfigKey& key, const int64_t& id, - const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard, - int conditionIndex, bool nesting, uint64_t currentBucketStartNs, - uint64_t bucketSizeNs, bool conditionSliced, + const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, + int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting, + uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); + OringDurationTracker(const OringDurationTracker& tracker) = default; + + unique_ptr<DurationTracker> clone(const uint64_t eventTime) override; + void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime, const ConditionKey& conditionKey) override; void noteStop(const HashableDimensionKey& key, const uint64_t eventTime, @@ -44,7 +48,7 @@ public: bool flushIfNeeded( uint64_t timestampNs, - std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) override; + std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override; int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const override; diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index a41f30c2bece..6c6140081bd8 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -54,6 +54,9 @@ const int FIELD_ID_MIN_PULL_INTERVAL_SEC = 4; void writeDimensionsValueProtoToStream(const DimensionsValue& dimensionsValue, ProtoOutputStream* protoOutput) { + if (!dimensionsValue.has_field()) { + return; + } protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, dimensionsValue.field()); switch (dimensionsValue.value_case()) { case DimensionsValue::ValueCase::kValueStr: @@ -103,6 +106,9 @@ const int FIELD_CHILD = 3; void writeFieldProtoToStream( const Field& field, util::ProtoOutputStream* protoOutput) { + if (!field.has_field()) { + return; + } protoOutput->write(FIELD_TYPE_INT32 | FIELD_FIELD, field.field()); if (field.has_position_index()) { protoOutput->write(FIELD_TYPE_INT32 | FIELD_POSITION_INDEX, field.position_index()); diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h index 160b1f40243f..31f51a7ac0a4 100644 --- a/cmds/statsd/src/stats_util.h +++ b/cmds/statsd/src/stats_util.h @@ -28,13 +28,14 @@ namespace os { namespace statsd { const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(); +const MetricDimensionKey DEFAULT_METRIC_DIMENSION_KEY = MetricDimensionKey(); // Minimum bucket size in seconds const long kMinBucketSizeSec = 5 * 60; typedef std::map<int64_t, std::vector<HashableDimensionKey>> ConditionKey; -typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap; +typedef std::unordered_map<MetricDimensionKey, int64_t> DimToValMap; } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp index f912e4b2cd24..3af684fa6069 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp +++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp @@ -56,7 +56,7 @@ void SubscriberReporter::removeConfig(const ConfigKey& configKey) { void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, const Subscription& subscription, - const HashableDimensionKey& dimKey) const { + const MetricDimensionKey& dimKey) const { // Reminder about ids: // subscription id - name of the Subscription (that ties the Alert to the broadcast) // subscription rule_id - the name of the Alert (that triggers the broadcast) @@ -92,7 +92,7 @@ void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender, const ConfigKey& configKey, const Subscription& subscription, - const HashableDimensionKey& dimKey) const { + const MetricDimensionKey& dimKey) const { VLOG("SubscriberReporter::sendBroadcastLocked called."); if (mStatsCompanionService == nullptr) { ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService."); @@ -107,8 +107,8 @@ void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender, } StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue( - const HashableDimensionKey& dimKey) { - return protoToStatsDimensionsValue(dimKey.getDimensionsValue()); + const MetricDimensionKey& dimKey) { + return protoToStatsDimensionsValue(dimKey.getDimensionKeyInWhat().getDimensionsValue()); } StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue( diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h index 5bb458a8b1d8..13fc7fd06279 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.h +++ b/cmds/statsd/src/subscriber/SubscriberReporter.h @@ -80,7 +80,7 @@ public: */ void alertBroadcastSubscriber(const ConfigKey& configKey, const Subscription& subscription, - const HashableDimensionKey& dimKey) const; + const MetricDimensionKey& dimKey) const; private: SubscriberReporter() {}; @@ -101,7 +101,7 @@ private: void sendBroadcastLocked(const sp<android::IBinder>& intentSender, const ConfigKey& configKey, const Subscription& subscription, - const HashableDimensionKey& dimKey) const; + const MetricDimensionKey& dimKey) const; /** Converts a stats_log.proto DimensionsValue to a StatsDimensionsValue. */ static StatsDimensionsValue protoToStatsDimensionsValue( @@ -109,7 +109,7 @@ private: /** Converts a HashableDimensionKey to a StatsDimensionsValue. */ static StatsDimensionsValue protoToStatsDimensionsValue( - const HashableDimensionKey& dimKey); + const MetricDimensionKey& dimKey); }; } // namespace statsd diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp index 66bfa68ecc55..a415ea1a3069 100644 --- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp +++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp @@ -33,14 +33,14 @@ namespace statsd { const ConfigKey kConfigKey(0, 12345); -HashableDimensionKey getMockDimensionKey(int key, string value) { +MetricDimensionKey getMockMetricDimensionKey(int key, string value) { DimensionsValue dimensionsValue; dimensionsValue.set_field(key); dimensionsValue.set_value_str(value); - return HashableDimensionKey(dimensionsValue); + return MetricDimensionKey(HashableDimensionKey(dimensionsValue), DEFAULT_DIMENSION_KEY); } -void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list, +void AddValueToBucket(const std::vector<std::pair<MetricDimensionKey, long>>& key_value_pair_list, std::shared_ptr<DimToValMap> bucket) { for (auto itr = key_value_pair_list.begin(); itr != key_value_pair_list.end(); itr++) { (*bucket)[itr->first] += itr->second; @@ -48,7 +48,7 @@ void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, long>>& } std::shared_ptr<DimToValMap> MockBucket( - const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list) { + const std::vector<std::pair<MetricDimensionKey, long>>& key_value_pair_list) { std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>(); AddValueToBucket(key_value_pair_list, bucket); return bucket; @@ -56,7 +56,7 @@ std::shared_ptr<DimToValMap> MockBucket( // Returns the value, for the given key, in that bucket, or 0 if not present. int64_t getBucketValue(const std::shared_ptr<DimToValMap>& bucket, - const HashableDimensionKey& key) { + const MetricDimensionKey& key) { const auto& itr = bucket->find(key); if (itr != bucket->end()) { return itr->second; @@ -68,14 +68,14 @@ int64_t getBucketValue(const std::shared_ptr<DimToValMap>& bucket, bool detectAnomaliesPass(AnomalyTracker& tracker, const int64_t& bucketNum, const std::shared_ptr<DimToValMap>& currentBucket, - const std::set<const HashableDimensionKey>& trueList, - const std::set<const HashableDimensionKey>& falseList) { - for (HashableDimensionKey key : trueList) { + const std::set<const MetricDimensionKey>& trueList, + const std::set<const MetricDimensionKey>& falseList) { + for (MetricDimensionKey key : trueList) { if (!tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) { return false; } } - for (HashableDimensionKey key : falseList) { + for (MetricDimensionKey key : falseList) { if (tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) { return false; } @@ -100,7 +100,7 @@ void detectAndDeclareAnomalies(AnomalyTracker& tracker, void checkRefractoryTimes(AnomalyTracker& tracker, const int64_t& currTimestampNs, const int32_t& refractoryPeriodSec, - const std::unordered_map<HashableDimensionKey, int64_t>& timestamps) { + const std::unordered_map<MetricDimensionKey, int64_t>& timestamps) { for (const auto& kv : timestamps) { if (kv.second < 0) { // Make sure that, if there is a refractory period, it is already past. @@ -124,9 +124,9 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { alert.set_trigger_if_sum_gt(2); AnomalyTracker anomalyTracker(alert, kConfigKey); - HashableDimensionKey keyA = getMockDimensionKey(1, "a"); - HashableDimensionKey keyB = getMockDimensionKey(1, "b"); - HashableDimensionKey keyC = getMockDimensionKey(1, "c"); + MetricDimensionKey keyA = getMockMetricDimensionKey(1, "a"); + MetricDimensionKey keyB = getMockMetricDimensionKey(1, "b"); + MetricDimensionKey keyC = getMockMetricDimensionKey(1, "c"); int64_t eventTimestamp0 = 10 * NS_PER_SEC; int64_t eventTimestamp1 = bucketSizeNs + 11 * NS_PER_SEC; @@ -269,11 +269,11 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { alert.set_trigger_if_sum_gt(2); AnomalyTracker anomalyTracker(alert, kConfigKey); - HashableDimensionKey keyA = getMockDimensionKey(1, "a"); - HashableDimensionKey keyB = getMockDimensionKey(1, "b"); - HashableDimensionKey keyC = getMockDimensionKey(1, "c"); - HashableDimensionKey keyD = getMockDimensionKey(1, "d"); - HashableDimensionKey keyE = getMockDimensionKey(1, "e"); + MetricDimensionKey keyA = getMockMetricDimensionKey(1, "a"); + MetricDimensionKey keyB = getMockMetricDimensionKey(1, "b"); + MetricDimensionKey keyC = getMockMetricDimensionKey(1, "c"); + MetricDimensionKey keyD = getMockMetricDimensionKey(1, "d"); + MetricDimensionKey keyE = getMockMetricDimensionKey(1, "e"); std::shared_ptr<DimToValMap> bucket9 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}}); std::shared_ptr<DimToValMap> bucket16 = MockBucket({{keyB, 4}}); diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp index 819f2bebf327..d1b7b2842fd2 100644 --- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp +++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp @@ -78,7 +78,7 @@ void makeWakeLockEvent( std::map<int64_t, std::vector<HashableDimensionKey>> getWakeLockQueryKey( const Position position, const std::vector<int> &uids, const string& conditionName) { - std::map<int64_t, std::vector<HashableDimensionKey>> outputKeyMap; + std::map<int64_t, std::vector<HashableDimensionKey>> outputKeyMap; std::vector<int> uid_indexes; switch(position) { case Position::FIRST: @@ -265,6 +265,9 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { TEST(SimpleConditionTrackerTest, TestSlicedCondition) { for (Position position : { Position::ANY, Position::FIRST, Position::LAST}) { + FieldMatcher dimensionInCondition; + std::unordered_set<HashableDimensionKey> dimensionKeys; + SimplePredicate simplePredicate = getWakeLockHeldCondition( true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, position); @@ -307,7 +310,8 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { const auto queryKey = getWakeLockQueryKey(position, uids, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by this uid @@ -361,7 +365,8 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { // query again conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } @@ -369,6 +374,9 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { } TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { + FieldMatcher dimensionInCondition; + std::unordered_set<HashableDimensionKey> dimensionKeys; + SimplePredicate simplePredicate = getWakeLockHeldCondition( true /*nesting*/, true /*default to false*/, false /*slice output by uid*/, Position::ANY /* position */); @@ -410,7 +418,8 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { ConditionKey queryKey; conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by this uid @@ -452,13 +461,17 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { // query again conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + dimensionKeys.clear(); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } TEST(SimpleConditionTrackerTest, TestStopAll) { for (Position position : {Position::ANY, Position::FIRST, Position::LAST}) { + FieldMatcher dimensionInCondition; + std::unordered_set<HashableDimensionKey> dimensionKeys; SimplePredicate simplePredicate = getWakeLockHeldCondition( true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, position); @@ -502,7 +515,8 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by uid2 @@ -528,8 +542,9 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { // TEST QUERY const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); @@ -550,15 +565,15 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { // TEST QUERY const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); // TEST QUERY const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } diff --git a/cmds/statsd/tests/dimension_test.cpp b/cmds/statsd/tests/dimension_test.cpp new file mode 100644 index 000000000000..678abaed5fd8 --- /dev/null +++ b/cmds/statsd/tests/dimension_test.cpp @@ -0,0 +1,149 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dimension.h" + +#include <gtest/gtest.h> + +using namespace android::os::statsd; + +#ifdef __ANDROID__ + +TEST(DimensionTest, subLeafNodes) { + DimensionsValue dimension; + int tagId = 100; + dimension.set_field(tagId); + auto child = dimension.mutable_value_tuple()->add_dimensions_value(); + child->set_field(1); + child->set_value_int(2000); + + child = dimension.mutable_value_tuple()->add_dimensions_value(); + child->set_field(3); + child->set_value_str("test"); + + child = dimension.mutable_value_tuple()->add_dimensions_value(); + child->set_field(4); + auto grandChild = child->mutable_value_tuple()->add_dimensions_value(); + grandChild->set_field(1); + grandChild->set_value_float(1.3f); + grandChild = child->mutable_value_tuple()->add_dimensions_value(); + grandChild->set_field(3); + grandChild->set_value_str("tag"); + + child = dimension.mutable_value_tuple()->add_dimensions_value(); + child->set_field(6); + child->set_value_bool(false); + + DimensionsValue sub_dimension; + FieldMatcher matcher; + + // Tag id not matched. + matcher.set_field(tagId + 1); + EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Field not exist. + matcher.Clear(); + matcher.set_field(tagId); + matcher.add_child()->set_field(5); + EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Field exists. + matcher.Clear(); + matcher.set_field(tagId); + matcher.add_child()->set_field(3); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Field exists. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + matcher.add_child()->set_field(6); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Field exists. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + matcher.add_child()->set_field(1); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Not leaf field. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + matcher.add_child()->set_field(4); + EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Grand-child leaf field not exist. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + auto childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(2); + EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Grand-child leaf field. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(1); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(3); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Multiple grand-child fields. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(3); + childMatcher->add_child()->set_field(1); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Multiple fields. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(3); + childMatcher->add_child()->set_field(1); + matcher.add_child()->set_field(3); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Subset of the fields not exist. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(3); + childMatcher->add_child()->set_field(1); + matcher.add_child()->set_field(2); + EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension)); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp new file mode 100644 index 000000000000..b5d48efb55f6 --- /dev/null +++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp @@ -0,0 +1,725 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <gtest/gtest.h> + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +#include <vector> + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +StatsdConfig CreateCountMetricWithNoLinkConfig() { + StatsdConfig config; + auto screenBrightnessChangeAtomMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = screenBrightnessChangeAtomMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + *config.add_predicate() = screenIsOffPredicate; + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + // The predicate is dimensioning by any attribution node and both by uid and tag. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidAndTagDimensions( + android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *config.add_predicate() = holdingWakelockPredicate; + + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(987654); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(holdingWakelockPredicate, combinationPredicate); + + auto metric = config.add_count_metric(); + metric->set_id(StringToId("ScreenBrightnessChangeMetric")); + metric->set_what(screenBrightnessChangeAtomMatcher.id()); + metric->set_condition(combinationPredicate->id()); + *metric->mutable_dimensions_in_what() = CreateDimensions( + android::util::SCREEN_BRIGHTNESS_CHANGED, {1 /* level */}); + *metric->mutable_dimensions_in_condition() = CreateAttributionUidDimensions( + android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + metric->set_bucket(ONE_MINUTE); + return config; +} + +} // namespace + +TEST(DimensionInConditionE2eTest, TestCountMetricNoLink) { + ConfigKey cfgKey; + auto config = CreateCountMetricWithNoLinkConfig(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + std::vector<AttributionNode> attributions1 = + {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector<AttributionNode> attributions2 = + {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + bucketSizeNs + 1)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 2 * bucketSizeNs - 10)); + + events.push_back(CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 200)); + events.push_back(CreateReleaseWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1)); + + events.push_back(CreateAcquireWakelockEvent( + attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 100)); + events.push_back(CreateReleaseWakelockEvent( + attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 50)); + + events.push_back(CreateScreenBrightnessChangedEvent( + 123, bucketStartTimeNs + 11)); + events.push_back(CreateScreenBrightnessChangedEvent( + 123, bucketStartTimeNs + 101)); + events.push_back(CreateScreenBrightnessChangedEvent( + 123, bucketStartTimeNs + 201)); + events.push_back(CreateScreenBrightnessChangedEvent( + 456, bucketStartTimeNs + 203)); + events.push_back(CreateScreenBrightnessChangedEvent( + 456, bucketStartTimeNs + bucketSizeNs - 99)); + events.push_back(CreateScreenBrightnessChangedEvent( + 456, bucketStartTimeNs + bucketSizeNs - 2)); + events.push_back(CreateScreenBrightnessChangedEvent( + 789, bucketStartTimeNs + bucketSizeNs - 1)); + events.push_back(CreateScreenBrightnessChangedEvent( + 456, bucketStartTimeNs + bucketSizeNs + 2)); + events.push_back(CreateScreenBrightnessChangedEvent( + 789, bucketStartTimeNs + 2 * bucketSizeNs - 11)); + events.push_back(CreateScreenBrightnessChangedEvent( + 789, bucketStartTimeNs + 2 * bucketSizeNs - 9)); + events.push_back(CreateScreenBrightnessChangedEvent( + 789, bucketStartTimeNs + 2 * bucketSizeNs - 1)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + + EXPECT_EQ(countMetrics.data_size(), 7); + auto data = countMetrics.data(0); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs ); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + + data = countMetrics.data(1); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123); + ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111); + + data = countMetrics.data(2); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 3); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456); + ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111); + + data = countMetrics.data(3); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).count(), 2); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).count(), 1); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456); + ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 333); + + data = countMetrics.data(4); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 2); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + + data = countMetrics.data(5); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); + ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111); + + data = countMetrics.data(6); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); + ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 333); +} + +namespace { + +StatsdConfig CreateCountMetricWithLinkConfig() { + StatsdConfig config; + auto appCrashMatcher = CreateProcessCrashAtomMatcher(); + *config.add_atom_matcher() = appCrashMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + auto isSyncingPredicate = CreateIsSyncingPredicate(); + auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); + *syncDimension = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + syncDimension->add_child()->set_field(2 /* name field*/); + + *config.add_predicate() = screenIsOffPredicate; + *config.add_predicate() = isSyncingPredicate; + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(987654); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); + + auto metric = config.add_count_metric(); + metric->set_bucket(ONE_MINUTE); + metric->set_id(StringToId("AppCrashMetric")); + metric->set_what(appCrashMatcher.id()); + metric->set_condition(combinationPredicate->id()); + *metric->mutable_dimensions_in_what() = CreateDimensions( + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1 /* uid */}); + *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + + // Links between crash atom and condition of app is in syncing. + auto links = metric->add_links(); + links->set_condition(isSyncingPredicate.id()); + auto dimensionWhat = links->mutable_fields_in_what(); + dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + *links->mutable_fields_in_condition() = CreateAttributionUidDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + return config; +} + +} // namespace + +TEST(DimensionInConditionE2eTest, TestCountMetricWithLink) { + ConfigKey cfgKey; + auto config = CreateCountMetricWithLinkConfig(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + std::vector<AttributionNode> attributions1 = + {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector<AttributionNode> attributions2 = + {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector<std::unique_ptr<LogEvent>> events; + + events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 11)); + events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 101)); + events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 101)); + + events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 201)); + events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 211)); + events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 211)); + + events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 401)); + events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 401)); + events.push_back(CreateAppCrashEvent(555, bucketStartTimeNs + 401)); + + events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + bucketSizeNs + 301)); + events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + bucketSizeNs + 301)); + + events.push_back(CreateAppCrashEvent(777, bucketStartTimeNs + bucketSizeNs + 701)); + + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 700)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 300)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc", + bucketStartTimeNs + bucketSizeNs - 1)); + + events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 400)); + events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 600)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + + EXPECT_EQ(countMetrics.data_size(), 5); + auto data = countMetrics.data(0); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + + data = countMetrics.data(1); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1"); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 2); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + + data = countMetrics.data(2); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 222); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 2); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + + data = countMetrics.data(3); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2"); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).count(), 1); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + + data = countMetrics.data(4); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 777); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); +} + +namespace { + +StatsdConfig CreateDurationMetricConfigNoLink(DurationMetric::AggregationType aggregationType) { + StatsdConfig config; + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); + + auto inBatterySaverModePredicate = CreateBatterySaverModePredicate(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + auto isSyncingPredicate = CreateIsSyncingPredicate(); + auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); + *syncDimension = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + syncDimension->add_child()->set_field(2 /* name field */); + + *config.add_predicate() = inBatterySaverModePredicate; + *config.add_predicate() = screenIsOffPredicate; + *config.add_predicate() = isSyncingPredicate; + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(987654); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); + + auto metric = config.add_duration_metric(); + metric->set_bucket(ONE_MINUTE); + metric->set_id(StringToId("BatterySaverModeDurationMetric")); + metric->set_what(inBatterySaverModePredicate.id()); + metric->set_condition(combinationPredicate->id()); + *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + return config; +} + +} // namespace + + +TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink) { + for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { + ConfigKey cfgKey; + auto config = CreateDurationMetricConfigNoLink(aggregationType); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + std::vector<AttributionNode> attributions1 = + {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector<AttributionNode> attributions2 = + {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector<std::unique_ptr<LogEvent>> events; + + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 1)); + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 101)); + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 110)); + + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 201)); + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 500)); + + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 600)); + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 850)); + + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + bucketSizeNs + 870)); + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 900)); + + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 800)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 300)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc", + bucketStartTimeNs + bucketSizeNs - 1)); + + events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401)); + events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 700)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::DurationMetricDataWrapper metrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics); + + EXPECT_EQ(metrics.data_size(), 3); + auto data = metrics.data(0); + EXPECT_FALSE(data.dimensions_in_what().has_field()); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + + data = metrics.data(1); + EXPECT_FALSE(data.dimensions_in_what().has_field()); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1"); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + + data = metrics.data(2); + EXPECT_FALSE(data.dimensions_in_what().has_field()); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2"); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + } +} + +namespace { + +StatsdConfig CreateDurationMetricConfigWithLink(DurationMetric::AggregationType aggregationType) { + StatsdConfig config; + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + auto isSyncingPredicate = CreateIsSyncingPredicate(); + auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); + *syncDimension = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + syncDimension->add_child()->set_field(2 /* name field */); + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + + *config.add_predicate() = screenIsOffPredicate; + *config.add_predicate() = isSyncingPredicate; + *config.add_predicate() = isInBackgroundPredicate; + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(987654); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); + + auto metric = config.add_duration_metric(); + metric->set_bucket(ONE_MINUTE); + metric->set_id(StringToId("AppInBackgroundMetric")); + metric->set_what(isInBackgroundPredicate.id()); + metric->set_condition(combinationPredicate->id()); + *metric->mutable_dimensions_in_what() = CreateDimensions( + android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + + // Links between crash atom and condition of app is in syncing. + auto links = metric->add_links(); + links->set_condition(isSyncingPredicate.id()); + auto dimensionWhat = links->mutable_fields_in_what(); + dimensionWhat->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + *links->mutable_fields_in_condition() = CreateAttributionUidDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + return config; +} + +} // namespace + +TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink) { + for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { + ConfigKey cfgKey; + auto config = CreateDurationMetricConfigWithLink(aggregationType); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + std::vector<AttributionNode> attributions1 = + {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector<AttributionNode> attributions2 = + {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector<std::unique_ptr<LogEvent>> events; + + events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 101)); + events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + 110)); + + events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 201)); + events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + bucketSizeNs + 100)); + + events.push_back(CreateMoveToBackgroundEvent(333, bucketStartTimeNs + 399)); + events.push_back(CreateMoveToForegroundEvent(333, bucketStartTimeNs + bucketSizeNs + 800)); + + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 801)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 300)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc", + bucketStartTimeNs + bucketSizeNs - 1)); + + events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401)); + events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 700)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::DurationMetricDataWrapper metrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics); + + EXPECT_EQ(metrics.data_size(), 3); + auto data = metrics.data(0); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + + data = metrics.data(1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1"); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 201); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + + data = metrics.data(2); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2"); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 401); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + } +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp index 4504a95c8ef0..233031c5c8da 100644 --- a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp @@ -44,9 +44,10 @@ StatsdConfig CreateStatsdConfig() { auto screenIsOffPredicate = CreateScreenIsOffPredicate(); auto isSyncingPredicate = CreateIsSyncingPredicate(); - *isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions( - android::util::SYNC_STATE_CHANGED, {1 /* uid field */, 2 /* name field*/}); + auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); + *syncDimension = CreateAttributionUidDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + syncDimension->add_child()->set_field(2 /* name field*/); auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = @@ -78,9 +79,8 @@ StatsdConfig CreateStatsdConfig() { auto dimensionWhat = links->mutable_fields_in_what(); dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); dimensionWhat->add_child()->set_field(1); // uid field. - auto dimensionCondition = links->mutable_fields_in_condition(); - dimensionCondition->set_field(android::util::SYNC_STATE_CHANGED); - dimensionCondition->add_child()->set_field(1); // uid field. + *links->mutable_fields_in_condition() = CreateAttributionUidDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); // Links between crash atom and condition of app is in background. links = countMetric->add_links(); @@ -88,7 +88,7 @@ StatsdConfig CreateStatsdConfig() { dimensionWhat = links->mutable_fields_in_what(); dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); dimensionWhat->add_child()->set_field(1); // uid field. - dimensionCondition = links->mutable_fields_in_condition(); + auto dimensionCondition = links->mutable_fields_in_condition(); dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); dimensionCondition->add_child()->set_field(1); // uid field. return config; @@ -132,12 +132,14 @@ TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks) { CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 2 * bucketSizeNs - 100); + std::vector<AttributionNode> attributions = + {CreateAttribution(appUid, "App1"), CreateAttribution(appUid + 1, "GMSCoreModule1")}; auto syncOnEvent1 = - CreateSyncStartEvent(appUid, "ReadEmail", bucketStartTimeNs + 50); + CreateSyncStartEvent(attributions, "ReadEmail", bucketStartTimeNs + 50); auto syncOffEvent1 = - CreateSyncEndEvent(appUid, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300); + CreateSyncEndEvent(attributions, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300); auto syncOnEvent2 = - CreateSyncStartEvent(appUid, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000); + CreateSyncStartEvent(attributions, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000); auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15); diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index 4ad209712905..50b3532e827a 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -67,9 +67,9 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { // Flushes. countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); - const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets.size()); EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); @@ -80,10 +80,10 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); countProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); - EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY].size()); - const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY][1]; + EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1]; EXPECT_EQ(bucket2StartTimeNs, bucketInfo2.mBucketStartNs); EXPECT_EQ(bucket2StartTimeNs + bucketSizeNs, bucketInfo2.mBucketEndNs); EXPECT_EQ(1LL, bucketInfo2.mCount); @@ -91,9 +91,9 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { // nothing happens in bucket 3. we should not record anything for bucket 3. countProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1); EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); - const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(2UL, buckets3.size()); } @@ -124,10 +124,10 @@ TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); { - const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets.size()); const auto& bucketInfo = buckets[0]; EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); @@ -167,9 +167,9 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { {getMockedDimensionKey(conditionTagId, 2, "222")}; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse)); + EXPECT_CALL(*wizard, query(_, key1, _, _)).WillOnce(Return(ConditionState::kFalse)); - EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue)); + EXPECT_CALL(*wizard, query(_, key2, _, _)).WillOnce(Return(ConditionState::kTrue)); CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard, bucketStartTimeNs); @@ -181,9 +181,9 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); - const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets.size()); const auto& bucketInfo = buckets[0]; EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); @@ -229,13 +229,13 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // One event in bucket #2. No alarm as bucket #0 is trashed out. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // Two events in bucket #3. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); @@ -244,13 +244,13 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second); // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6 - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event5.GetTimestampNs() / NS_PER_SEC + refPeriodSec); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7); EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event7.GetTimestampNs() / NS_PER_SEC + refPeriodSec); } diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp index a59f1fe66354..c9fe2523c577 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -62,9 +62,9 @@ TEST(DurationMetricTrackerTest, TestNoCondition) { durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); EXPECT_EQ(1UL, durationProducer.mPastBuckets.size()); - EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != durationProducer.mPastBuckets.end()); - const auto& buckets = durationProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(2UL, buckets.size()); EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); @@ -107,9 +107,9 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { durationProducer.onMatchedLogEvent(2 /* stop index*/, event4); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); EXPECT_EQ(1UL, durationProducer.mPastBuckets.size()); - EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != durationProducer.mPastBuckets.end()); - const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets2.size()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs); diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp index da00cae125c7..3deab3710dd1 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -114,9 +114,9 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")}; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse)); + EXPECT_CALL(*wizard, query(_, key1, _, _)).WillOnce(Return(ConditionState::kFalse)); - EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue)); + EXPECT_CALL(*wizard, query(_, key2, _, _)).WillOnce(Return(ConditionState::kTrue)); EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs); diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 4533ac610057..58be5b071b53 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -218,7 +218,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()-> second.front().mFields->begin()->second.value_int()); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 20); @@ -231,7 +231,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()-> second.front().mFields->begin()->second.value_int()); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec); std::shared_ptr<LogEvent> event3 = @@ -245,7 +245,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { EXPECT_EQ(26L, gaugeProducer.mCurrentSlicedBucket->begin()-> second.front().mFields->begin()->second.value_int()); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec); // The event4 does not have the gauge field. Thus the current bucket value is 0. diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp index 0772b0d4001d..203f028080d2 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -41,22 +41,24 @@ const ConfigKey kConfigKey(0, 12345); const int TagId = 1; -const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1"); -const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; -const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); -const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, - bucketSizeNs, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey()); // Event starts again. This would not change anything as it already starts. @@ -75,16 +77,22 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { } TEST(MaxDurationTrackerTest, TestStopAll) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, - bucketSizeNs, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey()); @@ -105,21 +113,26 @@ TEST(MaxDurationTrackerTest, TestStopAll) { } TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, - bucketSizeNs, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, false, {}); // The event starts. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); - // Starts again. Does not change anything. + // Starts again. Does not DEFAULT_DIMENSION_KEY anything. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1, ConditionKey()); @@ -135,16 +148,21 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { } TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, - bucketSizeNs, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, false, {}); // 2 starts tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); @@ -160,7 +178,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration); // real stop now. - tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (2 * bucketSizeNs) + 5, false); + tracker.noteStop(DEFAULT_DIMENSION_KEY, + bucketStartTimeNs + (2 * bucketSizeNs) + 5, false); tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets); EXPECT_EQ(3u, buckets[eventKey].size()); @@ -170,16 +189,20 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { } TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { + const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; - HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 2, "maps"); + MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps"); conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; - EXPECT_CALL(*wizard, query(_, conditionKey1)) // #4 + EXPECT_CALL(*wizard, query(_, conditionKey1, _, _)) // #4 .WillOnce(Return(ConditionState::kFalse)); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; @@ -187,8 +210,8 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { int64_t durationTimeNs = 2 * 1000; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, - bucketSizeNs, true, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, true, {}); EXPECT_TRUE(tracker.mAnomalyTrackers.empty()); tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1); @@ -204,6 +227,10 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { } TEST(MaxDurationTrackerTest, TestAnomalyDetection) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + FieldMatcher dimensionInCondition; int64_t metricId = 1; Alert alert; alert.set_id(101); @@ -213,7 +240,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { const int32_t refPeriodSec = 1; alert.set_refractory_period_secs(refPeriodSec); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; @@ -221,8 +248,8 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { uint64_t bucketSizeNs = 30 * NS_PER_SEC; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, - bucketSizeNs, false, {anomalyTracker}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker}); tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey()); tracker.noteStop(key1, eventStartTimeNs + 10, false); diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp index 6b8893e5973f..80e16a1f3b55 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -38,24 +38,26 @@ namespace statsd { const ConfigKey kConfigKey(0, 12345); const int TagId = 1; const int64_t metricId = 123; -const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event"); - -const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")}; -const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); -const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); TEST(OringDurationTrackerTest, TestDurationOverlap) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector<HashableDimensionKey> kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, - bucketStartTimeNs, bucketSizeNs, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -71,16 +73,23 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { } TEST(OringDurationTrackerTest, TestDurationNested) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector<HashableDimensionKey> kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketSizeNs, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -95,16 +104,23 @@ TEST(OringDurationTrackerTest, TestDurationNested) { } TEST(OringDurationTrackerTest, TestStopAll) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector<HashableDimensionKey> kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketSizeNs, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -118,17 +134,24 @@ TEST(OringDurationTrackerTest, TestStopAll) { } TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector<HashableDimensionKey> kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketSizeNs, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -150,23 +173,30 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { } TEST(OringDurationTrackerTest, TestDurationConditionChange) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector<HashableDimensionKey> kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1)) // #4 + EXPECT_CALL(*wizard, query(_, key1, _, _)) // #4 .WillOnce(Return(ConditionState::kFalse)); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, - bucketStartTimeNs, bucketSizeNs, true, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, true, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -181,25 +211,32 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { } TEST(OringDurationTrackerTest, TestDurationConditionChange2) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector<HashableDimensionKey> kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1)) + EXPECT_CALL(*wizard, query(_, key1, _, _)) .Times(2) .WillOnce(Return(ConditionState::kFalse)) .WillOnce(Return(ConditionState::kTrue)); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, - bucketStartTimeNs, bucketSizeNs, true, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, true, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); // condition to false; record duration 5n @@ -216,22 +253,29 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { } TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector<HashableDimensionKey> kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1)) // #4 + EXPECT_CALL(*wizard, query(_, key1, _, _)) // #4 .WillOnce(Return(ConditionState::kFalse)); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketSizeNs, true, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, true, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1); @@ -249,6 +293,13 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { } TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector<HashableDimensionKey> kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -256,7 +307,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { alert.set_num_buckets(2); alert.set_refractory_period_secs(1); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; @@ -264,8 +315,8 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { uint64_t bucketSizeNs = 30 * NS_PER_SEC; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketSizeNs, true, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, true, {anomalyTracker}); // Nothing in the past bucket. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); @@ -310,6 +361,12 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { } TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -318,7 +375,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { const int32_t refPeriodSec = 45; alert.set_refractory_period_secs(refPeriodSec); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; @@ -326,8 +383,8 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { uint64_t bucketSizeNs = 30 * NS_PER_SEC; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, - bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false); @@ -352,6 +409,13 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { } TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector<HashableDimensionKey> kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -360,7 +424,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { const int32_t refPeriodSec = 45; alert.set_refractory_period_secs(refPeriodSec); - unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; + unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conkey; conkey[StringToId("APP_BACKGROUND")] = kConditionKey1; @@ -369,8 +433,9 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { uint64_t bucketSizeNs = 30 * NS_PER_SEC; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, - bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false, + {anomalyTracker}); tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1 EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index fff3dbf5428d..55c078dcbab8 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -299,26 +299,26 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); // Value sum == 30 <= 130. - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // One event in bucket #2. No alarm as bucket #0 is trashed out. valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); // Value sum == 130 <= 130. - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // Three events in bucket #3. valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); // Anomaly at event 4 since Value sum == 131 > 130! - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event4->GetTimestampNs() / NS_PER_SEC + refPeriodSec); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event5); // Event 5 is within 3 sec refractory period. Thus last alarm timestamp is still event4. - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event4->GetTimestampNs() / NS_PER_SEC + refPeriodSec); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event6); // Anomaly at event 6 since Value sum == 160 > 130 and after refractory period. - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event6->GetTimestampNs() / NS_PER_SEC + refPeriodSec); } diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.cpp b/cmds/statsd/tests/metrics/metrics_test_helper.cpp index fc7245ca54e3..ab9345af172e 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.cpp +++ b/cmds/statsd/tests/metrics/metrics_test_helper.cpp @@ -26,6 +26,13 @@ HashableDimensionKey getMockedDimensionKey(int tagId, int key, string value) { return HashableDimensionKey(dimensionsValue); } +MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, string value) { + DimensionsValue dimensionsValue; + dimensionsValue.set_field(tagId); + dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field(key); + dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)->set_value_str(value); + return MetricDimensionKey(HashableDimensionKey(dimensionsValue), DEFAULT_DIMENSION_KEY); +} } // namespace statsd } // namespace os } // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h index 23e86f92f844..0a97456c5684 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.h +++ b/cmds/statsd/tests/metrics/metrics_test_helper.h @@ -25,10 +25,12 @@ namespace statsd { class MockConditionWizard : public ConditionWizard { public: - MOCK_METHOD2( + MOCK_METHOD4( query, ConditionState(const int conditionIndex, - const ConditionKey& conditionParameters)); + const ConditionKey& conditionParameters, + const FieldMatcher& dimensionFields, + std::unordered_set<HashableDimensionKey> *dimensionKeySet)); }; class MockStatsPullerManager : public StatsPullerManager { @@ -39,6 +41,7 @@ public: }; HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value); +MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, std::string value); } // namespace statsd } // namespace os diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 9f4582dc6994..13055cb9e7a3 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <gtest/gtest.h> #include "statsd_test_util.h" namespace android { @@ -27,6 +26,22 @@ AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { return atom_matcher; } +AtomMatcher CreateScreenBrightnessChangedAtomMatcher() { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId("ScreenBrightnessChanged")); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::SCREEN_BRIGHTNESS_CHANGED); + return atom_matcher; +} + +AtomMatcher CreateUidProcessStateChangedAtomMatcher() { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId("UidProcessStateChanged")); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::UID_PROCESS_STATE_CHANGED); + return atom_matcher; +} + AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name, WakelockStateChanged::State state) { AtomMatcher atom_matcher; @@ -47,6 +62,30 @@ AtomMatcher CreateReleaseWakelockAtomMatcher() { return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE); } +AtomMatcher CreateBatterySaverModeStateChangedAtomMatcher( + const string& name, BatterySaverModeStateChanged::State state) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::BATTERY_SAVER_MODE_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(1); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateBatterySaverModeStartAtomMatcher() { + return CreateBatterySaverModeStateChangedAtomMatcher( + "BatterySaverModeStart", BatterySaverModeStateChanged::ON); +} + + +AtomMatcher CreateBatterySaverModeStopAtomMatcher() { + return CreateBatterySaverModeStateChangedAtomMatcher( + "BatterySaverModeStop", BatterySaverModeStateChanged::OFF); +} + + AtomMatcher CreateScreenStateChangedAtomMatcher( const string& name, android::view::DisplayStateEnum state) { AtomMatcher atom_matcher; @@ -59,6 +98,7 @@ AtomMatcher CreateScreenStateChangedAtomMatcher( return atom_matcher; } + AtomMatcher CreateScreenTurnedOnAtomMatcher() { return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", android::view::DisplayStateEnum::DISPLAY_STATE_ON); @@ -128,6 +168,13 @@ AtomMatcher CreateProcessCrashAtomMatcher() { "ProcessCrashed", ProcessLifeCycleStateChanged::PROCESS_CRASHED); } +Predicate CreateBatterySaverModePredicate() { + Predicate predicate; + predicate.set_id(StringToId("BatterySaverIsOn")); + predicate.mutable_simple_predicate()->set_start(StringToId("BatterySaverModeStart")); + predicate.mutable_simple_predicate()->set_stop(StringToId("BatterySaverModeStop")); + return predicate; +} Predicate CreateScreenIsOnPredicate() { Predicate predicate; @@ -218,6 +265,31 @@ std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( return event; } +std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs) { + auto event = std::make_unique<LogEvent>( + android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs); + EXPECT_TRUE(event->write(BatterySaverModeStateChanged::ON)); + event->init(); + return event; +} + +std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs) { + auto event = std::make_unique<LogEvent>( + android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs); + EXPECT_TRUE(event->write(BatterySaverModeStateChanged::OFF)); + event->init(); + return event; +} + +std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent( + int level, uint64_t timestampNs) { + auto event = std::make_unique<LogEvent>(android::util::SCREEN_BRIGHTNESS_CHANGED, timestampNs); + EXPECT_TRUE(event->write(level)); + event->init(); + return event; + +} + std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent( const std::vector<AttributionNode>& attributions, const string& wakelockName, const WakelockStateChanged::State state, uint64_t timestampNs) { @@ -267,9 +339,10 @@ std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t ti } std::unique_ptr<LogEvent> CreateSyncStateChangedEvent( - const int uid, const string& name, const SyncStateChanged::State state, uint64_t timestampNs) { + const std::vector<AttributionNode>& attributions, + const string& name, const SyncStateChanged::State state, uint64_t timestampNs) { auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs); - event->write(uid); + event->write(attributions); event->write(name); event->write(state); event->init(); @@ -277,13 +350,13 @@ std::unique_ptr<LogEvent> CreateSyncStateChangedEvent( } std::unique_ptr<LogEvent> CreateSyncStartEvent( - const int uid, const string& name, uint64_t timestampNs){ - return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::ON, timestampNs); + const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs){ + return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::ON, timestampNs); } std::unique_ptr<LogEvent> CreateSyncEndEvent( - const int uid, const string& name, uint64_t timestampNs) { - return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::OFF, timestampNs); + const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs) { + return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::OFF, timestampNs); } std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent( diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index ff8fef0c46b6..6638893f6aeb 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -14,6 +14,7 @@ #pragma once +#include <gtest/gtest.h> #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "statslog.h" #include "src/logd/LogEvent.h" @@ -26,6 +27,18 @@ namespace statsd { // Create AtomMatcher proto to simply match a specific atom type. AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId); +// Create AtomMatcher proto for screen brightness state changed. +AtomMatcher CreateScreenBrightnessChangedAtomMatcher(); + +// Create AtomMatcher proto for starting battery save mode. +AtomMatcher CreateBatterySaverModeStartAtomMatcher(); + +// Create AtomMatcher proto for stopping battery save mode. +AtomMatcher CreateBatterySaverModeStopAtomMatcher(); + +// Create AtomMatcher proto for process state changed. +AtomMatcher CreateUidProcessStateChangedAtomMatcher(); + // Create AtomMatcher proto for acquiring wakelock. AtomMatcher CreateAcquireWakelockAtomMatcher(); @@ -59,6 +72,9 @@ Predicate CreateScreenIsOnPredicate(); // Create Predicate proto for screen is off. Predicate CreateScreenIsOffPredicate(); +// Create Predicate proto for battery saver mode. +Predicate CreateBatterySaverModePredicate(); + // Create Predicate proto for holding wakelock. Predicate CreateHoldingWakelockPredicate(); @@ -86,6 +102,15 @@ FieldMatcher CreateAttributionUidDimensions(const int atomId, std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( const android::view::DisplayStateEnum state, uint64_t timestampNs); +// Create log event for screen brightness state changed. +std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent( + int level, uint64_t timestampNs); + +// Create log event when battery saver starts. +std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs); +// Create log event when battery saver stops. +std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs); + // Create log event for app moving to background. std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs); @@ -94,11 +119,11 @@ std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t ti // Create log event when the app sync starts. std::unique_ptr<LogEvent> CreateSyncStartEvent( - const int uid, const string& name, uint64_t timestampNs); + const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs); // Create log event when the app sync ends. std::unique_ptr<LogEvent> CreateSyncEndEvent( - const int uid, const string& name, uint64_t timestampNs); + const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs); // Create log event when the app sync ends. std::unique_ptr<LogEvent> CreateAppCrashEvent( @@ -136,9 +161,12 @@ void ValidateAttributionUidAndTagDimension( template <typename T> void sortMetricDataByDimensionsValue(const T& metricData, T* sortedMetricData) { - std::map<HashableDimensionKey, int> dimensionIndexMap; + std::map<MetricDimensionKey, int> dimensionIndexMap; for (int i = 0; i < metricData.data_size(); ++i) { - dimensionIndexMap.insert(std::make_pair(metricData.data(i).dimensions_in_what(), i)); + dimensionIndexMap.insert(std::make_pair( + MetricDimensionKey(HashableDimensionKey(metricData.data(i).dimensions_in_what()), + HashableDimensionKey(metricData.data(i).dimensions_in_condition())), + i)); } for (const auto& itr : dimensionIndexMap) { *sortedMetricData->add_data() = metricData.data(itr.second); diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java index d39aa1d314e6..57575ae145f6 100644 --- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java @@ -116,28 +116,32 @@ public class MainActivity extends Activity { findViewById(R.id.plug).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 1); + StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, + StatsLog.PLUGGED_STATE_CHANGED__STATE__BATTERY_PLUGGED_AC); } }); findViewById(R.id.unplug).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 0); + StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, + StatsLog.PLUGGED_STATE_CHANGED__STATE__BATTERY_PLUGGED_NONE); } }); findViewById(R.id.screen_on).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 2); + StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, + StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_ON); } }); findViewById(R.id.screen_off).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 1); + StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, + StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_OFF); } }); @@ -255,7 +259,9 @@ public class MainActivity extends Activity { } int[] uids = new int[] {mUids[id]}; String[] tags = new String[] {"acquire"}; - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, 0, name, 1); + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, + StatsLog.WAKELOCK_STATE_CHANGED__LEVEL__PARTIAL_WAKE_LOCK, name, + StatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE); StringBuilder sb = new StringBuilder(); sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) .append(", ").append(name).append(", 1);"); @@ -269,7 +275,9 @@ public class MainActivity extends Activity { } int[] uids = new int[] {mUids[id]}; String[] tags = new String[] {"release"}; - StatsLog.write(10, uids, tags, 0, name, 0); + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, + StatsLog.WAKELOCK_STATE_CHANGED__LEVEL__PARTIAL_WAKE_LOCK, name, + StatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE); StringBuilder sb = new StringBuilder(); sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) .append(", ").append(name).append(", 0);"); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index fdb0ac9d9a98..04c44a3818a3 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -425,6 +425,12 @@ import java.util.List; * safely called after {@link #onPause()} and allows and application to safely * wait until {@link #onStop()} to save persistent state.</p> * + * <p class="note">For applications targeting platforms starting with + * {@link android.os.Build.VERSION_CODES#P} {@link #onSaveInstanceState(Bundle)} + * will always be called after {@link #onStop}, so an application may safely + * perform fragment transactions in {@link #onStop} and will be able to save + * persistent state later.</p> + * * <p>For those methods that are not marked as being killable, the activity's * process will not be killed by the system starting from the time the method * is called and continuing after it returns. Thus an activity is in the killable @@ -1577,8 +1583,11 @@ public class Activity extends ContextThemeWrapper * call through to the default implementation, otherwise be prepared to save * all of the state of each view yourself. * - * <p>If called, this method will occur before {@link #onStop}. There are - * no guarantees about whether it will occur before or after {@link #onPause}. + * <p>If called, this method will occur after {@link #onStop} for applications + * targeting platforms starting with {@link android.os.Build.VERSION_CODES#P}. + * For applications targeting earlier platform versions this method will occur + * before {@link #onStop} and there are no guarantees about whether it will + * occur before or after {@link #onPause}. * * @param outState Bundle in which to place your saved state. * diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 29e091b0f26b..42825f0ad8d4 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -488,12 +488,14 @@ public final class ActivityThread extends ClientTransactionHandler { } } - public boolean isPreHoneycomb() { - if (activity != null) { - return activity.getApplicationInfo().targetSdkVersion - < android.os.Build.VERSION_CODES.HONEYCOMB; - } - return false; + private boolean isPreHoneycomb() { + return activity != null && activity.getApplicationInfo().targetSdkVersion + < android.os.Build.VERSION_CODES.HONEYCOMB; + } + + private boolean isPreP() { + return activity != null && activity.getApplicationInfo().targetSdkVersion + < android.os.Build.VERSION_CODES.P; } public boolean isPersistable() { @@ -4165,9 +4167,12 @@ public final class ActivityThread extends ClientTransactionHandler { * {@link Activity#onSaveInstanceState(Bundle)} is also executed in the same call. */ private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) { + // Before P onSaveInstanceState was called before onStop, starting with P it's + // called after. Before Honeycomb state was always saved before onPause. final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null && !r.isPreHoneycomb(); - if (shouldSaveState) { + final boolean isPreP = r.isPreP(); + if (shouldSaveState && isPreP) { callActivityOnSaveInstanceState(r); } @@ -4186,6 +4191,10 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_STOP); EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), r.activity.getComponentName().getClassName(), reason); + + if (shouldSaveState && !isPreP) { + callActivityOnSaveInstanceState(r); + } } private void updateVisibility(ActivityClientRecord r, boolean show) { diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index f21746cdd275..39bccc37d2b1 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -51,6 +51,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.DeadSystemException; +import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -1329,11 +1330,7 @@ public class WallpaperManager { private void copyStreamToWallpaperFile(InputStream data, FileOutputStream fos) throws IOException { - byte[] buffer = new byte[32768]; - int amt; - while ((amt=data.read(buffer)) > 0) { - fos.write(buffer, 0, amt); - } + FileUtils.copy(data, fos); } /** diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java new file mode 100644 index 000000000000..c811999ce304 --- /dev/null +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.biometrics; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.os.CancellationSignal; +import android.os.Parcelable; + +import java.util.concurrent.Executor; + +/** + * This is the common interface that all biometric authentication classes should implement. + * @hide + */ +public interface BiometricAuthenticator { + + /** + * Container for biometric data + * @hide + */ + abstract class BiometricIdentifier implements Parcelable {} + + /** + * Container for callback data from {@link BiometricAuthenticator#authenticate( + * CancellationSignal, Executor, AuthenticationCallback)} and + * {@link BiometricAuthenticator#authenticate(CryptoObject, CancellationSignal, Executor, + * AuthenticationCallback)} + */ + class AuthenticationResult { + private BiometricIdentifier mIdentifier; + private CryptoObject mCryptoObject; + private int mUserId; + + /** + * @hide + */ + public AuthenticationResult() { } + + /** + * Authentication result + * @param crypto + * @param identifier + * @param userId + * @hide + */ + public AuthenticationResult(CryptoObject crypto, BiometricIdentifier identifier, + int userId) { + mCryptoObject = crypto; + mIdentifier = identifier; + mUserId = userId; + } + + /** + * Obtain the crypto object associated with this transaction + * @return crypto object provided to {@link BiometricAuthenticator#authenticate( + * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)} + */ + public CryptoObject getCryptoObject() { + return mCryptoObject; + } + + /** + * Obtain the biometric identifier associated with this operation. Applications are strongly + * discouraged from associating specific identifiers with specific applications or + * operations. + * @hide + */ + public BiometricIdentifier getId() { + return mIdentifier; + } + + /** + * Obtain the userId for which this biometric was authenticated. + * @hide + */ + public int getUserId() { + return mUserId; + } + }; + + /** + * Callback structure provided to {@link BiometricAuthenticator#authenticate(CancellationSignal, + * Executor, AuthenticationCallback)} or {@link BiometricAuthenticator#authenticate( + * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)}. Users must provide + * an implementation of this for listening to biometric events. + */ + abstract class AuthenticationCallback { + /** + * Called when an unrecoverable error has been encountered and the operation is complete. + * No further actions will be made on this object. + * @param errorCode An integer identifying the error message + * @param errString A human-readable error string that can be shown on an UI + */ + public void onAuthenticationError(int errorCode, CharSequence errString) {} + + /** + * Called when a recoverable error has been encountered during authentication. The help + * string is provided to give the user guidance for what went wrong, such as "Sensor dirty, + * please clean it." + * @param helpCode An integer identifying the error message + * @param helpString A human-readable string that can be shown on an UI + */ + public void onAuthenticationHelp(int helpCode, CharSequence helpString) {} + + /** + * Called when a biometric is recognized. + * @param result An object containing authentication-related data + */ + public void onAuthenticationSucceeded(AuthenticationResult result) {} + + /** + * Called when a biometric is valid but not recognized. + */ + public void onAuthenticationFailed() {} + + /** + * Called when a biometric has been acquired, but hasn't been processed yet. + * @hide + */ + public void onAuthenticationAcquired(int acquireInfo) {} + }; + + /** + * This call warms up the hardware and starts scanning for valid biometrics. It terminates + * when {@link AuthenticationCallback#onAuthenticationError(int, + * CharSequence)} is called or when {@link AuthenticationCallback#onAuthenticationSucceeded( + * AuthenticationResult)} is called, at which point the crypto object becomes invalid. This + * operation can be canceled by using the provided cancel object. The application wil receive + * authentication errors through {@link AuthenticationCallback}. Calling + * {@link BiometricAuthenticator#authenticate(CryptoObject, CancellationSignal, Executor, + * AuthenticationCallback)} while an existing authentication attempt is occurring will stop + * the previous client and start a new authentication. The interrupted client will receive a + * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int, + * CharSequence)}. + * + * @throws IllegalArgumentException If any of the arguments are null + * + * @param crypto Object associated with the call + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events + */ + void authenticate(@NonNull CryptoObject crypto, + @NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull AuthenticationCallback callback); + + /** + * This call warms up the hardware and starts scanning for valid biometrics. It terminates + * when {@link AuthenticationCallback#onAuthenticationError(int, + * CharSequence)} is called or when {@link AuthenticationCallback#onAuthenticationSucceeded( + * AuthenticationResult)} is called. This operation can be canceled by using the provided cancel + * object. The application wil receive authentication errors through + * {@link AuthenticationCallback}. Calling {@link BiometricAuthenticator#authenticate( + * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)} while an existing + * authentication attempt is occurring will stop the previous client and start a new + * authentication. The interrupted client will receive a cancelled notification through + * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}. + * + * @throws IllegalArgumentException If any of the arguments are null + * + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events + */ + void authenticate(@NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull AuthenticationCallback callback); +} diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java new file mode 100644 index 000000000000..638f525bfb10 --- /dev/null +++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.biometrics; + +import android.hardware.fingerprint.FingerprintManager; + +/** + * Interface containing all of the fingerprint-specific constants. + * @hide + */ +public interface BiometricFingerprintConstants { + // + // Error messages from fingerprint hardware during initilization, enrollment, authentication or + // removal. Must agree with the list in fingerprint.h + // + + /** + * The hardware is unavailable. Try again later. + */ + public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; + + /** + * Error state returned when the sensor was unable to process the current image. + */ + public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; + + /** + * Error state returned when the current request has been running too long. This is intended to + * prevent programs from waiting for the fingerprint sensor indefinitely. The timeout is + * platform and sensor-specific, but is generally on the order of 30 seconds. + */ + public static final int FINGERPRINT_ERROR_TIMEOUT = 3; + + /** + * Error state returned for operations like enrollment; the operation cannot be completed + * because there's not enough storage remaining to complete the operation. + */ + public static final int FINGERPRINT_ERROR_NO_SPACE = 4; + + /** + * The operation was canceled because the fingerprint sensor is unavailable. For example, + * this may happen when the user is switched, the device is locked or another pending operation + * prevents or disables it. + */ + public static final int FINGERPRINT_ERROR_CANCELED = 5; + + /** + * The {@link FingerprintManager#remove} call failed. Typically this will happen when the + * provided fingerprint id was incorrect. + * + * @hide + */ + public static final int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6; + + /** + * The operation was canceled because the API is locked out due to too many attempts. + * This occurs after 5 failed attempts, and lasts for 30 seconds. + */ + public static final int FINGERPRINT_ERROR_LOCKOUT = 7; + + /** + * Hardware vendors may extend this list if there are conditions that do not fall under one of + * the above categories. Vendors are responsible for providing error strings for these errors. + * These messages are typically reserved for internal operations such as enrollment, but may be + * used to express vendor errors not covered by the ones in fingerprint.h. Applications are + * expected to show the error message string if they happen, but are advised not to rely on the + * message id since they will be device and vendor-specific + */ + public static final int FINGERPRINT_ERROR_VENDOR = 8; + + /** + * The operation was canceled because FINGERPRINT_ERROR_LOCKOUT occurred too many times. + * Fingerprint authentication is disabled until the user unlocks with strong authentication + * (PIN/Pattern/Password) + */ + public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; + + /** + * The user canceled the operation. Upon receiving this, applications should use alternate + * authentication (e.g. a password). The application should also provide the means to return + * to fingerprint authentication, such as a "use fingerprint" button. + */ + public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; + + /** + * The user does not have any fingerprints enrolled. + */ + public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; + + /** + * The device does not have a fingerprint sensor. + */ + public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; + + /** + * @hide + */ + public static final int FINGERPRINT_ERROR_VENDOR_BASE = 1000; + + // + // Image acquisition messages. Must agree with those in fingerprint.h + // + + /** + * The image acquired was good. + */ + public static final int FINGERPRINT_ACQUIRED_GOOD = 0; + + /** + * Only a partial fingerprint image was detected. During enrollment, the user should be + * informed on what needs to happen to resolve this problem, e.g. "press firmly on sensor." + */ + public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; + + /** + * The fingerprint image was too noisy to process due to a detected condition (i.e. dry skin) or + * a possibly dirty sensor (See {@link #FINGERPRINT_ACQUIRED_IMAGER_DIRTY}). + */ + public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; + + /** + * The fingerprint image was too noisy due to suspected or detected dirt on the sensor. + * For example, it's reasonable return this after multiple + * {@link #FINGERPRINT_ACQUIRED_INSUFFICIENT} or actual detection of dirt on the sensor + * (stuck pixels, swaths, etc.). The user is expected to take action to clean the sensor + * when this is returned. + */ + public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; + + /** + * The fingerprint image was unreadable due to lack of motion. This is most appropriate for + * linear array sensors that require a swipe motion. + */ + public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; + + /** + * The fingerprint image was incomplete due to quick motion. While mostly appropriate for + * linear array sensors, this could also happen if the finger was moved during acquisition. + * The user should be asked to move the finger slower (linear) or leave the finger on the sensor + * longer. + */ + public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; + + /** + * Hardware vendors may extend this list if there are conditions that do not fall under one of + * the above categories. Vendors are responsible for providing error strings for these errors. + * @hide + */ + public static final int FINGERPRINT_ACQUIRED_VENDOR = 6; + /** + * @hide + */ + public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; +} diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java new file mode 100644 index 000000000000..496d9c57f252 --- /dev/null +++ b/core/java/android/hardware/biometrics/CryptoObject.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.biometrics; + +import android.annotation.NonNull; +import android.security.keystore.AndroidKeyStoreProvider; + +import java.security.Signature; + +import javax.crypto.Cipher; +import javax.crypto.Mac; + +/** + * A wrapper class for the crypto objects supported by FingerprintManager. Currently the + * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects. + * @hide + */ +public class CryptoObject { + private final Object mCrypto; + + public CryptoObject(@NonNull Signature signature) { + mCrypto = signature; + } + + public CryptoObject(@NonNull Cipher cipher) { + mCrypto = cipher; + } + + public CryptoObject(@NonNull Mac mac) { + mCrypto = mac; + } + + /** + * Get {@link Signature} object. + * @return {@link Signature} object or null if this doesn't contain one. + */ + public Signature getSignature() { + return mCrypto instanceof Signature ? (Signature) mCrypto : null; + } + + /** + * Get {@link Cipher} object. + * @return {@link Cipher} object or null if this doesn't contain one. + */ + public Cipher getCipher() { + return mCrypto instanceof Cipher ? (Cipher) mCrypto : null; + } + + /** + * Get {@link Mac} object. + * @return {@link Mac} object or null if this doesn't contain one. + */ + public Mac getMac() { + return mCrypto instanceof Mac ? (Mac) mCrypto : null; + } + + /** + * @hide + * @return the opId associated with this object or 0 if none + */ + public final long getOpId() { + return mCrypto != null + ? AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto) : 0; + } +}; diff --git a/core/java/android/hardware/display/AmbientBrightnessDayStats.java b/core/java/android/hardware/display/AmbientBrightnessDayStats.java index 41be397cabc6..00f3c36d0361 100644 --- a/core/java/android/hardware/display/AmbientBrightnessDayStats.java +++ b/core/java/android/hardware/display/AmbientBrightnessDayStats.java @@ -17,6 +17,8 @@ package android.hardware.display; import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -28,11 +30,12 @@ import java.util.Arrays; /** * AmbientBrightnessDayStats stores and manipulates brightness stats over a single day. * {@see DisplayManager.getAmbientBrightnessStats()} - * TODO: Make this system API * * @hide */ -public class AmbientBrightnessDayStats implements Parcelable { +@SystemApi +@TestApi +public final class AmbientBrightnessDayStats implements Parcelable { /** The localdate for which brightness stats are being tracked */ private final LocalDate mLocalDate; @@ -48,30 +51,30 @@ public class AmbientBrightnessDayStats implements Parcelable { */ public AmbientBrightnessDayStats(@NonNull LocalDate localDate, @NonNull float[] bucketBoundaries) { - Preconditions.checkNotNull(localDate); - Preconditions.checkNotNull(bucketBoundaries); - int numBuckets = bucketBoundaries.length; - if (numBuckets < 1) { - throw new IllegalArgumentException("Bucket boundaries must contain at least 1 value"); - } - mLocalDate = localDate; - mBucketBoundaries = bucketBoundaries; - mStats = new float[numBuckets]; + this(localDate, bucketBoundaries, null); } /** * @hide */ public AmbientBrightnessDayStats(@NonNull LocalDate localDate, - @NonNull float[] bucketBoundaries, @NonNull float[] stats) { + @NonNull float[] bucketBoundaries, float[] stats) { Preconditions.checkNotNull(localDate); Preconditions.checkNotNull(bucketBoundaries); - Preconditions.checkNotNull(stats); + Preconditions.checkArrayElementsInRange(bucketBoundaries, 0, Float.MAX_VALUE, + "bucketBoundaries"); if (bucketBoundaries.length < 1) { throw new IllegalArgumentException("Bucket boundaries must contain at least 1 value"); } - if (bucketBoundaries.length != stats.length) { - throw new IllegalArgumentException("Bucket boundaries and stats must be of same size."); + checkSorted(bucketBoundaries); + if (stats == null) { + stats = new float[bucketBoundaries.length]; + } else { + Preconditions.checkArrayElementsInRange(stats, 0, Float.MAX_VALUE, "stats"); + if (bucketBoundaries.length != stats.length) { + throw new IllegalArgumentException( + "Bucket boundaries and stats must be of same size."); + } } mLocalDate = localDate; mBucketBoundaries = bucketBoundaries; @@ -193,4 +196,16 @@ public class AmbientBrightnessDayStats implements Parcelable { } return low; } + + private static void checkSorted(float[] values) { + if (values.length <= 1) { + return; + } + float prevValue = values[0]; + for (int i = 1; i < values.length; i++) { + Preconditions.checkState(prevValue < values[i]); + prevValue = values[i]; + } + return; + } } diff --git a/core/java/android/hardware/fingerprint/Fingerprint.java b/core/java/android/hardware/fingerprint/Fingerprint.java index c30763475fa8..c7ce8fad2543 100644 --- a/core/java/android/hardware/fingerprint/Fingerprint.java +++ b/core/java/android/hardware/fingerprint/Fingerprint.java @@ -15,6 +15,7 @@ */ package android.hardware.fingerprint; +import android.hardware.biometrics.BiometricAuthenticator; import android.os.Parcel; import android.os.Parcelable; @@ -22,7 +23,7 @@ import android.os.Parcelable; * Container for fingerprint metadata. * @hide */ -public final class Fingerprint implements Parcelable { +public final class Fingerprint extends BiometricAuthenticator.BiometricIdentifier { private CharSequence mName; private int mGroupId; private int mFingerId; diff --git a/core/java/android/hardware/fingerprint/FingerprintDialog.java b/core/java/android/hardware/fingerprint/FingerprintDialog.java index 6b7fab773b43..49835963a3b1 100644 --- a/core/java/android/hardware/fingerprint/FingerprintDialog.java +++ b/core/java/android/hardware/fingerprint/FingerprintDialog.java @@ -23,19 +23,25 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.content.Context; import android.content.DialogInterface; -import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; -import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; +import android.content.pm.PackageManager; +import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricFingerprintConstants; +import android.hardware.biometrics.CryptoObject; import android.hardware.fingerprint.IFingerprintDialogReceiver; import android.os.Bundle; import android.os.CancellationSignal; import android.text.TextUtils; +import java.security.Signature; import java.util.concurrent.Executor; +import javax.crypto.Cipher; +import javax.crypto.Mac; + /** * A class that manages a system-provided fingerprint dialog. */ -public class FingerprintDialog { +public class FingerprintDialog implements BiometricAuthenticator, BiometricFingerprintConstants { /** * @hide @@ -200,6 +206,7 @@ public class FingerprintDialog { } } + private PackageManager mPackageManager; private FingerprintManager mFingerprintManager; private Bundle mBundle; private ButtonInfo mPositiveButtonInfo; @@ -227,37 +234,209 @@ public class FingerprintDialog { mPositiveButtonInfo = positiveButtonInfo; mNegativeButtonInfo = negativeButtonInfo; mFingerprintManager = context.getSystemService(FingerprintManager.class); + mPackageManager = context.getPackageManager(); + } + + /** + * A wrapper class for the crypto objects supported by FingerprintManager. Currently the + * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects. + */ + public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { + public CryptoObject(@NonNull Signature signature) { + super(signature); + } + + public CryptoObject(@NonNull Cipher cipher) { + super(cipher); + } + + public CryptoObject(@NonNull Mac mac) { + super(mac); + } + + /** + * Get {@link Signature} object. + * @return {@link Signature} object or null if this doesn't contain one. + */ + public Signature getSignature() { + return super.getSignature(); + } + + /** + * Get {@link Cipher} object. + * @return {@link Cipher} object or null if this doesn't contain one. + */ + public Cipher getCipher() { + return super.getCipher(); + } + + /** + * Get {@link Mac} object. + * @return {@link Mac} object or null if this doesn't contain one. + */ + public Mac getMac() { + return super.getMac(); + } + } + + /** + * Container for callback data from {@link #authenticate( + * CancellationSignal, Executor, AuthenticationCallback)} and + * {@link #authenticate(CryptoObject, CancellationSignal, Executor, + * AuthenticationCallback)} + */ + public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult { + /** + * Authentication result + * @param crypto + * @param identifier + * @param userId + * @hide + */ + public AuthenticationResult(CryptoObject crypto, BiometricIdentifier identifier, + int userId) { + super(crypto, identifier, userId); + } + /** + * Obtain the crypto object associated with this transaction + * @return crypto object provided to {@link #authenticate( + * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)} + */ + public CryptoObject getCryptoObject() { + return (CryptoObject) super.getCryptoObject(); + } + } + + /** + * Callback structure provided to {@link FingerprintDialog#authenticate(CancellationSignal, + * Executor, AuthenticationCallback)} or {@link FingerprintDialog#authenticate(CryptoObject, + * CancellationSignal, Executor, AuthenticationCallback)}. Users must provide an implementation + * of this for listening to authentication events. + */ + public static abstract class AuthenticationCallback extends + BiometricAuthenticator.AuthenticationCallback { + /** + * Called when an unrecoverable error has been encountered and the operation is complete. + * No further actions will be made on this object. + * @param errorCode An integer identifying the error message + * @param errString A human-readable error string that can be shown on an UI + */ + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) {} + + /** + * Called when a recoverable error has been encountered during authentication. The help + * string is provided to give the user guidance for what went wrong, such as "Sensor dirty, + * please clean it." + * @param helpCode An integer identifying the error message + * @param helpString A human-readable string that can be shown on an UI + */ + @Override + public void onAuthenticationHelp(int helpCode, CharSequence helpString) {} + + /** + * Called when a biometric is recognized. + * @param result An object containing authentication-related data + */ + public void onAuthenticationSucceeded(AuthenticationResult result) {} + + /** + * Called when a biometric is valid but not recognized. + */ + @Override + public void onAuthenticationFailed() {} + + /** + * Called when a biometric has been acquired, but hasn't been processed yet. + * @hide + */ + @Override + public void onAuthenticationAcquired(int acquireInfo) {} + + /** + * @param result An object containing authentication-related data + * @hide + */ + @Override + public void onAuthenticationSucceeded(BiometricAuthenticator.AuthenticationResult result) { + onAuthenticationSucceeded(new AuthenticationResult( + (CryptoObject) result.getCryptoObject(), + result.getId(), + result.getUserId())); + } + } + + + /** + * @param crypto Object associated with the call + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events + * @hide + */ + @Override + public void authenticate(@NonNull android.hardware.biometrics.CryptoObject crypto, + @NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull BiometricAuthenticator.AuthenticationCallback callback) { + if (!(callback instanceof FingerprintDialog.AuthenticationCallback)) { + throw new IllegalArgumentException("Callback cannot be casted"); + } + authenticate(crypto, cancel, executor, (AuthenticationCallback) callback); } /** + * + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events + * @hide + */ + @Override + public void authenticate(@NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull BiometricAuthenticator.AuthenticationCallback callback) { + if (!(callback instanceof FingerprintDialog.AuthenticationCallback)) { + throw new IllegalArgumentException("Callback cannot be casted"); + } + authenticate(cancel, executor, (AuthenticationCallback) callback); + } + + + /** * This call warms up the fingerprint hardware, displays a system-provided dialog, * and starts scanning for a fingerprint. It terminates when - * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when - * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, - * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user - * dismisses the system-provided dialog, at which point the crypto object becomes invalid. - * This operation can be canceled by using the provided cancel object. The application will - * receive authentication errors through {@link AuthenticationCallback}, and button events - * through the corresponding callback set in - * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. - * It is safe to reuse the {@link FingerprintDialog} object, and calling - * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)} - * while an existing authentication attempt is occurring will stop the previous client and - * start a new authentication. The interrupted client will receive a cancelled notification - * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}. + * {@link AuthenticationCallback#onAuthenticationError(int, + * CharSequence)} is called, when + * {@link AuthenticationCallback#onAuthenticationSucceeded( + * AuthenticationResult)}, or when the user dismisses the system-provided dialog, at which point + * the crypto object becomes invalid. This operation can be canceled by using the provided + * cancel object. The application will receive authentication errors through + * {@link AuthenticationCallback}, and button events through the + * corresponding callback set in {@link Builder#setNegativeButton(CharSequence, + * Executor, DialogInterface.OnClickListener)}. It is safe to reuse the + * {@link FingerprintDialog} object, and calling {@link FingerprintDialog#authenticate( + * CancellationSignal, Executor, AuthenticationCallback)} while an + * existing authentication attempt is occurring will stop the previous client and start a + * new authentication. The interrupted client will receive a cancelled notification through + * {@link AuthenticationCallback#onAuthenticationError(int, + * CharSequence)}. * - * @throws IllegalArgumentException if any of the arguments are null + * @throws IllegalArgumentException If any of the arguments are null * - * @param crypto object associated with the call - * @param cancel an object that can be used to cancel authentication - * @param executor an executor to handle callback events - * @param callback an object to receive authentication events + * @param crypto Object associated with the call + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events */ @RequiresPermission(USE_FINGERPRINT) - public void authenticate(@NonNull FingerprintManager.CryptoObject crypto, + public void authenticate(@NonNull CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, - @NonNull FingerprintManager.AuthenticationCallback callback) { + @NonNull AuthenticationCallback callback) { + if (handlePreAuthenticationErrors(callback, executor)) { + return; + } mFingerprintManager.authenticate(crypto, cancel, mBundle, executor, mDialogReceiver, callback); } @@ -265,29 +444,57 @@ public class FingerprintDialog { /** * This call warms up the fingerprint hardware, displays a system-provided dialog, * and starts scanning for a fingerprint. It terminates when - * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when - * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, - * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user - * dismisses the system-provided dialog. This operation can be canceled by using the provided - * cancel object. The application will receive authentication errors through - * {@link AuthenticationCallback}, and button events through the corresponding callback set in + * {@link AuthenticationCallback#onAuthenticationError(int, + * CharSequence)} is called, when + * {@link AuthenticationCallback#onAuthenticationSucceeded( + * AuthenticationResult)} is called, or when the user dismisses the system-provided dialog. + * This operation can be canceled by using the provided cancel object. The application will + * receive authentication errors through {@link AuthenticationCallback}, + * and button events through the corresponding callback set in * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. * It is safe to reuse the {@link FingerprintDialog} object, and calling - * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)} - * while an existing authentication attempt is occurring will stop the previous client and - * start a new authentication. The interrupted client will receive a cancelled notification - * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}. + * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, + * AuthenticationCallback)} while an existing authentication attempt is + * occurring will stop the previous client and start a new authentication. The interrupted + * client will receive a cancelled notification through + * {@link AuthenticationCallback#onAuthenticationError(int, + * CharSequence)}. * - * @throws IllegalArgumentException if any of the arguments are null + * @throws IllegalArgumentException If any of the arguments are null * - * @param cancel an object that can be used to cancel authentication - * @param executor an executor to handle callback events - * @param callback an object to receive authentication events + * @param cancel An object that can be used to cancel authentication + * @param executor An executor to handle callback events + * @param callback An object to receive authentication events */ @RequiresPermission(USE_FINGERPRINT) public void authenticate(@NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, - @NonNull FingerprintManager.AuthenticationCallback callback) { + @NonNull AuthenticationCallback callback) { + if (handlePreAuthenticationErrors(callback, executor)) { + return; + } mFingerprintManager.authenticate(cancel, mBundle, executor, mDialogReceiver, callback); } + + private boolean handlePreAuthenticationErrors(AuthenticationCallback callback, + Executor executor) { + if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + sendError(FINGERPRINT_ERROR_HW_NOT_PRESENT, callback, executor); + return true; + } else if (!mFingerprintManager.isHardwareDetected()) { + sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, callback, executor); + return true; + } else if (!mFingerprintManager.hasEnrolledFingerprints()) { + sendError(FINGERPRINT_ERROR_NO_FINGERPRINTS, callback, executor); + return true; + } + return false; + } + + private void sendError(int error, AuthenticationCallback callback, Executor executor) { + executor.execute(() -> { + callback.onAuthenticationError(error, mFingerprintManager.getErrorString( + error, 0 /* vendorCode */)); + }); + } } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 62d92c4ac385..2dfe673a1c6c 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -27,6 +27,8 @@ import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.app.ActivityManager; import android.content.Context; +import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricFingerprintConstants; import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; @@ -38,7 +40,6 @@ import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; -import android.security.keystore.AndroidKeyStoreProvider; import android.util.Log; import android.util.Slog; @@ -51,9 +52,14 @@ import javax.crypto.Mac; /** * A class that coordinates access to the fingerprint hardware. + * @deprecated See {@link FingerprintDialog} which shows a system-provided dialog upon starting + * authentication. In a world where devices may have in-display fingerprint sensors, it's much + * more realistic to have a system-provided authentication dialog since the in-display sensor + * location may vary by vendor/device. */ +@Deprecated @SystemService(Context.FINGERPRINT_SERVICE) -public class FingerprintManager { +public class FingerprintManager implements BiometricFingerprintConstants { private static final String TAG = "FingerprintManager"; private static final boolean DEBUG = true; private static final int MSG_ENROLL_RESULT = 100; @@ -64,147 +70,14 @@ public class FingerprintManager { private static final int MSG_REMOVED = 105; private static final int MSG_ENUMERATED = 106; - // - // Error messages from fingerprint hardware during initilization, enrollment, authentication or - // removal. Must agree with the list in fingerprint.h - // - - /** - * The hardware is unavailable. Try again later. - */ - public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; - - /** - * Error state returned when the sensor was unable to process the current image. - */ - public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; - - /** - * Error state returned when the current request has been running too long. This is intended to - * prevent programs from waiting for the fingerprint sensor indefinitely. The timeout is - * platform and sensor-specific, but is generally on the order of 30 seconds. - */ - public static final int FINGERPRINT_ERROR_TIMEOUT = 3; - - /** - * Error state returned for operations like enrollment; the operation cannot be completed - * because there's not enough storage remaining to complete the operation. - */ - public static final int FINGERPRINT_ERROR_NO_SPACE = 4; - - /** - * The operation was canceled because the fingerprint sensor is unavailable. For example, - * this may happen when the user is switched, the device is locked or another pending operation - * prevents or disables it. - */ - public static final int FINGERPRINT_ERROR_CANCELED = 5; - - /** - * The {@link FingerprintManager#remove} call failed. Typically this will happen when the - * provided fingerprint id was incorrect. - * - * @hide - */ - public static final int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6; - - /** - * The operation was canceled because the API is locked out due to too many attempts. - * This occurs after 5 failed attempts, and lasts for 30 seconds. - */ - public static final int FINGERPRINT_ERROR_LOCKOUT = 7; - - /** - * Hardware vendors may extend this list if there are conditions that do not fall under one of - * the above categories. Vendors are responsible for providing error strings for these errors. - * These messages are typically reserved for internal operations such as enrollment, but may be - * used to express vendor errors not covered by the ones in fingerprint.h. Applications are - * expected to show the error message string if they happen, but are advised not to rely on the - * message id since they will be device and vendor-specific - */ - public static final int FINGERPRINT_ERROR_VENDOR = 8; - - /** - * The operation was canceled because FINGERPRINT_ERROR_LOCKOUT occurred too many times. - * Fingerprint authentication is disabled until the user unlocks with strong authentication - * (PIN/Pattern/Password) - */ - public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; - - /** - * The user canceled the operation. Upon receiving this, applications should use alternate - * authentication (e.g. a password). The application should also provide the means to return - * to fingerprint authentication, such as a "use fingerprint" button. - */ - public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; - - /** - * @hide - */ - public static final int FINGERPRINT_ERROR_VENDOR_BASE = 1000; - - // - // Image acquisition messages. Must agree with those in fingerprint.h - // - - /** - * The image acquired was good. - */ - public static final int FINGERPRINT_ACQUIRED_GOOD = 0; - - /** - * Only a partial fingerprint image was detected. During enrollment, the user should be - * informed on what needs to happen to resolve this problem, e.g. "press firmly on sensor." - */ - public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; - - /** - * The fingerprint image was too noisy to process due to a detected condition (i.e. dry skin) or - * a possibly dirty sensor (See {@link #FINGERPRINT_ACQUIRED_IMAGER_DIRTY}). - */ - public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; - - /** - * The fingerprint image was too noisy due to suspected or detected dirt on the sensor. - * For example, it's reasonable return this after multiple - * {@link #FINGERPRINT_ACQUIRED_INSUFFICIENT} or actual detection of dirt on the sensor - * (stuck pixels, swaths, etc.). The user is expected to take action to clean the sensor - * when this is returned. - */ - public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; - - /** - * The fingerprint image was unreadable due to lack of motion. This is most appropriate for - * linear array sensors that require a swipe motion. - */ - public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; - - /** - * The fingerprint image was incomplete due to quick motion. While mostly appropriate for - * linear array sensors, this could also happen if the finger was moved during acquisition. - * The user should be asked to move the finger slower (linear) or leave the finger on the sensor - * longer. - */ - public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; - - /** - * Hardware vendors may extend this list if there are conditions that do not fall under one of - * the above categories. Vendors are responsible for providing error strings for these errors. - * @hide - */ - public static final int FINGERPRINT_ACQUIRED_VENDOR = 6; - /** - * @hide - */ - public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; - private IFingerprintService mService; private Context mContext; private IBinder mToken = new Binder(); - private AuthenticationCallback mAuthenticationCallback; + private BiometricAuthenticator.AuthenticationCallback mAuthenticationCallback; private EnrollmentCallback mEnrollmentCallback; private RemovalCallback mRemovalCallback; private EnumerateCallback mEnumerateCallback; - private CryptoObject mCryptoObject; + private android.hardware.biometrics.CryptoObject mCryptoObject; private Fingerprint mRemovalFingerprint; private Handler mHandler; private Executor mExecutor; @@ -217,9 +90,9 @@ public class FingerprintManager { } private class OnAuthenticationCancelListener implements OnCancelListener { - private CryptoObject mCrypto; + private android.hardware.biometrics.CryptoObject mCrypto; - public OnAuthenticationCancelListener(CryptoObject crypto) { + public OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto) { mCrypto = crypto; } @@ -233,18 +106,17 @@ public class FingerprintManager { * A wrapper class for the crypto objects supported by FingerprintManager. Currently the * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects. */ - public static final class CryptoObject { - + public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { public CryptoObject(@NonNull Signature signature) { - mCrypto = signature; + super(signature); } public CryptoObject(@NonNull Cipher cipher) { - mCrypto = cipher; + super(cipher); } public CryptoObject(@NonNull Mac mac) { - mCrypto = mac; + super(mac); } /** @@ -252,7 +124,7 @@ public class FingerprintManager { * @return {@link Signature} object or null if this doesn't contain one. */ public Signature getSignature() { - return mCrypto instanceof Signature ? (Signature) mCrypto : null; + return super.getSignature(); } /** @@ -260,7 +132,7 @@ public class FingerprintManager { * @return {@link Cipher} object or null if this doesn't contain one. */ public Cipher getCipher() { - return mCrypto instanceof Cipher ? (Cipher) mCrypto : null; + return super.getCipher(); } /** @@ -268,20 +140,9 @@ public class FingerprintManager { * @return {@link Mac} object or null if this doesn't contain one. */ public Mac getMac() { - return mCrypto instanceof Mac ? (Mac) mCrypto : null; + return super.getMac(); } - - /** - * @hide - * @return the opId associated with this object or 0 if none - */ - public long getOpId() { - return mCrypto != null ? - AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto) : 0; - } - - private final Object mCrypto; - }; + } /** * Container for callback data from {@link FingerprintManager#authenticate(CryptoObject, @@ -334,13 +195,15 @@ public class FingerprintManager { * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to * fingerprint events. */ - public static abstract class AuthenticationCallback { + public static abstract class AuthenticationCallback + extends BiometricAuthenticator.AuthenticationCallback { /** * Called when an unrecoverable error has been encountered and the operation is complete. * No further callbacks will be made on this object. * @param errorCode An integer identifying the error message * @param errString A human-readable error string that can be shown in UI */ + @Override public void onAuthenticationError(int errorCode, CharSequence errString) { } /** @@ -350,6 +213,7 @@ public class FingerprintManager { * @param helpCode An integer identifying the error message * @param helpString A human-readable string that can be shown in UI */ + @Override public void onAuthenticationHelp(int helpCode, CharSequence helpString) { } /** @@ -361,6 +225,7 @@ public class FingerprintManager { /** * Called when a fingerprint is valid but not recognized. */ + @Override public void onAuthenticationFailed() { } /** @@ -369,7 +234,19 @@ public class FingerprintManager { * @param acquireInfo one of FINGERPRINT_ACQUIRED_* constants * @hide */ + @Override public void onAuthenticationAcquired(int acquireInfo) {} + + /** + * @hide + * @param result + */ + @Override + public void onAuthenticationSucceeded(BiometricAuthenticator.AuthenticationResult result) { + onAuthenticationSucceeded(new AuthenticationResult( + (CryptoObject) result.getCryptoObject(), + (Fingerprint) result.getId(), result.getUserId())); + } }; /** @@ -489,7 +366,12 @@ public class FingerprintManager { * by <a href="{@docRoot}training/articles/keystore.html">Android Keystore * facility</a>. * @throws IllegalStateException if the crypto primitive is not initialized. + * @deprecated See {@link FingerprintDialog#authenticate(CancellationSignal, Executor, + * FingerprintDialog.AuthenticationCallback)} and {@link FingerprintDialog#authenticate( + * FingerprintDialog.CryptoObject, CancellationSignal, Executor, + * FingerprintDialog.AuthenticationCallback)} */ + @Deprecated @RequiresPermission(USE_FINGERPRINT) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) { @@ -554,12 +436,12 @@ public class FingerprintManager { * @param userId the user ID that the fingerprint hardware will authenticate for. */ private void authenticate(int userId, - @Nullable CryptoObject crypto, + @Nullable android.hardware.biometrics.CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, @NonNull IFingerprintDialogReceiver receiver, - @NonNull AuthenticationCallback callback) { + @NonNull BiometricAuthenticator.AuthenticationCallback callback) { mCryptoObject = crypto; if (cancel.isCanceled()) { Log.w(TAG, "authentication already canceled"); @@ -598,7 +480,7 @@ public class FingerprintManager { @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, @NonNull IFingerprintDialogReceiver receiver, - @NonNull AuthenticationCallback callback) { + @NonNull BiometricAuthenticator.AuthenticationCallback callback) { if (cancel == null) { throw new IllegalArgumentException("Must supply a cancellation signal"); } @@ -626,12 +508,12 @@ public class FingerprintManager { * @param callback * @hide */ - public void authenticate(@NonNull CryptoObject crypto, + public void authenticate(@NonNull android.hardware.biometrics.CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, @NonNull IFingerprintDialogReceiver receiver, - @NonNull AuthenticationCallback callback) { + @NonNull BiometricAuthenticator.AuthenticationCallback callback) { if (crypto == null) { throw new IllegalArgumentException("Must supply a crypto object"); } @@ -648,9 +530,10 @@ public class FingerprintManager { throw new IllegalArgumentException("Must supply a receiver"); } if (callback == null) { - throw new IllegalArgumentException("Must supply a calback"); + throw new IllegalArgumentException("Must supply a callback"); } - authenticate(UserHandle.myUserId(), crypto, cancel, bundle, executor, receiver, callback); + authenticate(UserHandle.myUserId(), crypto, cancel, + bundle, executor, receiver, callback); } /** @@ -848,7 +731,10 @@ public class FingerprintManager { * Determine if there is at least one fingerprint enrolled. * * @return true if at least one fingerprint is enrolled, false otherwise + * @deprecated See {@link FingerprintDialog} and + * {@link FingerprintDialog#FINGERPRINT_ERROR_NO_FINGERPRINTS} */ + @Deprecated @RequiresPermission(USE_FINGERPRINT) public boolean hasEnrolledFingerprints() { if (mService != null) try { @@ -879,7 +765,10 @@ public class FingerprintManager { * Determine if fingerprint hardware is present and functional. * * @return true if hardware is present and functional, false otherwise. + * @deprecated See {@link FingerprintDialog} and + * {@link FingerprintDialog#FINGERPRINT_ERROR_HW_UNAVAILABLE} */ + @Deprecated @RequiresPermission(USE_FINGERPRINT) public boolean isHardwareDetected() { if (mService != null) { @@ -1049,8 +938,8 @@ public class FingerprintManager { private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) { if (mAuthenticationCallback != null) { - final AuthenticationResult result = - new AuthenticationResult(mCryptoObject, fp, userId); + final BiometricAuthenticator.AuthenticationResult result = + new BiometricAuthenticator.AuthenticationResult(mCryptoObject, fp, userId); mAuthenticationCallback.onAuthenticationSucceeded(result); } } @@ -1126,7 +1015,7 @@ public class FingerprintManager { } } - private void cancelAuthentication(CryptoObject cryptoObject) { + private void cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject) { if (mService != null) try { mService.cancelAuthentication(mToken, mContext.getOpPackageName()); } catch (RemoteException e) { @@ -1160,6 +1049,12 @@ public class FingerprintManager { case FINGERPRINT_ERROR_USER_CANCELED: return mContext.getString( com.android.internal.R.string.fingerprint_error_user_canceled); + case FINGERPRINT_ERROR_NO_FINGERPRINTS: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_no_fingerprints); + case FINGERPRINT_ERROR_HW_NOT_PRESENT: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_hw_not_present); case FINGERPRINT_ERROR_VENDOR: { String[] msgArray = mContext.getResources().getStringArray( com.android.internal.R.array.fingerprint_error_vendor); diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 9edcc0e9b8d4..5ca3a4106a2d 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -720,6 +720,10 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { LOOP: while (end < length) { switch (uriString.charAt(end)) { case '/': // Start of path + case '\\':// Start of path + // Per http://url.spec.whatwg.org/#host-state, the \ character + // is treated as if it were a / character when encountered in a + // host case '?': // Start of query case '#': // Start of fragment break LOOP; @@ -758,6 +762,10 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { case '#': // Start of fragment return ""; // Empty path. case '/': // Start of path! + case '\\':// Start of path! + // Per http://url.spec.whatwg.org/#host-state, the \ character + // is treated as if it were a / character when encountered in a + // host break LOOP; } pathStart++; diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 7c53ec198e7d..1160415ce212 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -16,6 +16,11 @@ package android.os; +import static android.system.OsConstants.SPLICE_F_MORE; +import static android.system.OsConstants.SPLICE_F_MOVE; +import static android.system.OsConstants.S_ISFIFO; +import static android.system.OsConstants.S_ISREG; + import android.annotation.NonNull; import android.annotation.Nullable; import android.provider.DocumentsContract.Document; @@ -28,7 +33,9 @@ import android.util.Slog; import android.webkit.MimeTypeMap; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.SizedInputStream; +import libcore.io.IoUtils; import libcore.util.EmptyArray; import java.io.BufferedInputStream; @@ -41,10 +48,12 @@ import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Comparator; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; @@ -81,6 +90,14 @@ public class FileUtils { private static final File[] EMPTY = new File[0]; + private static final boolean ENABLE_COPY_OPTIMIZATIONS = true; + + private static final long COPY_CHECKPOINT_BYTES = 524288; + + public interface ProgressListener { + public void onProgress(long progress); + } + /** * Set owner and mode of of given {@link File}. * @@ -185,6 +202,9 @@ public class FileUtils { return false; } + /** + * @deprecated use {@link #copy(File, File)} instead. + */ @Deprecated public static boolean copyFile(File srcFile, File destFile) { try { @@ -195,14 +215,19 @@ public class FileUtils { } } - // copy a file from srcFile to destFile, return true if succeed, return - // false if fail + /** + * @deprecated use {@link #copy(File, File)} instead. + */ + @Deprecated public static void copyFileOrThrow(File srcFile, File destFile) throws IOException { try (InputStream in = new FileInputStream(srcFile)) { copyToFileOrThrow(in, destFile); } } + /** + * @deprecated use {@link #copy(InputStream, OutputStream)} instead. + */ @Deprecated public static boolean copyToFile(InputStream inputStream, File destFile) { try { @@ -214,29 +239,247 @@ public class FileUtils { } /** - * Copy data from a source stream to destFile. - * Return true if succeed, return false if failed. + * @deprecated use {@link #copy(InputStream, OutputStream)} instead. */ - public static void copyToFileOrThrow(InputStream inputStream, File destFile) - throws IOException { + @Deprecated + public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException { if (destFile.exists()) { destFile.delete(); } - FileOutputStream out = new FileOutputStream(destFile); - try { - byte[] buffer = new byte[4096]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) >= 0) { - out.write(buffer, 0, bytesRead); + try (FileOutputStream out = new FileOutputStream(destFile)) { + copy(in, out); + try { + Os.fsync(out.getFD()); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); } - } finally { - out.flush(); + } + } + + /** + * Copy the contents of one file to another, replacing any existing content. + * <p> + * Attempts to use several optimization strategies to copy the data in the + * kernel before falling back to a userspace copy as a last resort. + * + * @return number of bytes copied. + */ + public static long copy(@NonNull File from, @NonNull File to) throws IOException { + return copy(from, to, null, null); + } + + /** + * Copy the contents of one file to another, replacing any existing content. + * <p> + * Attempts to use several optimization strategies to copy the data in the + * kernel before falling back to a userspace copy as a last resort. + * + * @param listener to be periodically notified as the copy progresses. + * @param signal to signal if the copy should be cancelled early. + * @return number of bytes copied. + */ + public static long copy(@NonNull File from, @NonNull File to, + @Nullable ProgressListener listener, @Nullable CancellationSignal signal) + throws IOException { + try (FileInputStream in = new FileInputStream(from); + FileOutputStream out = new FileOutputStream(to)) { + return copy(in, out, listener, signal); + } + } + + /** + * Copy the contents of one stream to another. + * <p> + * Attempts to use several optimization strategies to copy the data in the + * kernel before falling back to a userspace copy as a last resort. + * + * @return number of bytes copied. + */ + public static long copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException { + return copy(in, out, null, null); + } + + /** + * Copy the contents of one stream to another. + * <p> + * Attempts to use several optimization strategies to copy the data in the + * kernel before falling back to a userspace copy as a last resort. + * + * @param listener to be periodically notified as the copy progresses. + * @param signal to signal if the copy should be cancelled early. + * @return number of bytes copied. + */ + public static long copy(@NonNull InputStream in, @NonNull OutputStream out, + @Nullable ProgressListener listener, @Nullable CancellationSignal signal) + throws IOException { + if (ENABLE_COPY_OPTIMIZATIONS) { + if (in instanceof FileInputStream && out instanceof FileOutputStream) { + return copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(), + listener, signal); + } + } + + // Worse case fallback to userspace + return copyInternalUserspace(in, out, listener, signal); + } + + /** + * Copy the contents of one FD to another. + * <p> + * Attempts to use several optimization strategies to copy the data in the + * kernel before falling back to a userspace copy as a last resort. + * + * @return number of bytes copied. + */ + public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out) + throws IOException { + return copy(in, out, null, null); + } + + /** + * Copy the contents of one FD to another. + * <p> + * Attempts to use several optimization strategies to copy the data in the + * kernel before falling back to a userspace copy as a last resort. + * + * @param listener to be periodically notified as the copy progresses. + * @param signal to signal if the copy should be cancelled early. + * @return number of bytes copied. + */ + public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, + @Nullable ProgressListener listener, @Nullable CancellationSignal signal) + throws IOException { + return copy(in, out, listener, signal, Long.MAX_VALUE); + } + + /** + * Copy the contents of one FD to another. + * <p> + * Attempts to use several optimization strategies to copy the data in the + * kernel before falling back to a userspace copy as a last resort. + * + * @param listener to be periodically notified as the copy progresses. + * @param signal to signal if the copy should be cancelled early. + * @param count the number of bytes to copy. + * @return number of bytes copied. + */ + public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, + @Nullable ProgressListener listener, @Nullable CancellationSignal signal, long count) + throws IOException { + if (ENABLE_COPY_OPTIMIZATIONS) { try { - out.getFD().sync(); - } catch (IOException e) { + final StructStat st_in = Os.fstat(in); + final StructStat st_out = Os.fstat(out); + if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) { + return copyInternalSendfile(in, out, listener, signal, count); + } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) { + return copyInternalSplice(in, out, listener, signal, count); + } + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } + + // Worse case fallback to userspace + return copyInternalUserspace(in, out, listener, signal, count); + } + + /** + * Requires one of input or output to be a pipe. + */ + @VisibleForTesting + public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, + ProgressListener listener, CancellationSignal signal, long count) + throws ErrnoException { + long progress = 0; + long checkpoint = 0; + + long t; + while ((t = Os.splice(in, null, out, null, Math.min(count, COPY_CHECKPOINT_BYTES), + SPLICE_F_MOVE | SPLICE_F_MORE)) != 0) { + progress += t; + checkpoint += t; + count -= t; + + if (checkpoint >= COPY_CHECKPOINT_BYTES) { + if (signal != null) { + signal.throwIfCanceled(); + } + if (listener != null) { + listener.onProgress(progress); + } + checkpoint = 0; } - out.close(); } + return progress; + } + + /** + * Requires both input and output to be a regular file. + */ + @VisibleForTesting + public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, + ProgressListener listener, CancellationSignal signal, long count) + throws ErrnoException { + long progress = 0; + long checkpoint = 0; + + long t; + while ((t = Os.sendfile(out, in, null, Math.min(count, COPY_CHECKPOINT_BYTES))) != 0) { + progress += t; + checkpoint += t; + count -= t; + + if (checkpoint >= COPY_CHECKPOINT_BYTES) { + if (signal != null) { + signal.throwIfCanceled(); + } + if (listener != null) { + listener.onProgress(progress); + } + checkpoint = 0; + } + } + return progress; + } + + @VisibleForTesting + public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, + ProgressListener listener, CancellationSignal signal, long count) throws IOException { + if (count != Long.MAX_VALUE) { + return copyInternalUserspace(new SizedInputStream(new FileInputStream(in), count), + new FileOutputStream(out), listener, signal); + } else { + return copyInternalUserspace(new FileInputStream(in), + new FileOutputStream(out), listener, signal); + } + } + + @VisibleForTesting + public static long copyInternalUserspace(InputStream in, OutputStream out, + ProgressListener listener, CancellationSignal signal) throws IOException { + long progress = 0; + long checkpoint = 0; + byte[] buffer = new byte[8192]; + + int t; + while ((t = in.read(buffer)) != -1) { + out.write(buffer, 0, t); + + progress += t; + checkpoint += t; + + if (checkpoint >= COPY_CHECKPOINT_BYTES) { + if (signal != null) { + signal.throwIfCanceled(); + } + if (listener != null) { + listener.onProgress(progress); + } + checkpoint = 0; + } + } + return progress; } /** @@ -797,4 +1040,69 @@ public class FileUtils { } return val * pow; } + + @VisibleForTesting + public static class MemoryPipe extends Thread implements AutoCloseable { + private final FileDescriptor[] pipe; + private final byte[] data; + private final boolean sink; + + private MemoryPipe(byte[] data, boolean sink) throws IOException { + try { + this.pipe = Os.pipe(); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + this.data = data; + this.sink = sink; + } + + private MemoryPipe startInternal() { + super.start(); + return this; + } + + public static MemoryPipe createSource(byte[] data) throws IOException { + return new MemoryPipe(data, false).startInternal(); + } + + public static MemoryPipe createSink(byte[] data) throws IOException { + return new MemoryPipe(data, true).startInternal(); + } + + public FileDescriptor getFD() { + return sink ? pipe[1] : pipe[0]; + } + + public FileDescriptor getInternalFD() { + return sink ? pipe[0] : pipe[1]; + } + + @Override + public void run() { + final FileDescriptor fd = getInternalFD(); + try { + int i = 0; + while (i < data.length) { + if (sink) { + i += Os.read(fd, data, i, data.length - i); + } else { + i += Os.write(fd, data, i, data.length - i); + } + } + } catch (IOException | ErrnoException e) { + // Ignored + } finally { + if (sink) { + SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); + } + IoUtils.closeQuietly(fd); + } + } + + @Override + public void close() throws Exception { + IoUtils.closeQuietly(getFD()); + } + } } diff --git a/core/java/android/print/PrintFileDocumentAdapter.java b/core/java/android/print/PrintFileDocumentAdapter.java index 747400d486bf..a5f93050e307 100644 --- a/core/java/android/print/PrintFileDocumentAdapter.java +++ b/core/java/android/print/PrintFileDocumentAdapter.java @@ -21,6 +21,8 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.CancellationSignal; import android.os.CancellationSignal.OnCancelListener; +import android.os.FileUtils; +import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; import android.util.Log; @@ -114,28 +116,15 @@ public class PrintFileDocumentAdapter extends PrintDocumentAdapter { @Override protected Void doInBackground(Void... params) { - InputStream in = null; - OutputStream out = new FileOutputStream(mDestination.getFileDescriptor()); - final byte[] buffer = new byte[8192]; - try { - in = new FileInputStream(mFile); - while (true) { - if (isCancelled()) { - break; - } - final int readByteCount = in.read(buffer); - if (readByteCount < 0) { - break; - } - out.write(buffer, 0, readByteCount); - } - } catch (IOException ioe) { - Log.e(LOG_TAG, "Error writing data!", ioe); - mResultCallback.onWriteFailed(mContext.getString( - R.string.write_fail_reason_cannot_write)); - } finally { - IoUtils.closeQuietly(in); - IoUtils.closeQuietly(out); + try (InputStream in = new FileInputStream(mFile); + OutputStream out = new FileOutputStream(mDestination.getFileDescriptor())) { + FileUtils.copy(in, out, null, mCancellationSignal); + } catch (OperationCanceledException e) { + // Ignored; already handled below + } catch (IOException e) { + Log.e(LOG_TAG, "Error writing data!", e); + mResultCallback.onWriteFailed(mContext.getString( + R.string.write_fail_reason_cannot_write)); } return null; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1f0d683192d5..ad0ce49cf456 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1903,7 +1903,7 @@ public final class Settings { cp.call(cr.getPackageName(), mCallSetCommand, name, arg); String newValue = getStringForUser(cr, name, userHandle); StatsLog.write(StatsLog.SETTING_CHANGED, name, value, newValue, prevValue, tag, - makeDefault ? 1 : 0, userHandle); + makeDefault, userHandle); } catch (RemoteException e) { Log.w(TAG, "Can't set key " + name + " in " + mUri, e); return false; diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java index 4e4a0374087e..7cd08f76a47e 100644 --- a/core/java/android/security/keystore/recovery/RecoveryController.java +++ b/core/java/android/security/keystore/recovery/RecoveryController.java @@ -26,9 +26,13 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; +import android.security.KeyStore; +import android.security.keystore.AndroidKeyStoreProvider; import com.android.internal.widget.ILockSettings; +import java.security.Key; +import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.List; @@ -113,9 +117,11 @@ public class RecoveryController { private final ILockSettings mBinder; + private final KeyStore mKeyStore; - private RecoveryController(ILockSettings binder) { + private RecoveryController(ILockSettings binder, KeyStore keystore) { mBinder = binder; + mKeyStore = keystore; } /** @@ -133,7 +139,7 @@ public class RecoveryController { public static RecoveryController getInstance(Context context) { ILockSettings lockSettings = ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings")); - return new RecoveryController(lockSettings); + return new RecoveryController(lockSettings, KeyStore.getInstance()); } /** @@ -430,6 +436,7 @@ public class RecoveryController { } /** + * Deprecated. * Generates a AES256/GCM/NoPADDING key called {@code alias} and loads it into the recoverable * key store. Returns the raw material of the key. * @@ -444,7 +451,6 @@ public class RecoveryController { public byte[] generateAndStoreKey(@NonNull String alias, byte[] account) throws InternalRecoveryServiceException, LockScreenRequiredException { try { - // TODO: add account return mBinder.generateAndStoreKey(alias); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -457,6 +463,72 @@ public class RecoveryController { } /** + * Generates a AES256/GCM/NoPADDING key called {@code alias} and loads it into the recoverable + * key store. Returns {@link javax.crypto.SecretKey}. + * + * @param alias The key alias. + * @param account The account associated with the key. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + * @throws LockScreenRequiredException if the user has not set a lock screen. This is required + * to generate recoverable keys, as the snapshots are encrypted using a key derived from the + * lock screen. + * @hide + */ + public Key generateKey(@NonNull String alias, byte[] account) + throws InternalRecoveryServiceException, LockScreenRequiredException { + // TODO: update RecoverySession.recoverKeys + try { + String grantAlias = mBinder.generateKey(alias, account); + if (grantAlias == null) { + return null; + } + Key result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore( + mKeyStore, + grantAlias, + KeyStore.UID_SELF); + return result; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (UnrecoverableKeyException e) { + throw new InternalRecoveryServiceException("Access to newly generated key failed for"); + } catch (ServiceSpecificException e) { + if (e.errorCode == ERROR_INSECURE_USER) { + throw new LockScreenRequiredException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Gets a key called {@code alias} from the recoverable key store. + * + * @param alias The key alias. + * @return The key. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + * @throws UnrecoverableKeyException if key is permanently invalidated or not found. + * @hide + */ + public @Nullable Key getKey(@NonNull String alias) + throws InternalRecoveryServiceException, UnrecoverableKeyException { + try { + String grantAlias = mBinder.getKey(alias); + if (grantAlias == null) { + return null; + } + return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore( + mKeyStore, + grantAlias, + KeyStore.UID_SELF); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** * Removes a key called {@code alias} from the recoverable key store. * * @param alias The key alias. diff --git a/core/java/android/service/settings/suggestions/Suggestion.java b/core/java/android/service/settings/suggestions/Suggestion.java index 11e1e674a747..e97f963a0986 100644 --- a/core/java/android/service/settings/suggestions/Suggestion.java +++ b/core/java/android/service/settings/suggestions/Suggestion.java @@ -40,6 +40,7 @@ public final class Suggestion implements Parcelable { */ @IntDef(flag = true, prefix = { "FLAG_" }, value = { FLAG_HAS_BUTTON, + FLAG_ICON_TINTABLE, }) @Retention(RetentionPolicy.SOURCE) public @interface Flags { @@ -49,6 +50,10 @@ public final class Suggestion implements Parcelable { * Flag for suggestion type with a single button */ public static final int FLAG_HAS_BUTTON = 1 << 0; + /** + * @hide + */ + public static final int FLAG_ICON_TINTABLE = 1 << 1; private final String mId; private final CharSequence mTitle; diff --git a/core/java/android/text/style/DrawableMarginSpan.java b/core/java/android/text/style/DrawableMarginSpan.java index 35241796c3c3..cd199b3547c9 100644 --- a/core/java/android/text/style/DrawableMarginSpan.java +++ b/core/java/android/text/style/DrawableMarginSpan.java @@ -16,60 +16,100 @@ package android.text.style; +import android.annotation.NonNull; +import android.annotation.Px; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.text.Layout; import android.text.Spanned; -public class DrawableMarginSpan -implements LeadingMarginSpan, LineHeightSpan -{ - public DrawableMarginSpan(Drawable b) { - mDrawable = b; +/** + * A span which adds a drawable and a padding to the paragraph it's attached to. + * <p> + * If the height of the drawable is bigger than the height of the line it's attached to then the + * line height is increased to fit the drawable. <code>DrawableMarginSpan</code> allows setting a + * padding between the drawable and the text. The default value is 0. The span must be set from the + * beginning of the text, otherwise either the span won't be rendered or it will be rendered + * incorrectly. + * <p> + * For example, a drawable and a padding of 20px can be added like this: + * <pre>{@code SpannableString string = new SpannableString("Text with a drawable."); + * string.setSpan(new DrawableMarginSpan(drawable, 20), 0, string.length(), + * Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> + * <img src="{@docRoot}reference/android/images/text/style/drawablemarginspan.png" /> + * <figcaption>Text with a drawable and a padding.</figcaption> + * <p> + * + * @see IconMarginSpan for working with a {@link android.graphics.Bitmap} instead of + * a {@link Drawable}. + */ +public class DrawableMarginSpan implements LeadingMarginSpan, LineHeightSpan { + private static final int STANDARD_PAD_WIDTH = 0; + + @NonNull + private final Drawable mDrawable; + @Px + private final int mPad; + + /** + * Creates a {@link DrawableMarginSpan} from a {@link Drawable}. The pad width will be 0. + * + * @param drawable the drawable to be added + */ + public DrawableMarginSpan(@NonNull Drawable drawable) { + this(drawable, STANDARD_PAD_WIDTH); } - public DrawableMarginSpan(Drawable b, int pad) { - mDrawable = b; + /** + * Creates a {@link DrawableMarginSpan} from a {@link Drawable} and a padding, in pixels. + * + * @param drawable the drawable to be added + * @param pad the distance between the drawable and the text + */ + public DrawableMarginSpan(@NonNull Drawable drawable, int pad) { + mDrawable = drawable; mPad = pad; } + @Override public int getLeadingMargin(boolean first) { return mDrawable.getIntrinsicWidth() + mPad; } - public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, - int top, int baseline, int bottom, - CharSequence text, int start, int end, - boolean first, Layout layout) { + @Override + public void drawLeadingMargin(@NonNull Canvas c, @NonNull Paint p, int x, int dir, + int top, int baseline, int bottom, + @NonNull CharSequence text, int start, int end, + boolean first, @NonNull Layout layout) { int st = ((Spanned) text).getSpanStart(this); - int ix = (int)x; - int itop = (int)layout.getLineTop(layout.getLineForOffset(st)); + int ix = (int) x; + int itop = (int) layout.getLineTop(layout.getLineForOffset(st)); int dw = mDrawable.getIntrinsicWidth(); int dh = mDrawable.getIntrinsicHeight(); // XXX What to do about Paint? - mDrawable.setBounds(ix, itop, ix+dw, itop+dh); + mDrawable.setBounds(ix, itop, ix + dw, itop + dh); mDrawable.draw(c); } - public void chooseHeight(CharSequence text, int start, int end, - int istartv, int v, - Paint.FontMetricsInt fm) { + @Override + public void chooseHeight(@NonNull CharSequence text, int start, int end, + int istartv, int v, + @NonNull Paint.FontMetricsInt fm) { if (end == ((Spanned) text).getSpanEnd(this)) { int ht = mDrawable.getIntrinsicHeight(); int need = ht - (v + fm.descent - fm.ascent - istartv); - if (need > 0) + if (need > 0) { fm.descent += need; + } need = ht - (v + fm.bottom - fm.top - istartv); - if (need > 0) + if (need > 0) { fm.bottom += need; + } } } - - private Drawable mDrawable; - private int mPad; } diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java index 5b8a6dd3bf6f..1b16f3345bfa 100644 --- a/core/java/android/text/style/DynamicDrawableSpan.java +++ b/core/java/android/text/style/DynamicDrawableSpan.java @@ -16,6 +16,9 @@ package android.text.style; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; @@ -24,32 +27,71 @@ import android.graphics.drawable.Drawable; import java.lang.ref.WeakReference; /** + * Span that replaces the text it's attached to with a {@link Drawable} that can be aligned with + * the bottom or with the baseline of the surrounding text. + * <p> + * For an implementation that constructs the drawable from various sources (<code>Bitmap</code>, + * <code>Drawable</code>, resource id or <code>Uri</code>) use {@link ImageSpan}. + * <p> + * A simple implementation of <code>DynamicDrawableSpan</code> that uses drawables from resources + * looks like this: + * <pre> + * class MyDynamicDrawableSpan extends DynamicDrawableSpan { * + * private final Context mContext; + * private final int mResourceId; + * + * public MyDynamicDrawableSpan(Context context, @DrawableRes int resourceId) { + * mContext = context; + * mResourceId = resourceId; + * } + * + * {@literal @}Override + * public Drawable getDrawable() { + * Drawable drawable = mContext.getDrawable(mResourceId); + * drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + * return drawable; + * } + * }</pre> + * The class can be used like this: + * <pre> + * SpannableString string = new SpannableString("Text with a drawable span"); + * string.setSpan(new MyDynamicDrawableSpan(context, R.mipmap.ic_launcher), 12, 20, Spanned + * .SPAN_EXCLUSIVE_EXCLUSIVE);</pre> + * <img src="{@docRoot}reference/android/images/text/style/dynamicdrawablespan.png" /> + * <figcaption>Replacing text with a drawable.</figcaption> */ public abstract class DynamicDrawableSpan extends ReplacementSpan { - private static final String TAG = "DynamicDrawableSpan"; - + /** * A constant indicating that the bottom of this span should be aligned * with the bottom of the surrounding text, i.e., at the same level as the * lowest descender in the text. */ public static final int ALIGN_BOTTOM = 0; - + /** * A constant indicating that the bottom of this span should be aligned * with the baseline of the surrounding text. */ public static final int ALIGN_BASELINE = 1; - + protected final int mVerticalAlignment; - + + private WeakReference<Drawable> mDrawableRef; + + /** + * Creates a {@link DynamicDrawableSpan}. The default vertical alignment is + * {@link #ALIGN_BOTTOM} + */ public DynamicDrawableSpan() { mVerticalAlignment = ALIGN_BOTTOM; } /** - * @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE}. + * Creates a {@link DynamicDrawableSpan} based on a vertical alignment.\ + * + * @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE} */ protected DynamicDrawableSpan(int verticalAlignment) { mVerticalAlignment = verticalAlignment; @@ -64,22 +106,22 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { } /** - * Your subclass must implement this method to provide the bitmap + * Your subclass must implement this method to provide the bitmap * to be drawn. The dimensions of the bitmap must be the same * from each call to the next. */ public abstract Drawable getDrawable(); @Override - public int getSize(Paint paint, CharSequence text, - int start, int end, - Paint.FontMetricsInt fm) { + public int getSize(@NonNull Paint paint, CharSequence text, + @IntRange(from = 0) int start, @IntRange(from = 0) int end, + @Nullable Paint.FontMetricsInt fm) { Drawable d = getCachedDrawable(); Rect rect = d.getBounds(); if (fm != null) { - fm.ascent = -rect.bottom; - fm.descent = 0; + fm.ascent = -rect.bottom; + fm.descent = 0; fm.top = fm.ascent; fm.bottom = 0; @@ -89,12 +131,12 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { } @Override - public void draw(Canvas canvas, CharSequence text, - int start, int end, float x, - int top, int y, int bottom, Paint paint) { + public void draw(@NonNull Canvas canvas, CharSequence text, + @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, + int top, int y, int bottom, @NonNull Paint paint) { Drawable b = getCachedDrawable(); canvas.save(); - + int transY = bottom - b.getBounds().bottom; if (mVerticalAlignment == ALIGN_BASELINE) { transY -= paint.getFontMetricsInt().descent; @@ -109,8 +151,9 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { WeakReference<Drawable> wr = mDrawableRef; Drawable d = null; - if (wr != null) + if (wr != null) { d = wr.get(); + } if (d == null) { d = getDrawable(); @@ -119,7 +162,5 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { return d; } - - private WeakReference<Drawable> mDrawableRef; } diff --git a/core/java/android/text/style/IconMarginSpan.java b/core/java/android/text/style/IconMarginSpan.java index 304c83f19f02..ad78bd574696 100644 --- a/core/java/android/text/style/IconMarginSpan.java +++ b/core/java/android/text/style/IconMarginSpan.java @@ -16,57 +16,98 @@ package android.text.style; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Px; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.text.Layout; import android.text.Spanned; -public class IconMarginSpan -implements LeadingMarginSpan, LineHeightSpan -{ - public IconMarginSpan(Bitmap b) { - mBitmap = b; +/** + * Paragraph affecting span, that draws a bitmap at the beginning of a text. The span also allows + * setting a padding between the bitmap and the text. The default value of the padding is 0px. The + * span should be attached from the first character of the text. + * <p> + * For example, an <code>IconMarginSpan</code> with a bitmap and a padding of 30px can be set + * like this: + * <pre> + * SpannableString string = new SpannableString("Text with icon and padding"); + * string.setSpan(new IconMarginSpan(bitmap, 30), 0, string.length(), + * Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + * </pre> + * <img src="{@docRoot}reference/android/images/text/style/iconmarginspan.png" /> + * <figcaption>Text with <code>IconMarginSpan</code></figcaption> + * <p> + * + * @see DrawableMarginSpan for working with a {@link android.graphics.drawable.Drawable} instead of + * a {@link Bitmap}. + */ +public class IconMarginSpan implements LeadingMarginSpan, LineHeightSpan { + + @NonNull + private final Bitmap mBitmap; + @Px + private final int mPad; + + /** + * Creates an {@link IconMarginSpan} from a {@link Bitmap}. + * + * @param bitmap bitmap to be rendered at the beginning of the text + */ + public IconMarginSpan(@NonNull Bitmap bitmap) { + this(bitmap, 0); } - public IconMarginSpan(Bitmap b, int pad) { - mBitmap = b; + /** + * Creates an {@link IconMarginSpan} from a {@link Bitmap}. + * + * @param bitmap bitmap to be rendered at the beginning of the text + * @param pad padding width, in pixels, between the bitmap and the text + */ + public IconMarginSpan(@NonNull Bitmap bitmap, @IntRange(from = 0) int pad) { + mBitmap = bitmap; mPad = pad; } + @Override public int getLeadingMargin(boolean first) { return mBitmap.getWidth() + mPad; } + @Override public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, - int top, int baseline, int bottom, - CharSequence text, int start, int end, - boolean first, Layout layout) { + int top, int baseline, int bottom, + CharSequence text, int start, int end, + boolean first, Layout layout) { int st = ((Spanned) text).getSpanStart(this); int itop = layout.getLineTop(layout.getLineForOffset(st)); - if (dir < 0) + if (dir < 0) { x -= mBitmap.getWidth(); + } c.drawBitmap(mBitmap, x, itop, p); } + @Override public void chooseHeight(CharSequence text, int start, int end, - int istartv, int v, - Paint.FontMetricsInt fm) { + int istartv, int v, + Paint.FontMetricsInt fm) { if (end == ((Spanned) text).getSpanEnd(this)) { int ht = mBitmap.getHeight(); int need = ht - (v + fm.descent - fm.ascent - istartv); - if (need > 0) + if (need > 0) { fm.descent += need; + } need = ht - (v + fm.bottom - fm.top - istartv); - if (need > 0) + if (need > 0) { fm.bottom += need; + } } } - private Bitmap mBitmap; - private int mPad; } diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java index b0bff680f390..95f0b43341a2 100644 --- a/core/java/android/text/style/ImageSpan.java +++ b/core/java/android/text/style/ImageSpan.java @@ -17,6 +17,8 @@ package android.text.style; import android.annotation.DrawableRes; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -27,18 +29,49 @@ import android.util.Log; import java.io.InputStream; +/** + * Span that replaces the text it's attached to with a {@link Drawable} that can be aligned with + * the bottom or with the baseline of the surrounding text. The drawable can be constructed from + * varied sources: + * <ul> + * <li>{@link Bitmap} - see {@link #ImageSpan(Context, Bitmap)} and + * {@link #ImageSpan(Context, Bitmap, int)} + * </li> + * <li>{@link Drawable} - see {@link #ImageSpan(Drawable, int)}</li> + * <li>resource id - see {@link #ImageSpan(Context, int, int)}</li> + * <li>{@link Uri} - see {@link #ImageSpan(Context, Uri, int)}</li> + * </ul> + * The default value for the vertical alignment is {@link DynamicDrawableSpan#ALIGN_BOTTOM} + * <p> + * For example, an <code>ImagedSpan</code> can be used like this: + * <pre> + * SpannableString string = SpannableString("Bottom: span.\nBaseline: span."); + * // using the default alignment: ALIGN_BOTTOM + * string.setSpan(ImageSpan(this, R.mipmap.ic_launcher), 7, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + * string.setSpan(ImageSpan(this, R.mipmap.ic_launcher, DynamicDrawableSpan.ALIGN_BASELINE), + * 22, 23, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + * </pre> + * <img src="{@docRoot}reference/android/images/text/style/imagespan.png" /> + * <figcaption>Text with <code>ImageSpan</code>s aligned bottom and baseline.</figcaption> + */ public class ImageSpan extends DynamicDrawableSpan { + + @Nullable private Drawable mDrawable; + @Nullable private Uri mContentUri; + @DrawableRes private int mResourceId; + @Nullable private Context mContext; + @Nullable private String mSource; /** * @deprecated Use {@link #ImageSpan(Context, Bitmap)} instead. */ @Deprecated - public ImageSpan(Bitmap b) { + public ImageSpan(@NonNull Bitmap b) { this(null, b, ALIGN_BOTTOM); } @@ -46,80 +79,143 @@ public class ImageSpan extends DynamicDrawableSpan { * @deprecated Use {@link #ImageSpan(Context, Bitmap, int)} instead. */ @Deprecated - public ImageSpan(Bitmap b, int verticalAlignment) { + public ImageSpan(@NonNull Bitmap b, int verticalAlignment) { this(null, b, verticalAlignment); } - public ImageSpan(Context context, Bitmap b) { - this(context, b, ALIGN_BOTTOM); + /** + * Constructs an {@link ImageSpan} from a {@link Context} and a {@link Bitmap} with the default + * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} + * + * @param context context used to create a drawable from {@param bitmap} based on the display + * metrics of the resources + * @param bitmap bitmap to be rendered + */ + public ImageSpan(@NonNull Context context, @NonNull Bitmap bitmap) { + this(context, bitmap, ALIGN_BOTTOM); } /** + * Constructs an {@link ImageSpan} from a {@link Context}, a {@link Bitmap} and a vertical + * alignment. + * + * @param context context used to create a drawable from {@param bitmap} based on + * the display metrics of the resources + * @param bitmap bitmap to be rendered * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or - * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ - public ImageSpan(Context context, Bitmap b, int verticalAlignment) { + public ImageSpan(@NonNull Context context, @NonNull Bitmap bitmap, int verticalAlignment) { super(verticalAlignment); mContext = context; mDrawable = context != null - ? new BitmapDrawable(context.getResources(), b) - : new BitmapDrawable(b); + ? new BitmapDrawable(context.getResources(), bitmap) + : new BitmapDrawable(bitmap); int width = mDrawable.getIntrinsicWidth(); int height = mDrawable.getIntrinsicHeight(); - mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0); + mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0); } - public ImageSpan(Drawable d) { - this(d, ALIGN_BOTTOM); + /** + * Constructs an {@link ImageSpan} from a drawable with the default + * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}. + * + * @param drawable drawable to be rendered + */ + public ImageSpan(@NonNull Drawable drawable) { + this(drawable, ALIGN_BOTTOM); } /** + * Constructs an {@link ImageSpan} from a drawable and a vertical alignment. + * + * @param drawable drawable to be rendered * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or - * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ - public ImageSpan(Drawable d, int verticalAlignment) { + public ImageSpan(@NonNull Drawable drawable, int verticalAlignment) { super(verticalAlignment); - mDrawable = d; + mDrawable = drawable; } - public ImageSpan(Drawable d, String source) { - this(d, source, ALIGN_BOTTOM); + /** + * Constructs an {@link ImageSpan} from a drawable and a source with the default + * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} + * + * @param drawable drawable to be rendered + * @param source drawable's Uri source + */ + public ImageSpan(@NonNull Drawable drawable, @NonNull String source) { + this(drawable, source, ALIGN_BOTTOM); } /** + * Constructs an {@link ImageSpan} from a drawable, a source and a vertical alignment. + * + * @param drawable drawable to be rendered + * @param source drawable's uri source * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or - * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ - public ImageSpan(Drawable d, String source, int verticalAlignment) { + public ImageSpan(@NonNull Drawable drawable, @NonNull String source, int verticalAlignment) { super(verticalAlignment); - mDrawable = d; + mDrawable = drawable; mSource = source; } - public ImageSpan(Context context, Uri uri) { + /** + * Constructs an {@link ImageSpan} from a {@link Context} and a {@link Uri} with the default + * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}. The Uri source can be retrieved via + * {@link #getSource()} + * + * @param context context used to create a drawable from {@param bitmap} based on the display + * metrics of the resources + * @param uri {@link Uri} used to construct the drawable that will be rendered + */ + public ImageSpan(@NonNull Context context, @NonNull Uri uri) { this(context, uri, ALIGN_BOTTOM); } /** + * Constructs an {@link ImageSpan} from a {@link Context}, a {@link Uri} and a vertical + * alignment. The Uri source can be retrieved via {@link #getSource()} + * + * @param context context used to create a drawable from {@param bitmap} based on + * the display + * metrics of the resources + * @param uri {@link Uri} used to construct the drawable that will be rendered. * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or - * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ - public ImageSpan(Context context, Uri uri, int verticalAlignment) { + public ImageSpan(@NonNull Context context, @NonNull Uri uri, int verticalAlignment) { super(verticalAlignment); mContext = context; mContentUri = uri; mSource = uri.toString(); } - public ImageSpan(Context context, @DrawableRes int resourceId) { + /** + * Constructs an {@link ImageSpan} from a {@link Context} and a resource id with the default + * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} + * + * @param context context used to retrieve the drawable from resources + * @param resourceId drawable resource id based on which the drawable is retrieved + */ + public ImageSpan(@NonNull Context context, @DrawableRes int resourceId) { this(context, resourceId, ALIGN_BOTTOM); } /** + * Constructs an {@link ImageSpan} from a {@link Context}, a resource id and a vertical + * alignment. + * + * @param context context used to retrieve the drawable from resources + * @param resourceId drawable resource id based on which the drawable is retrieved. * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or - * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ - public ImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment) { + public ImageSpan(@NonNull Context context, @DrawableRes int resourceId, + int verticalAlignment) { super(verticalAlignment); mContext = context; mResourceId = resourceId; @@ -128,10 +224,10 @@ public class ImageSpan extends DynamicDrawableSpan { @Override public Drawable getDrawable() { Drawable drawable = null; - + if (mDrawable != null) { drawable = mDrawable; - } else if (mContentUri != null) { + } else if (mContentUri != null) { Bitmap bitmap = null; try { InputStream is = mContext.getContentResolver().openInputStream( @@ -142,7 +238,7 @@ public class ImageSpan extends DynamicDrawableSpan { drawable.getIntrinsicHeight()); is.close(); } catch (Exception e) { - Log.e("sms", "Failed to loaded content " + mContentUri, e); + Log.e("ImageSpan", "Failed to loaded content " + mContentUri, e); } } else { try { @@ -150,8 +246,8 @@ public class ImageSpan extends DynamicDrawableSpan { drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); } catch (Exception e) { - Log.e("sms", "Unable to find resource: " + mResourceId); - } + Log.e("ImageSpan", "Unable to find resource: " + mResourceId); + } } return drawable; @@ -159,9 +255,12 @@ public class ImageSpan extends DynamicDrawableSpan { /** * Returns the source string that was saved during construction. + * + * @return the source string that was saved during construction + * @see #ImageSpan(Drawable, String) and this{@link #ImageSpan(Context, Uri)} */ + @Nullable public String getSource() { return mSource; } - } diff --git a/core/java/android/text/style/LineHeightSpan.java b/core/java/android/text/style/LineHeightSpan.java index 1ebee82c18a8..50ee5f387595 100644 --- a/core/java/android/text/style/LineHeightSpan.java +++ b/core/java/android/text/style/LineHeightSpan.java @@ -19,16 +19,42 @@ package android.text.style; import android.graphics.Paint; import android.text.TextPaint; -public interface LineHeightSpan -extends ParagraphStyle, WrapTogetherSpan -{ +/** + * The classes that affect the height of the line should implement this interface. + */ +public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan { + /** + * Classes that implement this should define how the height is being calculated. + * + * @param text the text + * @param start the start of the line + * @param end the end of the line + * @param spanstartv the start of the span + * @param lineHeight the line height + * @param fm font metrics of the paint, in integers + */ public void chooseHeight(CharSequence text, int start, int end, - int spanstartv, int v, - Paint.FontMetricsInt fm); + int spanstartv, int lineHeight, + Paint.FontMetricsInt fm); + /** + * The classes that affect the height of the line with respect to density, should implement this + * interface. + */ public interface WithDensity extends LineHeightSpan { + + /** + * Classes that implement this should define how the height is being calculated. + * + * @param text the text + * @param start the start of the line + * @param end the end of the line + * @param spanstartv the start of the span + * @param lineHeight the line height + * @param paint the paint + */ public void chooseHeight(CharSequence text, int start, int end, - int spanstartv, int v, - Paint.FontMetricsInt fm, TextPaint paint); + int spanstartv, int lineHeight, + Paint.FontMetricsInt fm, TextPaint paint); } } diff --git a/core/java/android/text/style/MaskFilterSpan.java b/core/java/android/text/style/MaskFilterSpan.java index 2ff52a8f70e8..d76ef941d42e 100644 --- a/core/java/android/text/style/MaskFilterSpan.java +++ b/core/java/android/text/style/MaskFilterSpan.java @@ -18,15 +18,36 @@ package android.text.style; import android.graphics.MaskFilter; import android.text.TextPaint; - +/** + * Span that allows setting a {@link MaskFilter} to the text it's attached to. + * <p> + * For example, to blur a text, a {@link android.graphics.BlurMaskFilter} can be used: + * <pre> + * MaskFilter blurMask = new BlurMaskFilter(5f, BlurMaskFilter.Blur.NORMAL); + * SpannableString string = new SpannableString("Text with blur mask"); + * string.setSpan(new MaskFilterSpan(blurMask), 10, 15, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + * </pre> + * <img src="{@docRoot}reference/android/images/text/style/maskfilterspan.png" /> + * <figcaption>Text blurred with the <code>MaskFilterSpan</code>.</figcaption> + */ public class MaskFilterSpan extends CharacterStyle implements UpdateAppearance { private MaskFilter mFilter; + /** + * Creates a {@link MaskFilterSpan} from a {@link MaskFilter}. + * + * @param filter the filter to be applied to the <code>TextPaint</code> + */ public MaskFilterSpan(MaskFilter filter) { mFilter = filter; } + /** + * Return the mask filter for this span. + * + * @return the mask filter for this span + */ public MaskFilter getMaskFilter() { return mFilter; } diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java index f900db502d0d..bdfa700215f8 100644 --- a/core/java/android/text/style/StyleSpan.java +++ b/core/java/android/text/style/StyleSpan.java @@ -16,6 +16,7 @@ package android.text.style; +import android.annotation.NonNull; import android.graphics.Paint; import android.graphics.Typeface; import android.os.Parcel; @@ -24,55 +25,76 @@ import android.text.TextPaint; import android.text.TextUtils; /** - * - * Describes a style in a span. + * Span that allows setting the style of the text it's attached to. + * Possible styles are: {@link Typeface#NORMAL}, {@link Typeface#BOLD}, {@link Typeface#ITALIC} and + * {@link Typeface#BOLD_ITALIC}. + * <p> * Note that styles are cumulative -- if both bold and italic are set in * separate spans, or if the base style is bold and a span calls for italic, * you get bold italic. You can't turn off a style from the base style. - * + * <p> + * For example, the <code>StyleSpan</code> can be used like this: + * <pre> + * SpannableString string = new SpannableString("Bold and italic text"); + * string.setSpan(new StyleSpan(Typeface.BOLD), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + * string.setSpan(new StyleSpan(Typeface.ITALIC), 9, 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + * </pre> + * <img src="{@docRoot}reference/android/images/text/style/stylespan.png" /> + * <figcaption>Text styled bold and italic with the <code>StyleSpan</code>.</figcaption> */ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { private final int mStyle; /** - * + * Creates a {@link StyleSpan} from a style. + * * @param style An integer constant describing the style for this span. Examples - * include bold, italic, and normal. Values are constants defined - * in {@link android.graphics.Typeface}. + * include bold, italic, and normal. Values are constants defined + * in {@link Typeface}. */ public StyleSpan(int style) { mStyle = style; } - public StyleSpan(Parcel src) { + /** + * Creates a {@link StyleSpan} from a parcel. + * + * @param src the parcel + */ + public StyleSpan(@NonNull Parcel src) { mStyle = src.readInt(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.STYLE_SPAN; } - + + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mStyle); } /** - * Returns the style constant defined in {@link android.graphics.Typeface}. + * Returns the style constant defined in {@link Typeface}. */ public int getStyle() { return mStyle; diff --git a/core/java/android/text/style/TabStopSpan.java b/core/java/android/text/style/TabStopSpan.java index 056642841815..2cceb2c8ced9 100644 --- a/core/java/android/text/style/TabStopSpan.java +++ b/core/java/android/text/style/TabStopSpan.java @@ -16,39 +16,54 @@ package android.text.style; +import android.annotation.IntRange; +import android.annotation.Px; + /** - * Represents a single tab stop on a line. + * Paragraph affecting span that changes the position of the tab with respect to + * the leading margin of the line. <code>TabStopSpan</code> will only affect the first tab + * encountered on the first line of the text. */ -public interface TabStopSpan -extends ParagraphStyle -{ +public interface TabStopSpan extends ParagraphStyle { + /** - * Returns the offset of the tab stop from the leading margin of the - * line. - * @return the offset + * Returns the offset of the tab stop from the leading margin of the line, in pixels. + * + * @return the offset, in pixels */ - public int getTabStop(); + int getTabStop(); /** - * The default implementation of TabStopSpan. + * The default implementation of TabStopSpan that allows setting the offset of the tab stop + * from the leading margin of the first line of text. + * <p> + * Let's consider that we have the following text: <i>"\tParagraph text beginning with tab."</i> + * and we want to move the tab stop with 100px. This can be achieved like this: + * <pre> + * SpannableString string = new SpannableString("\tParagraph text beginning with tab."); + * string.setSpan(new TabStopSpan.Standard(100), 0, string.length(), + * Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);</pre> + * <img src="{@docRoot}reference/android/images/text/style/tabstopspan.png" /> + * <figcaption>Text with a tab stop and a <code>TabStopSpan</code></figcaption> */ - public static class Standard - implements TabStopSpan - { + class Standard implements TabStopSpan { + + @Px + private int mTabOffset; + /** - * Constructor. + * Constructs a {@link TabStopSpan.Standard} based on an offset. * - * @param where the offset of the tab stop from the leading margin of - * the line + * @param offset the offset of the tab stop from the leading margin of + * the line, in pixels */ - public Standard(int where) { - mTab = where; + public Standard(@IntRange(from = 0) int offset) { + mTabOffset = offset; } + @Override public int getTabStop() { - return mTab; + return mTabOffset; } - - private int mTab; } } diff --git a/core/java/android/text/style/TypefaceSpan.java b/core/java/android/text/style/TypefaceSpan.java index aa622d87c74e..162281250208 100644 --- a/core/java/android/text/style/TypefaceSpan.java +++ b/core/java/android/text/style/TypefaceSpan.java @@ -16,6 +16,7 @@ package android.text.style; +import android.annotation.NonNull; import android.graphics.Paint; import android.graphics.Typeface; import android.os.Parcel; @@ -24,42 +25,59 @@ import android.text.TextPaint; import android.text.TextUtils; /** - * Changes the typeface family of the text to which the span is attached. + * Changes the typeface family of the text to which the span is attached. Examples of typeface + * family include "monospace", "serif", and "sans-serif". + * <p> + * For example, change the typeface of a text to "monospace" like this: + * <pre> + * SpannableString string = new SpannableString("Text with typeface span"); + * string.setSpan(new TypefaceSpan("monospace"), 10, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + * </pre> + * <img src="{@docRoot}reference/android/images/text/style/typefacespan.png" /> + * <figcaption>Text with "monospace" typeface family.</figcaption> */ public class TypefaceSpan extends MetricAffectingSpan implements ParcelableSpan { + private final String mFamily; /** - * @param family The font family for this typeface. Examples include - * "monospace", "serif", and "sans-serif". + * Constructs a {@link TypefaceSpan} based on a font family. + * + * @param family The font family for this typeface. Examples include + * "monospace", "serif", and "sans-serif". */ public TypefaceSpan(String family) { mFamily = family; } - public TypefaceSpan(Parcel src) { + public TypefaceSpan(@NonNull Parcel src) { mFamily = src.readString(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.TYPEFACE_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeString(mFamily); } @@ -71,16 +89,16 @@ public class TypefaceSpan extends MetricAffectingSpan implements ParcelableSpan } @Override - public void updateDrawState(TextPaint ds) { - apply(ds, mFamily); + public void updateDrawState(@NonNull TextPaint textPaint) { + apply(textPaint, mFamily); } @Override - public void updateMeasureState(TextPaint paint) { - apply(paint, mFamily); + public void updateMeasureState(@NonNull TextPaint textPaint) { + apply(textPaint, mFamily); } - private static void apply(Paint paint, String family) { + private static void apply(@NonNull Paint paint, String family) { int oldStyle; Typeface old = paint.getTypeface(); diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java index 58239efe5c5f..eab1ef4f6afd 100644 --- a/core/java/android/text/style/URLSpan.java +++ b/core/java/android/text/style/URLSpan.java @@ -16,6 +16,7 @@ package android.text.style; +import android.annotation.NonNull; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -27,40 +28,71 @@ import android.text.TextUtils; import android.util.Log; import android.view.View; +/** + * Implementation of the {@link ClickableSpan} that allows setting a url string. When + * selecting and clicking on the text to which the span is attached, the <code>URLSpan</code> + * will try to open the url, by launching an an Activity with an {@link Intent#ACTION_VIEW} intent. + * <p> + * For example, a <code>URLSpan</code> can be used like this: + * <pre> + * SpannableString string = new SpannableString("Text with a url span"); + * string.setSpan(new URLSpan("http://www.developer.android.com"), 12, 15, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + * </pre> + * <img src="{@docRoot}reference/android/images/text/style/urlspan.png" /> + * <figcaption>Text with <code>URLSpan</code>.</figcaption> + */ public class URLSpan extends ClickableSpan implements ParcelableSpan { private final String mURL; + /** + * Constructs a {@link URLSpan} from a url string. + * + * @param url the url string + */ public URLSpan(String url) { mURL = url; } - public URLSpan(Parcel src) { + /** + * Constructs a {@link URLSpan} from a parcel. + */ + public URLSpan(@NonNull Parcel src) { mURL = src.readString(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.URL_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeString(mURL); } + /** + * Get the url string for this span. + * + * @return the url string. + */ public String getURL() { return mURL; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3cbd2754c9ce..c3e9e7387732 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -23689,9 +23689,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Point shadowTouchPoint = new Point(); shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint); - if ((shadowSize.x <= 0) || (shadowSize.y <= 0) + if ((shadowSize.x < 0) || (shadowSize.y < 0) || (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) { - throw new IllegalStateException("Drag shadow dimensions must be positive"); + throw new IllegalStateException("Drag shadow dimensions must not be negative"); + } + + // Create 1x1 surface when zero surface size is specified because SurfaceControl.Builder + // does not accept zero size surface. + if (shadowSize.x == 0 || shadowSize.y == 0) { + shadowSize.x = 1; + shadowSize.y = 1; } if (ViewDebug.DEBUG_DRAG) { diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java index 2e4cccfc230f..e58e62f972c6 100644 --- a/core/java/android/widget/MediaControlView2.java +++ b/core/java/android/widget/MediaControlView2.java @@ -19,12 +19,11 @@ package android.widget; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.content.Context; import android.media.session.MediaController; import android.media.update.ApiLoader; -import android.media.update.FrameLayoutHelper; import android.media.update.MediaControlView2Provider; +import android.media.update.ViewGroupHelper; import android.util.AttributeSet; import java.lang.annotation.Retention; @@ -62,7 +61,7 @@ import java.lang.annotation.RetentionPolicy; * TODO PUBLIC API * @hide */ -public class MediaControlView2 extends FrameLayoutHelper<MediaControlView2Provider> { +public class MediaControlView2 extends ViewGroupHelper<MediaControlView2Provider> { /** @hide */ @IntDef({ BUTTON_PLAY_PAUSE, @@ -156,18 +155,12 @@ public class MediaControlView2 extends FrameLayoutHelper<MediaControlView2Provid public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super((instance, superProvider) -> + super((instance, superProvider, privateProvider) -> ApiLoader.getProvider(context).createMediaControlView2( - (MediaControlView2) instance, superProvider), + (MediaControlView2) instance, superProvider, privateProvider, + attrs, defStyleAttr, defStyleRes), context, attrs, defStyleAttr, defStyleRes); - } - - /** - * @hide - */ - @SystemApi - public MediaControlView2Provider getProvider() { - return mProvider; + mProvider.initialize(attrs, defStyleAttr, defStyleRes); } /** @@ -178,13 +171,6 @@ public class MediaControlView2 extends FrameLayoutHelper<MediaControlView2Provid } /** - * Returns whether the control view is currently shown or hidden. - */ - public boolean isShowing() { - return mProvider.isShowing_impl(); - } - - /** * Changes the visibility state of an individual button. Default value is View.Visible. * * @param button the {@code Button} assigned to individual buttons @@ -232,9 +218,7 @@ public class MediaControlView2 extends FrameLayoutHelper<MediaControlView2Provid } @Override - // TODO Move this method to ViewProvider - public void onVisibilityAggregated(boolean isVisible) { - - mProvider.onVisibilityAggregated_impl(isVisible); + protected void onLayout(boolean changed, int l, int t, int r, int b) { + mProvider.onLayout_impl(changed, l, t, r, b); } } diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java index 78ca0114b798..30811803bf9a 100644 --- a/core/java/android/widget/VideoView2.java +++ b/core/java/android/widget/VideoView2.java @@ -27,8 +27,8 @@ import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.media.update.ApiLoader; -import android.media.update.FrameLayoutHelper; import android.media.update.VideoView2Provider; +import android.media.update.ViewGroupHelper; import android.net.Uri; import android.os.Bundle; import android.util.AttributeSet; @@ -101,7 +101,7 @@ import java.util.concurrent.Executor; * * @hide */ -public class VideoView2 extends FrameLayoutHelper<VideoView2Provider> { +public class VideoView2 extends ViewGroupHelper<VideoView2Provider> { /** @hide */ @IntDef({ VIEW_TYPE_TEXTUREVIEW, @@ -139,10 +139,12 @@ public class VideoView2 extends FrameLayoutHelper<VideoView2Provider> { public VideoView2( @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super((instance, superProvider) -> + super((instance, superProvider, privateProvider) -> ApiLoader.getProvider(context).createVideoView2( - (VideoView2) instance, superProvider, attrs, defStyleAttr, defStyleRes), + (VideoView2) instance, superProvider, privateProvider, + attrs, defStyleAttr, defStyleRes), context, attrs, defStyleAttr, defStyleRes); + mProvider.initialize(attrs, defStyleAttr, defStyleRes); } /** @@ -487,4 +489,9 @@ public class VideoView2 extends FrameLayoutHelper<VideoView2Provider> { */ void onCustomAction(String action, Bundle extras); } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + mProvider.onLayout_impl(changed, l, t, r, b); + } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 7def87655ae3..40dcf25bbd10 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -3880,7 +3880,8 @@ public class BatteryStatsImpl extends BatteryStats { public void addIsolatedUidLocked(int isolatedUid, int appUid) { mIsolatedUids.put(isolatedUid, appUid); - StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, 1); + StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, + StatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED); final Uid u = getUidStatsLocked(appUid); u.addIsolatedUid(isolatedUid); } @@ -3904,7 +3905,8 @@ public class BatteryStatsImpl extends BatteryStats { */ public void removeIsolatedUidLocked(int isolatedUid) { StatsLog.write( - StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0); + StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), + isolatedUid, StatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED); final int idx = mIsolatedUids.indexOfKey(isolatedUid); if (idx >= 0) { final int ownerUid = mIsolatedUids.valueAt(idx); @@ -4255,10 +4257,12 @@ public class BatteryStatsImpl extends BatteryStats { if (wc != null) { StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), - getPowerManagerWakeLockLevel(type), name, 1); + getPowerManagerWakeLockLevel(type), name, + StatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE); } else { StatsLog.write_non_chained(StatsLog.WAKELOCK_STATE_CHANGED, uid, null, - getPowerManagerWakeLockLevel(type), name, 1); + getPowerManagerWakeLockLevel(type), name, + StatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE); } } } @@ -4298,10 +4302,12 @@ public class BatteryStatsImpl extends BatteryStats { getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime); if (wc != null) { StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), - getPowerManagerWakeLockLevel(type), name, 0); + getPowerManagerWakeLockLevel(type), name, + StatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE); } else { StatsLog.write_non_chained(StatsLog.WAKELOCK_STATE_CHANGED, uid, null, - getPowerManagerWakeLockLevel(type), name, 0); + getPowerManagerWakeLockLevel(type), name, + StatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE); } } } @@ -4426,7 +4432,8 @@ public class BatteryStatsImpl extends BatteryStats { public void noteLongPartialWakelockStart(String name, String historyName, int uid) { StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - uid, null, name, historyName, 1); + uid, null, name, historyName, + StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__ON); uid = mapUid(uid); noteLongPartialWakeLockStartInternal(name, historyName, uid); @@ -4439,7 +4446,8 @@ public class BatteryStatsImpl extends BatteryStats { final int uid = mapUid(workSource.get(i)); noteLongPartialWakeLockStartInternal(name, historyName, uid); StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - workSource.get(i), workSource.getName(i), name, historyName, 1); + workSource.get(i), workSource.getName(i), name, historyName, + StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__ON); } final ArrayList<WorkChain> workChains = workSource.getWorkChains(); @@ -4450,7 +4458,8 @@ public class BatteryStatsImpl extends BatteryStats { noteLongPartialWakeLockStartInternal(name, historyName, uid); StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), name, historyName, 1); + workChain.getUids(), workChain.getTags(), name, historyName, + StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__ON); } } } @@ -4470,8 +4479,8 @@ public class BatteryStatsImpl extends BatteryStats { } public void noteLongPartialWakelockFinish(String name, String historyName, int uid) { - StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - uid, null, name, historyName, 0); + StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, null, + name, historyName, StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__OFF); uid = mapUid(uid); noteLongPartialWakeLockFinishInternal(name, historyName, uid); @@ -4484,7 +4493,8 @@ public class BatteryStatsImpl extends BatteryStats { final int uid = mapUid(workSource.get(i)); noteLongPartialWakeLockFinishInternal(name, historyName, uid); StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - workSource.get(i), workSource.getName(i), name, historyName, 0); + workSource.get(i), workSource.getName(i), name, historyName, + StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__OFF); } final ArrayList<WorkChain> workChains = workSource.getWorkChains(); @@ -4494,7 +4504,8 @@ public class BatteryStatsImpl extends BatteryStats { final int uid = workChain.getAttributionUid(); noteLongPartialWakeLockFinishInternal(name, historyName, uid); StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), name, historyName, 0); + workChain.getUids(), workChain.getTags(), name, historyName, + StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__OFF); } } } @@ -4518,7 +4529,8 @@ public class BatteryStatsImpl extends BatteryStats { long deltaUptime = uptimeMs - mLastWakeupUptimeMs; SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason); timer.add(deltaUptime * 1000, 1); // time in in microseconds - StatsLog.write(StatsLog.KERNEL_WAKEUP_REPORTED, mLastWakeupReason, deltaUptime * 1000); + StatsLog.write(StatsLog.KERNEL_WAKEUP_REPORTED, mLastWakeupReason, + /* duration_usec */ deltaUptime * 1000); mLastWakeupReason = null; } } @@ -4659,10 +4671,12 @@ public class BatteryStatsImpl extends BatteryStats { mGpsNesting++; if (workChain == null) { - StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, uid, null, 1); + StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, uid, null, + StatsLog.GPS_SCAN_STATE_CHANGED__STATE__ON); } else { StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), 1); + workChain.getUids(), workChain.getTags(), + StatsLog.GPS_SCAN_STATE_CHANGED__STATE__ON); } getUidStatsLocked(uid).noteStartGps(elapsedRealtime); @@ -4683,10 +4697,11 @@ public class BatteryStatsImpl extends BatteryStats { } if (workChain == null) { - StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, uid, null, 0); + StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, uid, null, + StatsLog.GPS_SCAN_STATE_CHANGED__STATE__OFF); } else { StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, workChain.getUids(), - workChain.getTags(), 0); + workChain.getTags(), StatsLog.GPS_SCAN_STATE_CHANGED__STATE__OFF); } getUidStatsLocked(uid).noteStopGps(elapsedRealtime); @@ -4941,7 +4956,9 @@ public class BatteryStatsImpl extends BatteryStats { mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtime); } addHistoryRecordLocked(elapsedRealtime, uptime); - StatsLog.write(StatsLog.BATTERY_SAVER_MODE_STATE_CHANGED, enabled ? 1 : 0); + StatsLog.write(StatsLog.BATTERY_SAVER_MODE_STATE_CHANGED, enabled ? + StatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__ON : + StatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__OFF); } } @@ -5545,16 +5562,19 @@ public class BatteryStatsImpl extends BatteryStats { if (workChain != null) { StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), 1); + workChain.getUids(), workChain.getTags(), + StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON); if (isUnoptimized) { StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), 1); + workChain.getUids(), workChain.getTags(), + StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED__STATE__ON); } } else { - StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, 1); + StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, + StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON); if (isUnoptimized) { StatsLog.write_non_chained(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, null, - 1); + StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED__STATE__ON); } } @@ -5594,16 +5614,19 @@ public class BatteryStatsImpl extends BatteryStats { if (workChain != null) { StatsLog.write( - StatsLog.BLE_SCAN_STATE_CHANGED, workChain.getUids(), workChain.getTags(), 0); + StatsLog.BLE_SCAN_STATE_CHANGED, workChain.getUids(), workChain.getTags(), + StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF); if (isUnoptimized) { StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), 0); + workChain.getUids(), workChain.getTags(), + StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED__STATE__OFF); } } else { - StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, 0); + StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, + StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF); if (isUnoptimized) { StatsLog.write_non_chained(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, null, - 0); + StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED__STATE__OFF); } } @@ -5656,7 +5679,8 @@ public class BatteryStatsImpl extends BatteryStats { for (int j = 0; j < allWorkChains.size(); ++j) { StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, allWorkChains.get(j).getUids(), - allWorkChains.get(j).getTags(), 0); + allWorkChains.get(j).getTags(), + StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF); } allWorkChains.clear(); } @@ -5666,7 +5690,8 @@ public class BatteryStatsImpl extends BatteryStats { for (int j = 0; j < unoptimizedWorkChains.size(); ++j) { StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, unoptimizedWorkChains.get(j).getUids(), - unoptimizedWorkChains.get(j).getTags(), 0); + unoptimizedWorkChains.get(j).getTags(), + StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED__STATE__OFF); } unoptimizedWorkChains.clear(); } @@ -6011,7 +6036,8 @@ public class BatteryStatsImpl extends BatteryStats { for (int i=0; i<N; i++) { final int uid = mapUid(ws.get(i)); noteFullWifiLockAcquiredLocked(uid); - StatsLog.write_non_chained(StatsLog.WIFI_LOCK_STATE_CHANGED, ws.get(i), ws.getName(i), 1); + StatsLog.write_non_chained(StatsLog.WIFI_LOCK_STATE_CHANGED, ws.get(i), ws.getName(i), + StatsLog.WIFI_LOCK_STATE_CHANGED__STATE__ON); } final List<WorkChain> workChains = ws.getWorkChains(); @@ -6021,7 +6047,8 @@ public class BatteryStatsImpl extends BatteryStats { final int uid = mapUid(workChain.getAttributionUid()); noteFullWifiLockAcquiredLocked(uid); StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), 1); + workChain.getUids(), workChain.getTags(), + StatsLog.WIFI_LOCK_STATE_CHANGED__STATE__ON); } } } @@ -6031,7 +6058,8 @@ public class BatteryStatsImpl extends BatteryStats { for (int i=0; i<N; i++) { final int uid = mapUid(ws.get(i)); noteFullWifiLockReleasedLocked(uid); - StatsLog.write_non_chained(StatsLog.WIFI_LOCK_STATE_CHANGED, ws.get(i), ws.getName(i), 0); + StatsLog.write_non_chained(StatsLog.WIFI_LOCK_STATE_CHANGED, ws.get(i), ws.getName(i), + StatsLog.WIFI_LOCK_STATE_CHANGED__STATE__OFF); } final List<WorkChain> workChains = ws.getWorkChains(); @@ -6041,7 +6069,8 @@ public class BatteryStatsImpl extends BatteryStats { final int uid = mapUid(workChain.getAttributionUid()); noteFullWifiLockReleasedLocked(uid); StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), 0); + workChain.getUids(), workChain.getTags(), + StatsLog.WIFI_LOCK_STATE_CHANGED__STATE__OFF); } } } @@ -6052,7 +6081,7 @@ public class BatteryStatsImpl extends BatteryStats { final int uid = mapUid(ws.get(i)); noteWifiScanStartedLocked(uid); StatsLog.write_non_chained(StatsLog.WIFI_SCAN_STATE_CHANGED, ws.get(i), ws.getName(i), - 1); + StatsLog.WIFI_SCAN_STATE_CHANGED__STATE__ON); } final List<WorkChain> workChains = ws.getWorkChains(); @@ -6062,7 +6091,7 @@ public class BatteryStatsImpl extends BatteryStats { final int uid = mapUid(workChain.getAttributionUid()); noteWifiScanStartedLocked(uid); StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, workChain.getUids(), - workChain.getTags(), 1); + workChain.getTags(), StatsLog.WIFI_SCAN_STATE_CHANGED__STATE__ON); } } } @@ -6073,7 +6102,7 @@ public class BatteryStatsImpl extends BatteryStats { final int uid = mapUid(ws.get(i)); noteWifiScanStoppedLocked(uid); StatsLog.write_non_chained(StatsLog.WIFI_SCAN_STATE_CHANGED, ws.get(i), ws.getName(i), - 0); + StatsLog.WIFI_SCAN_STATE_CHANGED__STATE__OFF); } final List<WorkChain> workChains = ws.getWorkChains(); @@ -6083,7 +6112,8 @@ public class BatteryStatsImpl extends BatteryStats { final int uid = mapUid(workChain.getAttributionUid()); noteWifiScanStoppedLocked(uid); StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), 0); + workChain.getUids(), workChain.getTags(), + StatsLog.WIFI_SCAN_STATE_CHANGED__STATE__OFF); } } } @@ -7035,7 +7065,8 @@ public class BatteryStatsImpl extends BatteryStats { } mWifiMulticastTimer.startRunningLocked(elapsedRealtimeMs); StatsLog.write_non_chained( - StatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, getUid(), null, 1); + StatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, getUid(), null, + StatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED__STATE__ON); } } @@ -7045,7 +7076,8 @@ public class BatteryStatsImpl extends BatteryStats { mWifiMulticastEnabled = false; mWifiMulticastTimer.stopRunningLocked(elapsedRealtimeMs); StatsLog.write_non_chained( - StatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, getUid(), null, 0); + StatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, getUid(), null, + StatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED__STATE__OFF); } } @@ -7098,14 +7130,16 @@ public class BatteryStatsImpl extends BatteryStats { public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) { createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 1); + StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, + StatsLog.AUDIO_STATE_CHANGED__STATE__ON); } public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) { if (mAudioTurnedOnTimer != null) { mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mAudioTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped - StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 0); + StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, + StatsLog.AUDIO_STATE_CHANGED__STATE__OFF); } } } @@ -7113,7 +7147,8 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetAudioLocked(long elapsedRealtimeMs) { if (mAudioTurnedOnTimer != null) { mAudioTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); - StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 0); + StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, + StatsLog.AUDIO_STATE_CHANGED__STATE__OFF); } } @@ -7127,7 +7162,8 @@ public class BatteryStatsImpl extends BatteryStats { public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) { createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), null, 1); + StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), null, + StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED__STATE__ON); } public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) { @@ -7135,7 +7171,7 @@ public class BatteryStatsImpl extends BatteryStats { mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mVideoTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), - null, 0); + null, StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED__STATE__OFF); } } } @@ -7144,7 +7180,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mVideoTurnedOnTimer != null) { mVideoTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), null, - 0); + StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED__STATE__OFF); } } @@ -7158,7 +7194,8 @@ public class BatteryStatsImpl extends BatteryStats { public void noteFlashlightTurnedOnLocked(long elapsedRealtimeMs) { createFlashlightTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null,1); + StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null, + StatsLog.FLASHLIGHT_STATE_CHANGED__STATE__ON); } public void noteFlashlightTurnedOffLocked(long elapsedRealtimeMs) { @@ -7166,7 +7203,7 @@ public class BatteryStatsImpl extends BatteryStats { mFlashlightTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mFlashlightTurnedOnTimer.isRunningLocked()) { StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null, - 0); + StatsLog.FLASHLIGHT_STATE_CHANGED__STATE__OFF); } } } @@ -7174,7 +7211,8 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetFlashlightLocked(long elapsedRealtimeMs) { if (mFlashlightTurnedOnTimer != null) { mFlashlightTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); - StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null, 0); + StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null, + StatsLog.FLASHLIGHT_STATE_CHANGED__STATE__OFF); } } @@ -7188,14 +7226,16 @@ public class BatteryStatsImpl extends BatteryStats { public void noteCameraTurnedOnLocked(long elapsedRealtimeMs) { createCameraTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 1); + StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, + StatsLog.CAMERA_STATE_CHANGED__STATE__ON); } public void noteCameraTurnedOffLocked(long elapsedRealtimeMs) { if (mCameraTurnedOnTimer != null) { mCameraTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mCameraTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped - StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 0); + StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, + StatsLog.CAMERA_STATE_CHANGED__STATE__OFF); } } } @@ -7203,7 +7243,8 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetCameraLocked(long elapsedRealtimeMs) { if (mCameraTurnedOnTimer != null) { mCameraTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); - StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 0); + StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, + StatsLog.CAMERA_STATE_CHANGED__STATE__OFF); } } @@ -9777,7 +9818,8 @@ public class BatteryStatsImpl extends BatteryStats { DualTimer t = mSyncStats.startObject(name); if (t != null) { t.startRunningLocked(elapsedRealtimeMs); - StatsLog.write_non_chained(StatsLog.SYNC_STATE_CHANGED, getUid(), null, name, 1); + StatsLog.write_non_chained(StatsLog.SYNC_STATE_CHANGED, getUid(), null, name, + StatsLog.SYNC_STATE_CHANGED__STATE__ON); } } @@ -9786,7 +9828,8 @@ public class BatteryStatsImpl extends BatteryStats { if (t != null) { t.stopRunningLocked(elapsedRealtimeMs); if (!t.isRunningLocked()) { // only tell statsd if truly stopped - StatsLog.write_non_chained(StatsLog.SYNC_STATE_CHANGED, getUid(), null, name, 0); + StatsLog.write_non_chained(StatsLog.SYNC_STATE_CHANGED, getUid(), null, name, + StatsLog.SYNC_STATE_CHANGED__STATE__OFF); } } } @@ -9796,7 +9839,7 @@ public class BatteryStatsImpl extends BatteryStats { if (t != null) { t.startRunningLocked(elapsedRealtimeMs); StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), null, - name, 1); + name, StatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__STARTED); } } @@ -9806,7 +9849,7 @@ public class BatteryStatsImpl extends BatteryStats { t.stopRunningLocked(elapsedRealtimeMs); if (!t.isRunningLocked()) { // only tell statsd if truly stopped StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), null, - name, 0); + name, StatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED); } } if (mBsi.mOnBatteryTimeBase.isRunning()) { @@ -9919,7 +9962,7 @@ public class BatteryStatsImpl extends BatteryStats { t.startRunningLocked(elapsedRealtimeMs); if (sensor != Sensor.GPS) { StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null, sensor, - 1); + StatsLog.SENSOR_STATE_CHANGED__STATE__ON); } } @@ -9930,7 +9973,7 @@ public class BatteryStatsImpl extends BatteryStats { t.stopRunningLocked(elapsedRealtimeMs); if (sensor != Sensor.GPS) { StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null, - sensor, 0); + sensor, StatsLog.SENSOR_STATE_CHANGED__STATE__OFF); } } } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index f9a2341fc3e3..e69a36064693 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -55,6 +55,8 @@ public final class Zygote { public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10; /** Do not enfore hidden API access restrictions. */ public static final int DISABLE_HIDDEN_API_CHECKS = 1 << 11; + /** Force generation of native debugging information for backtraces. */ + public static final int DEBUG_GENERATE_MINI_DEBUG_INFO = 1 << 12; /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; diff --git a/core/java/com/android/internal/util/FileRotator.java b/core/java/com/android/internal/util/FileRotator.java index 71550be1c8d7..f8885a20970d 100644 --- a/core/java/com/android/internal/util/FileRotator.java +++ b/core/java/com/android/internal/util/FileRotator.java @@ -160,7 +160,7 @@ public class FileRotator { final File file = new File(mBasePath, name); final FileInputStream is = new FileInputStream(file); try { - Streams.copy(is, zos); + FileUtils.copy(is, zos); } finally { IoUtils.closeQuietly(is); } diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 5673814ca362..732534ccbcbe 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -66,6 +66,8 @@ interface ILockSettings { void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList); KeyChainSnapshot getKeyChainSnapshot(); byte[] generateAndStoreKey(String alias); + String generateKey(String alias, in byte[] account); + String getKey(String alias); void removeKey(String alias); void setSnapshotCreatedPendingIntent(in PendingIntent intent); Map getRecoverySnapshotVersions(); diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp index c88cf5cff04d..ba56d592acef 100644 --- a/core/jni/android/graphics/AnimatedImageDrawable.cpp +++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp @@ -26,10 +26,11 @@ #include <SkPictureRecorder.h> #include <hwui/AnimatedImageDrawable.h> #include <hwui/Canvas.h> +#include <utils/Looper.h> using namespace android; -static jmethodID gAnimatedImageDrawable_postOnAnimationEndMethodID; +static jmethodID gAnimatedImageDrawable_onAnimationEndMethodID; // Note: jpostProcess holds a handle to the ImageDecoder. static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/, @@ -123,9 +124,9 @@ static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlo return drawable->start(); } -static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { +static jboolean AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); - drawable->stop(); + return drawable->stop(); } // Java's LOOP_INFINITE relies on this being the same. @@ -137,33 +138,63 @@ static void AnimatedImageDrawable_nSetLoopCount(JNIEnv* env, jobject /*clazz*/, drawable->setRepetitionCount(loopCount); } -class JniAnimationEndListener : public OnAnimationEndListener { +class InvokeListener : public MessageHandler { public: - JniAnimationEndListener(JNIEnv* env, jobject javaObject) { + InvokeListener(JNIEnv* env, jobject javaObject) { LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJvm) != JNI_OK); - mJavaObject = env->NewGlobalRef(javaObject); + // Hold a weak reference to break a cycle that would prevent GC. + mWeakRef = env->NewWeakGlobalRef(javaObject); } - ~JniAnimationEndListener() override { + ~InvokeListener() override { auto* env = get_env_or_die(mJvm); - env->DeleteGlobalRef(mJavaObject); + env->DeleteWeakGlobalRef(mWeakRef); } - void onAnimationEnd() override { + virtual void handleMessage(const Message&) override { auto* env = get_env_or_die(mJvm); - env->CallVoidMethod(mJavaObject, gAnimatedImageDrawable_postOnAnimationEndMethodID); + jobject localRef = env->NewLocalRef(mWeakRef); + if (localRef) { + env->CallVoidMethod(localRef, gAnimatedImageDrawable_onAnimationEndMethodID); + } } private: JavaVM* mJvm; - jobject mJavaObject; + jweak mWeakRef; +}; + +class JniAnimationEndListener : public OnAnimationEndListener { +public: + JniAnimationEndListener(sp<Looper>&& looper, JNIEnv* env, jobject javaObject) { + mListener = new InvokeListener(env, javaObject); + mLooper = std::move(looper); + } + + void onAnimationEnd() override { mLooper->sendMessage(mListener, 0); } + +private: + sp<InvokeListener> mListener; + sp<Looper> mLooper; }; static void AnimatedImageDrawable_nSetOnAnimationEndListener(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jobject jdrawable) { auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); - drawable->setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener>( - new JniAnimationEndListener(env, jdrawable))); + if (!jdrawable) { + drawable->setOnAnimationEndListener(nullptr); + } else { + sp<Looper> looper = Looper::getForThread(); + if (!looper.get()) { + doThrowISE(env, + "Must set AnimatedImageDrawable's AnimationCallback on a thread with a " + "looper!"); + return; + } + + drawable->setOnAnimationEndListener( + std::make_unique<JniAnimationEndListener>(std::move(looper), env, jdrawable)); + } } static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { @@ -186,7 +217,7 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = { { "nSetColorFilter", "(JJ)V", (void*) AnimatedImageDrawable_nSetColorFilter }, { "nIsRunning", "(J)Z", (void*) AnimatedImageDrawable_nIsRunning }, { "nStart", "(J)Z", (void*) AnimatedImageDrawable_nStart }, - { "nStop", "(J)V", (void*) AnimatedImageDrawable_nStop }, + { "nStop", "(J)Z", (void*) AnimatedImageDrawable_nStop }, { "nSetLoopCount", "(JI)V", (void*) AnimatedImageDrawable_nSetLoopCount }, { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener }, { "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize }, @@ -195,7 +226,7 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = { int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) { jclass animatedImageDrawable_class = FindClassOrDie(env, "android/graphics/drawable/AnimatedImageDrawable"); - gAnimatedImageDrawable_postOnAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "postOnAnimationEnd", "()V"); + gAnimatedImageDrawable_onAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "onAnimationEnd", "()V"); return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable", gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods)); diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index dd3e6f02e9fe..937b3ffb9d60 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -44,11 +44,9 @@ namespace android { struct NativeFamilyBuilder { NativeFamilyBuilder(uint32_t langId, int variant) - : langId(langId), variant(static_cast<minikin::FontFamily::Variant>(variant)), - allowUnsupportedFont(false) {} + : langId(langId), variant(static_cast<minikin::FontFamily::Variant>(variant)) {} uint32_t langId; minikin::FontFamily::Variant variant; - bool allowUnsupportedFont; std::vector<minikin::Font> fonts; std::vector<minikin::FontVariation> axes; }; @@ -70,22 +68,17 @@ static jlong FontFamily_create(jlong builderPtr) { } std::unique_ptr<NativeFamilyBuilder> builder( reinterpret_cast<NativeFamilyBuilder*>(builderPtr)); + if (builder->fonts.empty()) { + return 0; + } std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>( builder->langId, builder->variant, std::move(builder->fonts)); - if (family->getCoverage().length() == 0 && !builder->allowUnsupportedFont) { + if (family->getCoverage().length() == 0) { return 0; } return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family))); } -static void FontFamily_allowUnsupportedFont(jlong builderPtr) { - if (builderPtr == 0) { - return; - } - NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); - builder->allowUnsupportedFont = true; -} - static void FontFamily_abort(jlong builderPtr) { NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); delete builder; @@ -270,7 +263,6 @@ static void FontFamily_addAxisValue(jlong builderPtr, jint tag, jfloat value) { static const JNINativeMethod gFontFamilyMethods[] = { { "nInitBuilder", "(Ljava/lang/String;I)J", (void*)FontFamily_initBuilder }, { "nCreateFamily", "(J)J", (void*)FontFamily_create }, - { "nAllowUnsupportedFont", "(J)V", (void*)FontFamily_allowUnsupportedFont }, { "nAbort", "(J)V", (void*)FontFamily_abort }, { "nUnrefFamily", "(J)V", (void*)FontFamily_unref }, { "nAddFont", "(JLjava/nio/ByteBuffer;III)Z", (void*)FontFamily_addFont }, diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h index c79f5bd96e99..12da27307ade 100644 --- a/core/jni/android_media_AudioFormat.h +++ b/core/jni/android_media_AudioFormat.h @@ -36,6 +36,7 @@ #define ENCODING_AAC_ELD 15 #define ENCODING_AAC_XHE 16 #define ENCODING_AC4 17 +#define ENCODING_E_AC3_JOC 18 #define ENCODING_INVALID 0 #define ENCODING_DEFAULT 1 @@ -80,6 +81,8 @@ static inline audio_format_t audioFormatToNative(int audioFormat) return AUDIO_FORMAT_AAC; // FIXME temporary value, needs addition of xHE-AAC case ENCODING_AC4: return AUDIO_FORMAT_AC4; + // case ENCODING_E_AC3_JOC: // FIXME Not defined on the native side yet + // return AUDIO_FORMAT_E_AC3_JOC; case ENCODING_DEFAULT: return AUDIO_FORMAT_DEFAULT; default: @@ -130,6 +133,8 @@ static inline int audioFormatFromNative(audio_format_t nativeFormat) // return ENCODING_AAC_XHE; case AUDIO_FORMAT_AC4: return ENCODING_AC4; + // case AUDIO_FORMAT_E_AC3_JOC: // FIXME Not defined on the native side yet + // return ENCODING_E_AC3_JOC; case AUDIO_FORMAT_DEFAULT: return ENCODING_DEFAULT; default: diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index afbc57913688..61a22c144f1b 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -1262,6 +1262,18 @@ static jobject android_media_AudioTrack_get_volume_shaper_state(JNIEnv *env, job return VolumeShaperHelper::convertStateToJobject(env, gVolumeShaperFields, state); } +static int android_media_AudioTrack_setPresentation( + JNIEnv *env, jobject thiz, jint presentationId, jint programId) { + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "AudioTrack not initialized"); + return (jint)AUDIO_JAVA_ERROR; + } + + return (jint)lpTrack->selectPresentation((int)presentationId, (int)programId); +} + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -1333,6 +1345,7 @@ static const JNINativeMethod gMethods[] = { {"native_getVolumeShaperState", "(I)Landroid/media/VolumeShaper$State;", (void *)android_media_AudioTrack_get_volume_shaper_state}, + {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation}, }; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 43d5b233c315..d58b95a6697c 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1785,13 +1785,22 @@ <!-- Must be required by an ImsService to ensure that only the system can bind to it. - <p>Protection level: signature|privileged + <p>Protection level: signature|privileged|vendorPrivileged @SystemApi @hide --> <permission android:name="android.permission.BIND_IMS_SERVICE" android:protectionLevel="signature|privileged|vendorPrivileged" /> + <!-- Must be required by a DataService to ensure that only the + system can bind to it. + <p>Protection level: signature|privileged|vendorPrivileged + @SystemApi + @hide + --> + <permission android:name="android.permission.BIND_DATA_SERVICE" + android:protectionLevel="signature|privileged|vendorPrivileged" /> + <!-- Allows an application to manage embedded subscriptions (those on a eUICC) through EuiccManager APIs. <p>Protection level: signature|privileged|development diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3b752c420723..ec81df7b89e2 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1351,6 +1351,10 @@ <string name="fingerprint_error_lockout_permanent">Too many attempts. Fingerprint sensor disabled.</string> <!-- Generic error message shown when the fingerprint hardware can't recognize the fingerprint --> <string name="fingerprint_error_unable_to_process">Try again.</string> + <!-- Generic error message shown when the user has no enrolled fingerprints --> + <string name="fingerprint_error_no_fingerprints">No fingerprints enrolled.</string> + <!-- Generic error message shown when the app requests fingerprint authentication on a device without a sensor --> + <string name="fingerprint_error_hw_not_present">This device does not have a fingerprint sensor</string> <!-- Template to be used to name enrolled fingerprints by default. --> <string name="fingerprint_name_template">Finger <xliff:g id="fingerId" example="1">%d</xliff:g></string> @@ -4806,7 +4810,7 @@ A toast message shown when an app shortcut that was restored from a previous device is clicked, but it cannot be started because the shortcut was created by a newer version of the app. --> - <string name="shortcut_restored_on_lower_version">This shortcut requires latest app</string> + <string name="shortcut_restored_on_lower_version">App version downgraded, or isn\u2019t compatible with this shortcut</string> <!-- A toast message shown when an app shortcut that was restored from a previous device is clicked, diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index fee5a80245b4..8e1d14ef803a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2351,6 +2351,8 @@ <java-symbol type="string" name="fingerprint_error_lockout_permanent" /> <java-symbol type="string" name="fingerprint_name_template" /> <java-symbol type="string" name="fingerprint_not_recognized" /> + <java-symbol type="string" name="fingerprint_error_no_fingerprints" /> + <java-symbol type="string" name="fingerprint_error_hw_not_present" /> <!-- Fingerprint config --> <java-symbol type="integer" name="config_fingerprintMaxTemplatesPerUser"/> diff --git a/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java b/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java new file mode 100644 index 000000000000..5989da7af655 --- /dev/null +++ b/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.os; + +import static android.os.FileUtils.copyInternalSendfile; +import static android.os.FileUtils.copyInternalSplice; +import static android.os.FileUtils.copyInternalUserspace; + +import android.os.FileUtils.MemoryPipe; + +import com.google.caliper.BeforeExperiment; +import com.google.caliper.Param; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; + +public class FileUtilsBenchmark { + @Param({"32", "32000", "32000000"}) + private int mSize; + + private File mSrc; + private File mDest; + + private byte[] mData; + + @BeforeExperiment + protected void setUp() throws Exception { + mSrc = new File("/data/local/tmp/src"); + mDest = new File("/data/local/tmp/dest"); + + mData = new byte[mSize]; + + try (FileOutputStream os = new FileOutputStream(mSrc)) { + os.write(mData); + } + } + + public void timeRegularUserspace(int reps) throws Exception { + for (int i = 0; i < reps; i++) { + try (FileInputStream in = new FileInputStream(mSrc); + FileOutputStream out = new FileOutputStream(mDest)) { + copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE); + } + } + } + + public void timeRegularSendfile(int reps) throws Exception { + for (int i = 0; i < reps; i++) { + try (FileInputStream in = new FileInputStream(mSrc); + FileOutputStream out = new FileOutputStream(mDest)) { + copyInternalSendfile(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE); + } + } + } + + public void timePipeSourceUserspace(int reps) throws Exception { + for (int i = 0; i < reps; i++) { + try (MemoryPipe in = MemoryPipe.createSource(mData); + FileOutputStream out = new FileOutputStream(mDest)) { + copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE); + } + } + } + + public void timePipeSourceSplice(int reps) throws Exception { + for (int i = 0; i < reps; i++) { + try (MemoryPipe in = MemoryPipe.createSource(mData); + FileOutputStream out = new FileOutputStream(mDest)) { + copyInternalSplice(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE); + } + } + } + + public void timePipeSinkUserspace(int reps) throws Exception { + for (int i = 0; i < reps; i++) { + try (FileInputStream in = new FileInputStream(mSrc); + MemoryPipe out = MemoryPipe.createSink(mData)) { + copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE); + } + } + } + + public void timePipeSinkSplice(int reps) throws Exception { + for (int i = 0; i < reps; i++) { + try (FileInputStream in = new FileInputStream(mSrc); + MemoryPipe out = MemoryPipe.createSink(mData)) { + copyInternalSplice(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE); + } + } + } +} diff --git a/core/tests/coretests/src/android/hardware/display/AmbientBrightnessDayStatsTest.java b/core/tests/coretests/src/android/hardware/display/AmbientBrightnessDayStatsTest.java index 84409d48a4dc..f90ae340b2e9 100644 --- a/core/tests/coretests/src/android/hardware/display/AmbientBrightnessDayStatsTest.java +++ b/core/tests/coretests/src/android/hardware/display/AmbientBrightnessDayStatsTest.java @@ -16,8 +16,11 @@ package android.hardware.display; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import android.os.Parcel; import android.support.test.filters.SmallTest; @@ -27,20 +30,53 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.time.LocalDate; +import java.util.Arrays; @SmallTest @RunWith(AndroidJUnit4.class) public class AmbientBrightnessDayStatsTest { + private static final LocalDate LOCAL_DATE = LocalDate.now(); + private static final float[] BUCKET_BOUNDARIES = {0, 1, 10, 100}; + private static final float[] STATS = {1.3f, 2.6f, 5.8f, 10}; + + @Test + public void testParamsMustNotBeNull() { + assertThrows(NullPointerException.class, + () -> new AmbientBrightnessDayStats(null, BUCKET_BOUNDARIES)); + + assertThrows(NullPointerException.class, + () -> new AmbientBrightnessDayStats(LOCAL_DATE, null)); + + assertThrows(NullPointerException.class, + () -> new AmbientBrightnessDayStats(null, BUCKET_BOUNDARIES, STATS)); + + assertThrows(NullPointerException.class, + () -> new AmbientBrightnessDayStats(LOCAL_DATE, null, STATS)); + } + + @Test(expected = IllegalArgumentException.class) + public void testBucketBoundariesMustNotBeEmpty() { + new AmbientBrightnessDayStats(LocalDate.now(), new float[]{}); + } + + @Test(expected = IllegalArgumentException.class) + public void testStatsAndBoundariesMustHaveSameLength() { + float[] stats = Arrays.copyOf(STATS, STATS.length + 1); + stats[stats.length - 1] = 0; + new AmbientBrightnessDayStats(LOCAL_DATE, BUCKET_BOUNDARIES, stats); + } + @Test public void testAmbientBrightnessDayStatsAdd() { - AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(LocalDate.now(), - new float[]{0, 1, 10, 100}); + AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(LOCAL_DATE, + BUCKET_BOUNDARIES); dayStats.log(0, 1); dayStats.log(0.5f, 1.5f); dayStats.log(50, 12.5f); dayStats.log(2000, 1.24f); dayStats.log(-10, 0.5f); + assertEquals(4, dayStats.getStats().length); assertEquals(2.5f, dayStats.getStats()[0], 0); assertEquals(0, dayStats.getStats()[1], 0); assertEquals(12.5f, dayStats.getStats()[2], 0); @@ -48,38 +84,12 @@ public class AmbientBrightnessDayStatsTest { } @Test - public void testAmbientBrightnessDayStatsEquals() { - LocalDate today = LocalDate.now(); - AmbientBrightnessDayStats dayStats1 = new AmbientBrightnessDayStats(today, - new float[]{0, 1, 10, 100}); - AmbientBrightnessDayStats dayStats2 = new AmbientBrightnessDayStats(today, - new float[]{0, 1, 10, 100}, new float[4]); - AmbientBrightnessDayStats dayStats3 = new AmbientBrightnessDayStats(today, - new float[]{0, 1, 10, 100}, new float[]{1, 3, 5, 7}); - AmbientBrightnessDayStats dayStats4 = new AmbientBrightnessDayStats(today, - new float[]{0, 1, 10, 100}, new float[]{1, 3, 5, 0}); - assertEquals(dayStats1, dayStats2); - assertEquals(dayStats1.hashCode(), dayStats2.hashCode()); - assertNotEquals(dayStats1, dayStats3); - assertNotEquals(dayStats1.hashCode(), dayStats3.hashCode()); - dayStats4.log(100, 7); - assertEquals(dayStats3, dayStats4); - assertEquals(dayStats3.hashCode(), dayStats4.hashCode()); - } - - @Test - public void testAmbientBrightnessDayStatsIncorrectInit() { - try { - new AmbientBrightnessDayStats(LocalDate.now(), new float[]{1, 10, 100}, - new float[]{1, 5, 6, 7}); - } catch (IllegalArgumentException e) { - // Expected - } - try { - new AmbientBrightnessDayStats(LocalDate.now(), new float[]{}); - } catch (IllegalArgumentException e) { - // Expected - } + public void testGetters() { + AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(LOCAL_DATE, + BUCKET_BOUNDARIES, STATS); + assertEquals(LOCAL_DATE, dayStats.getLocalDate()); + assertArrayEquals(BUCKET_BOUNDARIES, dayStats.getBucketBoundaries(), 0); + assertArrayEquals(STATS, dayStats.getStats(), 0); } @Test @@ -100,4 +110,62 @@ public class AmbientBrightnessDayStatsTest { parcel); assertEquals(stats, statsAgain); } + + @Test + public void testAmbientBrightnessDayStatsEquals() { + AmbientBrightnessDayStats emptyDayStats = new AmbientBrightnessDayStats(LOCAL_DATE, + BUCKET_BOUNDARIES); + AmbientBrightnessDayStats identicalEmptyDayStats = new AmbientBrightnessDayStats(LOCAL_DATE, + BUCKET_BOUNDARIES, new float[BUCKET_BOUNDARIES.length]); + assertEquals(emptyDayStats, identicalEmptyDayStats); + assertEquals(emptyDayStats.hashCode(), identicalEmptyDayStats.hashCode()); + + AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(LOCAL_DATE, + BUCKET_BOUNDARIES, STATS); + AmbientBrightnessDayStats identicalDayStats = new AmbientBrightnessDayStats(LOCAL_DATE, + BUCKET_BOUNDARIES, STATS); + assertEquals(dayStats, identicalDayStats); + assertEquals(dayStats.hashCode(), identicalDayStats.hashCode()); + + assertNotEquals(emptyDayStats, dayStats); + assertNotEquals(emptyDayStats.hashCode(), dayStats.hashCode()); + + AmbientBrightnessDayStats differentDateDayStats = new AmbientBrightnessDayStats( + LOCAL_DATE.plusDays(1), BUCKET_BOUNDARIES, STATS); + assertNotEquals(dayStats, differentDateDayStats); + assertNotEquals(dayStats.hashCode(), differentDateDayStats.hashCode()); + + float[] differentStats = Arrays.copyOf(STATS, STATS.length); + differentStats[differentStats.length - 1] += 5f; + AmbientBrightnessDayStats differentStatsDayStats = new AmbientBrightnessDayStats(LOCAL_DATE, + BUCKET_BOUNDARIES, differentStats); + assertNotEquals(dayStats, differentDateDayStats); + assertNotEquals(dayStats.hashCode(), differentStatsDayStats.hashCode()); + + float[] differentBucketBoundaries = Arrays.copyOf(BUCKET_BOUNDARIES, + BUCKET_BOUNDARIES.length); + differentBucketBoundaries[differentBucketBoundaries.length - 1] += 100f; + AmbientBrightnessDayStats differentBoundariesDayStats = new AmbientBrightnessDayStats( + LOCAL_DATE, differentBucketBoundaries, STATS); + assertNotEquals(dayStats, differentBoundariesDayStats); + assertNotEquals(dayStats.hashCode(), differentBoundariesDayStats.hashCode()); + } + + private interface ExceptionRunnable { + void run() throws Exception; + } + + private static void assertThrows(Class<? extends Throwable> exceptionClass, + ExceptionRunnable r) { + try { + r.run(); + } catch (Throwable e) { + assertTrue("Expected exception type " + exceptionClass.getName() + " but got " + + e.getClass().getName(), exceptionClass.isAssignableFrom(e.getClass())); + return; + } + fail("Expected exception type " + exceptionClass.getName() + + ", but no exception was thrown"); + } + } diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index 27b7f9e185bb..ea0347d67ad7 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -192,6 +192,12 @@ public class UriTest extends TestCase { assertEquals("a:a@example.com:a@example2.com", uri.getAuthority()); assertEquals("example2.com", uri.getHost()); assertEquals(-1, uri.getPort()); + assertEquals("/path", uri.getPath()); + + uri = Uri.parse("http://a.foo.com\\.example.com/path"); + assertEquals("a.foo.com", uri.getHost()); + assertEquals(-1, uri.getPort()); + assertEquals("\\.example.com/path", uri.getPath()); } @SmallTest diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java index cd20192edfd6..0bc3a2d879ab 100644 --- a/core/tests/coretests/src/android/os/FileUtilsTest.java +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -21,16 +21,19 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.WEEK_IN_MILLIS; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.Context; +import android.os.FileUtils.MemoryPipe; import android.provider.DocumentsContract.Document; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import libcore.io.IoUtils; +import libcore.io.Streams; import com.google.android.collect.Sets; @@ -40,11 +43,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FileWriter; import java.util.Arrays; import java.util.HashSet; +import java.util.Random; @RunWith(AndroidJUnit4.class) public class FileUtilsTest { @@ -56,6 +61,8 @@ public class FileUtilsTest { private File mCopyFile; private File mTarget; + private final int[] DATA_SIZES = { 32, 32_000, 32_000_000 }; + private Context getContext() { return InstrumentationRegistry.getContext(); } @@ -80,7 +87,7 @@ public class FileUtilsTest { @Test public void testCopyFile() throws Exception { - stageFile(mTestFile, TEST_DATA); + writeFile(mTestFile, TEST_DATA); assertFalse(mCopyFile.exists()); FileUtils.copyFile(mTestFile, mCopyFile); assertTrue(mCopyFile.exists()); @@ -97,6 +104,104 @@ public class FileUtilsTest { } @Test + public void testCopy_FileToFile() throws Exception { + for (int size : DATA_SIZES) { + final File src = new File(mTarget, "src"); + final File dest = new File(mTarget, "dest"); + + byte[] expected = new byte[size]; + byte[] actual = new byte[size]; + new Random().nextBytes(expected); + writeFile(src, expected); + + try (FileInputStream in = new FileInputStream(src); + FileOutputStream out = new FileOutputStream(dest)) { + FileUtils.copy(in, out); + } + + actual = readFile(dest); + assertArrayEquals(expected, actual); + } + } + + @Test + public void testCopy_FileToPipe() throws Exception { + for (int size : DATA_SIZES) { + final File src = new File(mTarget, "src"); + + byte[] expected = new byte[size]; + byte[] actual = new byte[size]; + new Random().nextBytes(expected); + writeFile(src, expected); + + try (FileInputStream in = new FileInputStream(src); + MemoryPipe out = MemoryPipe.createSink(actual)) { + FileUtils.copy(in.getFD(), out.getFD()); + out.join(); + } + + assertArrayEquals(expected, actual); + } + } + + @Test + public void testCopy_PipeToFile() throws Exception { + for (int size : DATA_SIZES) { + final File dest = new File(mTarget, "dest"); + + byte[] expected = new byte[size]; + byte[] actual = new byte[size]; + new Random().nextBytes(expected); + + try (MemoryPipe in = MemoryPipe.createSource(expected); + FileOutputStream out = new FileOutputStream(dest)) { + FileUtils.copy(in.getFD(), out.getFD()); + } + + actual = readFile(dest); + assertArrayEquals(expected, actual); + } + } + + @Test + public void testCopy_PipeToPipe() throws Exception { + for (int size : DATA_SIZES) { + byte[] expected = new byte[size]; + byte[] actual = new byte[size]; + new Random().nextBytes(expected); + + try (MemoryPipe in = MemoryPipe.createSource(expected); + MemoryPipe out = MemoryPipe.createSink(actual)) { + FileUtils.copy(in.getFD(), out.getFD()); + out.join(); + } + + assertArrayEquals(expected, actual); + } + } + + @Test + public void testCopy_ShortPipeToFile() throws Exception { + byte[] source = new byte[33_000_000]; + new Random().nextBytes(source); + + for (int size : DATA_SIZES) { + final File dest = new File(mTarget, "dest"); + + byte[] expected = Arrays.copyOf(source, size); + byte[] actual = new byte[size]; + + try (MemoryPipe in = MemoryPipe.createSource(source); + FileOutputStream out = new FileOutputStream(dest)) { + FileUtils.copy(in.getFD(), out.getFD(), null, null, size); + } + + actual = readFile(dest); + assertArrayEquals(expected, actual); + } + } + + @Test public void testIsFilenameSafe() throws Exception { assertTrue(FileUtils.isFilenameSafe(new File("foobar"))); assertTrue(FileUtils.isFilenameSafe(new File("a_b-c=d.e/0,1+23"))); @@ -106,7 +211,7 @@ public class FileUtilsTest { @Test public void testReadTextFile() throws Exception { - stageFile(mTestFile, TEST_DATA); + writeFile(mTestFile, TEST_DATA); assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 0, null)); @@ -127,7 +232,7 @@ public class FileUtilsTest { @Test public void testReadTextFileWithZeroLengthFile() throws Exception { - stageFile(mTestFile, TEST_DATA); + writeFile(mTestFile, TEST_DATA); new FileOutputStream(mTestFile).close(); // Zero out the file assertEquals("", FileUtils.readTextFile(mTestFile, 0, null)); assertEquals("", FileUtils.readTextFile(mTestFile, 1, "<>")); @@ -381,12 +486,21 @@ public class FileUtilsTest { file.setLastModified(System.currentTimeMillis() - age); } - private void stageFile(File file, String data) throws Exception { - FileWriter writer = new FileWriter(file); - try { - writer.write(data, 0, data.length()); - } finally { - writer.close(); + private void writeFile(File file, String data) throws Exception { + writeFile(file, data.getBytes()); + } + + private void writeFile(File file, byte[] data) throws Exception { + try (FileOutputStream out = new FileOutputStream(file)) { + out.write(data); + } + } + + private byte[] readFile(File file) throws Exception { + try (FileInputStream in = new FileInputStream(file); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + Streams.copy(in, out); + return out.toByteArray(); } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 8addffbb02db..a678c4d49092 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -157,6 +157,7 @@ applications that come with the platform <permission name="android.permission.LOCAL_MAC_ADDRESS"/> <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> + <permission name="android.permission.PACKAGE_USAGE_STATS"/> <permission name="android.permission.PERFORM_CDMA_PROVISIONING"/> <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> @@ -227,6 +228,7 @@ applications that come with the platform <permission name="android.permission.CALL_PRIVILEGED"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.MODIFY_AUDIO_ROUTING" /> <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.STOP_APP_SWITCHES"/> <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index dad24da6ad98..22867df76aea 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -333,6 +333,9 @@ <family lang="und-Cari"> <font weight="400" style="normal">NotoSansCarian-Regular.ttf</font> </family> + <family lang="und-Cakm"> + <font weight="400" style="normal">NotoSansChakma-Regular.ttf</font> + </family> <family lang="und-Cher"> <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font> </family> @@ -429,6 +432,9 @@ <family lang="und-Orkh"> <font weight="400" style="normal">NotoSansOldTurkic-Regular.ttf</font> </family> + <family lang="und-Osge"> + <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font> + </family> <family lang="und-Osma"> <font weight="400" style="normal">NotoSansOsmanya-Regular.ttf</font> </family> diff --git a/docs/html/reference/images/text/style/drawablemarginspan.png b/docs/html/reference/images/text/style/drawablemarginspan.png Binary files differnew file mode 100644 index 000000000000..edf926d80130 --- /dev/null +++ b/docs/html/reference/images/text/style/drawablemarginspan.png diff --git a/docs/html/reference/images/text/style/dynamicdrawablespan.png b/docs/html/reference/images/text/style/dynamicdrawablespan.png Binary files differnew file mode 100644 index 000000000000..8776b0387278 --- /dev/null +++ b/docs/html/reference/images/text/style/dynamicdrawablespan.png diff --git a/docs/html/reference/images/text/style/iconmarginspan.png b/docs/html/reference/images/text/style/iconmarginspan.png Binary files differnew file mode 100644 index 000000000000..8ec39be039f6 --- /dev/null +++ b/docs/html/reference/images/text/style/iconmarginspan.png diff --git a/docs/html/reference/images/text/style/imagespan.png b/docs/html/reference/images/text/style/imagespan.png Binary files differnew file mode 100644 index 000000000000..c03e6bb68bd5 --- /dev/null +++ b/docs/html/reference/images/text/style/imagespan.png diff --git a/docs/html/reference/images/text/style/maskfilterspan.png b/docs/html/reference/images/text/style/maskfilterspan.png Binary files differnew file mode 100644 index 000000000000..6e55dbc1509c --- /dev/null +++ b/docs/html/reference/images/text/style/maskfilterspan.png diff --git a/docs/html/reference/images/text/style/stylespan.png b/docs/html/reference/images/text/style/stylespan.png Binary files differnew file mode 100644 index 000000000000..9ffa05b5ac43 --- /dev/null +++ b/docs/html/reference/images/text/style/stylespan.png diff --git a/docs/html/reference/images/text/style/tabstopspan.png b/docs/html/reference/images/text/style/tabstopspan.png Binary files differnew file mode 100644 index 000000000000..89a1121d0745 --- /dev/null +++ b/docs/html/reference/images/text/style/tabstopspan.png diff --git a/docs/html/reference/images/text/style/typefacespan.png b/docs/html/reference/images/text/style/typefacespan.png Binary files differnew file mode 100644 index 000000000000..67e2cf9b0468 --- /dev/null +++ b/docs/html/reference/images/text/style/typefacespan.png diff --git a/docs/html/reference/images/text/style/urlspan.png b/docs/html/reference/images/text/style/urlspan.png Binary files differnew file mode 100644 index 000000000000..11345206e594 --- /dev/null +++ b/docs/html/reference/images/text/style/urlspan.png diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index 627d5515b77a..69a5874e9381 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -541,10 +541,19 @@ public abstract class BaseCanvas { return mAllowHwBitmapsInSwMode; } + /** + * @hide + */ + protected void onHwBitmapInSwMode() { + if (!mAllowHwBitmapsInSwMode) { + throw new IllegalArgumentException( + "Software rendering doesn't support hardware bitmaps"); + } + } + private void throwIfHwBitmapInSwMode(Bitmap bitmap) { - if (!mAllowHwBitmapsInSwMode && !isHardwareAccelerated() - && bitmap.getConfig() == Bitmap.Config.HARDWARE) { - throw new IllegalStateException("Software rendering doesn't support hardware bitmaps"); + if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) { + onHwBitmapInSwMode(); } } diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 0072012f69ad..44e7066c5c66 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -29,6 +29,10 @@ import android.os.StrictMode; import android.os.Trace; import android.util.DisplayMetrics; import android.util.Log; +import android.view.DisplayListCanvas; +import android.view.RenderNode; +import android.view.ThreadedRenderer; + import libcore.util.NativeAllocationRegistry; import java.io.OutputStream; @@ -1171,6 +1175,82 @@ public final class Bitmap implements Parcelable { } /** + * Creates a Bitmap from the given {@link Picture} source of recorded drawing commands. + * + * Equivalent to calling {@link #createBitmap(Picture, int, int, Config)} with + * width and height the same as the Picture's width and height and a Config.HARDWARE + * config. + * + * @param source The recorded {@link Picture} of drawing commands that will be + * drawn into the returned Bitmap. + * @return An immutable bitmap with a HARDWARE config whose contents are created + * from the recorded drawing commands in the Picture source. + */ + public static @NonNull Bitmap createBitmap(@NonNull Picture source) { + return createBitmap(source, source.getWidth(), source.getHeight(), Config.HARDWARE); + } + + /** + * Creates a Bitmap from the given {@link Picture} source of recorded drawing commands. + * + * The bitmap will be immutable with the given width and height. If the width and height + * are not the same as the Picture's width & height, the Picture will be scaled to + * fit the given width and height. + * + * @param source The recorded {@link Picture} of drawing commands that will be + * drawn into the returned Bitmap. + * @param width The width of the bitmap to create. The picture's width will be + * scaled to match if necessary. + * @param height The height of the bitmap to create. The picture's height will be + * scaled to match if necessary. + * @param config The {@link Config} of the created bitmap. If this is null then + * the bitmap will be {@link Config#HARDWARE}. + * + * @return An immutable bitmap with a HARDWARE config whose contents are created + * from the recorded drawing commands in the Picture source. + */ + public static @NonNull Bitmap createBitmap(@NonNull Picture source, int width, int height, + @NonNull Config config) { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("width & height must be > 0"); + } + if (config == null) { + throw new IllegalArgumentException("Config must not be null"); + } + if (source.requiresHardwareAcceleration() && config != Config.HARDWARE) { + StrictMode.noteSlowCall("GPU readback"); + } + if (config == Config.HARDWARE || source.requiresHardwareAcceleration()) { + final RenderNode node = RenderNode.create("BitmapTemporary", null); + node.setLeftTopRightBottom(0, 0, width, height); + node.setClipToBounds(false); + final DisplayListCanvas canvas = node.start(width, height); + if (source.getWidth() != width || source.getHeight() != height) { + canvas.scale(width / (float) source.getWidth(), + height / (float) source.getHeight()); + } + canvas.drawPicture(source); + node.end(canvas); + Bitmap bitmap = ThreadedRenderer.createHardwareBitmap(node, width, height); + if (config != Config.HARDWARE) { + bitmap = bitmap.copy(config, false); + } + return bitmap; + } else { + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + if (source.getWidth() != width || source.getHeight() != height) { + canvas.scale(width / (float) source.getWidth(), + height / (float) source.getHeight()); + } + canvas.drawPicture(source); + canvas.setBitmap(null); + bitmap.makeImmutable(); + return bitmap; + } + } + + /** * Returns an optional array of private data, used by the UI system for * some bitmaps. Not intended to be called by applications. */ @@ -1259,6 +1339,12 @@ public final class Bitmap implements Parcelable { return mIsMutable; } + /** @hide */ + public final void makeImmutable() { + // todo mIsMutable = false; + // todo nMakeImmutable(); + } + /** * <p>Indicates whether pixels stored in this bitmaps are stored pre-multiplied. * When a pixel is pre-multiplied, the RGB components have been multiplied by diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index d77e6012fb46..fe2b52341063 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -160,25 +160,6 @@ public class FontFamily { isItalic); } - /** - * Allow creating unsupported FontFamily. - * - * For compatibility reasons, we still need to create a FontFamily object even if Minikin failed - * to find any usable 'cmap' table for some reasons, e.g. broken 'cmap' table, no 'cmap' table - * encoded with Unicode code points, etc. Without calling this method, the freeze() method will - * return null if Minikin fails to find any usable 'cmap' table. By calling this method, the - * freeze() won't fail and will create an empty FontFamily. This empty FontFamily is placed at - * the top of the fallback chain but is never used. if we don't create this empty FontFamily - * and put it at top, bad things (performance regressions, unexpected glyph selection) will - * happen. - */ - public void allowUnsupportedFont() { - if (mBuilderPtr == 0) { - throw new IllegalStateException("Unable to allow unsupported font."); - } - nAllowUnsupportedFont(mBuilderPtr); - } - // TODO: Remove once internal user stop using private API. private static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) { return nAddFont(builderPtr, font, ttcIndex, -1, -1); @@ -190,9 +171,6 @@ public class FontFamily { private static native long nCreateFamily(long mBuilderPtr); @CriticalNative - private static native void nAllowUnsupportedFont(long builderPtr); - - @CriticalNative private static native void nAbort(long mBuilderPtr); @CriticalNative diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index 08eeaff69f9b..9ac94d895a37 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -31,8 +31,9 @@ import java.io.OutputStream; * be replayed on a hardware accelerated canvas.</p> */ public class Picture { - private Canvas mRecordingCanvas; + private PictureCanvas mRecordingCanvas; private long mNativePicture; + private boolean mRequiresHwAcceleration; private static final int WORKING_STREAM_STORAGE = 16 * 1024; @@ -78,8 +79,12 @@ public class Picture { * into it. */ public Canvas beginRecording(int width, int height) { + if (mRecordingCanvas != null) { + throw new IllegalStateException("Picture already recording, must call #endRecording()"); + } long ni = nativeBeginRecording(mNativePicture, width, height); - mRecordingCanvas = new RecordingCanvas(this, ni); + mRecordingCanvas = new PictureCanvas(this, ni); + mRequiresHwAcceleration = false; return mRecordingCanvas; } @@ -91,6 +96,7 @@ public class Picture { */ public void endRecording() { if (mRecordingCanvas != null) { + mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap; mRecordingCanvas = null; nativeEndRecording(mNativePicture); } @@ -113,6 +119,18 @@ public class Picture { } /** + * Indicates whether or not this Picture contains recorded commands that only work when + * drawn to a hardware-accelerated canvas. If this returns true then this Picture can only + * be drawn to another Picture or to a Canvas where canvas.isHardwareAccelerated() is true. + * + * @return true if the Picture can only be drawn to a hardware-accelerated canvas, + * false otherwise. + */ + public boolean requiresHardwareAcceleration() { + return mRequiresHwAcceleration; + } + + /** * Draw this picture on the canvas. * <p> * Prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this call could @@ -129,6 +147,9 @@ public class Picture { if (mRecordingCanvas != null) { endRecording(); } + if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()) { + canvas.onHwBitmapInSwMode(); + } nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture); } @@ -164,8 +185,7 @@ public class Picture { if (stream == null) { throw new NullPointerException(); } - if (!nativeWriteToStream(mNativePicture, stream, - new byte[WORKING_STREAM_STORAGE])) { + if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) { throw new RuntimeException(); } } @@ -182,10 +202,11 @@ public class Picture { OutputStream stream, byte[] storage); private static native void nativeDestructor(long nativePicture); - private static class RecordingCanvas extends Canvas { + private static class PictureCanvas extends Canvas { private final Picture mPicture; + boolean mHoldsHwBitmap; - public RecordingCanvas(Picture pict, long nativeCanvas) { + public PictureCanvas(Picture pict, long nativeCanvas) { super(nativeCanvas); mPicture = pict; } @@ -202,5 +223,10 @@ public class Picture { } super.drawPicture(picture); } + + @Override + protected void onHwBitmapInSwMode() { + mHoldsHwBitmap = true; + } } } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index ef4150763139..04c5295539ac 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -818,12 +818,9 @@ public class Typeface { if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */, 0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, null /* axes */)) { - // Due to backward compatibility, even if the font is not supported by our font - // stack, we need to place the empty font at the first place. The typeface with - // empty font behaves different from default typeface especially in fallback - // font selection. - fontFamily.allowUnsupportedFont(); - fontFamily.freeze(); + if (!fontFamily.freeze()) { + return Typeface.DEFAULT; + } final FontFamily[] families = { fontFamily }; typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); @@ -870,12 +867,9 @@ public class Typeface { final FontFamily fontFamily = new FontFamily(); if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) { - // Due to backward compatibility, even if the font is not supported by our font - // stack, we need to place the empty font at the first place. The typeface with - // empty font behaves different from default typeface especially in fallback font - // selection. - fontFamily.allowUnsupportedFont(); - fontFamily.freeze(); + if (!fontFamily.freeze()) { + return Typeface.DEFAULT; + } FontFamily[] families = { fontFamily }; return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java index bd49b87ec200..27c8fda0d6e9 100644 --- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java @@ -348,7 +348,9 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { if (mState == null) { throw new IllegalStateException("called stop on empty AnimatedImageDrawable"); } - nStop(mState.mNativePtr); + if (nStop(mState.mNativePtr)) { + postOnAnimationEnd(); + } } // Animatable2 overrides @@ -365,21 +367,31 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { nSetOnAnimationEndListener(mState.mNativePtr, this); } - mAnimationCallbacks.add(callback); + if (!mAnimationCallbacks.contains(callback)) { + mAnimationCallbacks.add(callback); + } } @Override public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) { - if (callback == null || mAnimationCallbacks == null) { + if (callback == null || mAnimationCallbacks == null + || !mAnimationCallbacks.remove(callback)) { return false; } - return mAnimationCallbacks.remove(callback); + if (mAnimationCallbacks.isEmpty()) { + clearAnimationCallbacks(); + } + + return true; } @Override public void clearAnimationCallbacks() { - mAnimationCallbacks = null; + if (mAnimationCallbacks != null) { + mAnimationCallbacks = null; + nSetOnAnimationEndListener(mState.mNativePtr, null); + } } private void postOnAnimationStart() { @@ -413,6 +425,21 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { return mHandler; } + /** + * Called by JNI. + * + * The JNI code has already posted this to the thread that created the + * callback, so no need to post. + */ + @SuppressWarnings("unused") + private void onAnimationEnd() { + if (mAnimationCallbacks != null) { + for (Animatable2.AnimationCallback callback : mAnimationCallbacks) { + callback.onAnimationEnd(this); + } + } + } + private static native long nCreate(long nativeImageDecoder, @Nullable ImageDecoder decoder, int width, int height, Rect cropRect) @@ -432,7 +459,7 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { @FastNative private static native boolean nStart(long nativePtr); @FastNative - private static native void nStop(long nativePtr); + private static native boolean nStop(long nativePtr); @FastNative private static native void nSetLoopCount(long nativePtr, int loopCount); // Pass the drawable down to native so it can call onAnimationEnd. diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 3323bce8b5ad..24d819e93ff2 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -134,6 +134,8 @@ cc_defaults { name: "libhwui_defaults", defaults: ["hwui_defaults"], + shared_libs: ["libstatslog"], + whole_static_libs: ["libskia"], srcs: [ @@ -318,7 +320,10 @@ cc_test { "libgmock", "libhwui_static_debug", ], - shared_libs: ["libmemunreachable"], + shared_libs: [ + "libmemunreachable", + "libstatslog", + ], cflags: [ "-include debug/wrap_gles.h", "-DHWUI_NULL_GPU", @@ -383,7 +388,10 @@ cc_benchmark { // set to libhwui_static_debug to skip actual GL commands whole_static_libs: ["libhwui"], - shared_libs: ["libmemunreachable"], + shared_libs: [ + "libmemunreachable", + "libstatslog", + ], srcs: [ "tests/macrobench/TestSceneRunner.cpp", @@ -405,7 +413,10 @@ cc_benchmark { ], whole_static_libs: ["libhwui_static_debug"], - shared_libs: ["libmemunreachable"], + shared_libs: [ + "libmemunreachable", + "libstatslog", + ], srcs: [ "tests/microbench/main.cpp", diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index f41956cdebeb..ab27a0d00246 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -18,6 +18,7 @@ #include <errno.h> #include <inttypes.h> +#include <statslog.h> #include <sys/mman.h> #include <algorithm> @@ -164,6 +165,7 @@ void JankTracker::finishFrame(const FrameInfo& frame) { ALOGI("%s", ss.str().c_str()); // Just so we have something that counts up, the value is largely irrelevant ATRACE_INT(ss.str().c_str(), ++sDaveyCount); + android::util::stats_write(android::util::DAVEY_OCCURRED, ns2ms(totalDuration)); } } diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index 5356d3bfc7c9..2bded9b3a2d8 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -48,8 +48,10 @@ bool AnimatedImageDrawable::start() { return true; } -void AnimatedImageDrawable::stop() { +bool AnimatedImageDrawable::stop() { + bool wasRunning = mRunning; mRunning = false; + return wasRunning; } bool AnimatedImageDrawable::isRunning() { @@ -180,7 +182,6 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { if (finalFrame) { if (mEndListener) { mEndListener->onAnimationEnd(); - mEndListener = nullptr; } } } diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h index 9d84ed5f4470..2fd6f40b71b5 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.h +++ b/libs/hwui/hwui/AnimatedImageDrawable.h @@ -68,7 +68,9 @@ public: // Returns true if the animation was started; false otherwise (e.g. it was // already running) bool start(); - void stop(); + // Returns true if the animation was stopped; false otherwise (e.g. it was + // already stopped) + bool stop(); bool isRunning(); void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); } diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index 43f46ef758ca..a0d000dd2179 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -44,7 +44,6 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, minikinPaint.familyVariant = paint->getFamilyVariant(); minikinPaint.fontStyle = resolvedFace->fStyle; minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings(); - minikinPaint.hyphenEdit = minikin::HyphenEdit(paint->getHyphenEdit()); return minikinPaint; } @@ -55,18 +54,23 @@ minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFla minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); minikin::Layout layout; + const minikin::U16StringPiece textBuf(buf, bufSize); + const minikin::Range range(start, start + count); + const minikin::HyphenEdit hyphenEdit = static_cast<minikin::HyphenEdit>(paint->getHyphenEdit()); + const minikin::StartHyphenEdit startHyphen = minikin::startHyphenEdit(hyphenEdit); + const minikin::EndHyphenEdit endHyphen = minikin::endHyphenEdit(hyphenEdit); + if (mt == nullptr) { - layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint); + layout.doLayout(textBuf,range, bidiFlags, minikinPaint, startHyphen, endHyphen); return layout; } - if (mt->buildLayout(minikin::U16StringPiece(buf, bufSize), - minikin::Range(start, start + count), - minikinPaint, bidiFlags, mtOffset, &layout)) { + if (mt->buildLayout(textBuf, range, minikinPaint, bidiFlags, mtOffset, startHyphen, endHyphen, + &layout)) { return layout; } - layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint); + layout.doLayout(textBuf, range, bidiFlags, minikinPaint, startHyphen, endHyphen); return layout; } @@ -74,8 +78,14 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, size_t start, size_t count, size_t bufSize, float* advances) { minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); - return minikin::Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinPaint, - advances, nullptr /* extent */); + const minikin::U16StringPiece textBuf(buf, bufSize); + const minikin::Range range(start, start + count); + const minikin::HyphenEdit hyphenEdit = static_cast<minikin::HyphenEdit>(paint->getHyphenEdit()); + const minikin::StartHyphenEdit startHyphen = minikin::startHyphenEdit(hyphenEdit); + const minikin::EndHyphenEdit endHyphen = minikin::endHyphenEdit(hyphenEdit); + + return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen, + endHyphen, advances, nullptr /* extent */); } bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) { diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index ebc14c8b675b..091b5267881d 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -182,10 +182,11 @@ void Typeface::setRobotoTypefaceForTest() { std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>( std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>()); - std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>( - std::vector<minikin::Font>({minikin::Font(std::move(font), minikin::FontStyle())})); - std::shared_ptr<minikin::FontCollection> collection = - std::make_shared<minikin::FontCollection>(std::move(family)); + std::vector<minikin::Font> fonts; + fonts.push_back(minikin::Font(std::move(font), minikin::FontStyle())); + + std::shared_ptr<minikin::FontCollection> collection = std::make_shared<minikin::FontCollection>( + std::make_shared<minikin::FontFamily>(std::move(fonts))); Typeface* hwTypeface = new Typeface(); hwTypeface->fFontCollection = collection; diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp index 66d6f527e604..2232c25de345 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -56,8 +56,9 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) { LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName); std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>( std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>()); - return std::make_shared<minikin::FontFamily>( - std::vector<minikin::Font>({minikin::Font(std::move(font), minikin::FontStyle())})); + std::vector<minikin::Font> fonts; + fonts.push_back(minikin::Font(std::move(font), minikin::FontStyle())); + return std::make_shared<minikin::FontFamily>(std::move(fonts)); } std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const char* fileName) { diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 018db9a7fc9d..fa3f99a90cc8 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -88,11 +88,6 @@ interface ILocationManager boolean providerMeetsCriteria(String provider, in Criteria criteria); ProviderProperties getProviderProperties(String provider); String getNetworkProviderPackage(); - boolean isProviderEnabled(String provider); - boolean isProviderEnabledForUser(String provider, int userId); - boolean setProviderEnabledForUser(String provider, boolean enabled, int userId); - boolean isLocationEnabledForUser(int userId); - void setLocationEnabledForUser(boolean enabled, int userId); void addTestProvider(String name, in ProviderProperties properties, String opPackageName); void removeTestProvider(String provider, String opPackageName); diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index b07d04220046..f98480b28001 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -265,6 +265,12 @@ public final class AudioFormat implements Parcelable { public static final int ENCODING_AAC_XHE = 16; /** Audio data format: AC-4 sync frame transport format */ public static final int ENCODING_AC4 = 17; + /** Audio data format: E-AC-3-JOC compressed + * E-AC-3-JOC streams can be decoded by downstream devices supporting {@link #ENCODING_E_AC3}. + * Use {@link #ENCODING_E_AC3} as the AudioTrack encoding when the downstream device + * supports {@link #ENCODING_E_AC3} but not {@link #ENCODING_E_AC3_JOC}. + **/ + public static final int ENCODING_E_AC3_JOC = 18; /** @hide */ public static String toLogFriendlyEncoding(int enc) { @@ -512,6 +518,7 @@ public final class AudioFormat implements Parcelable { case ENCODING_PCM_FLOAT: case ENCODING_AC3: case ENCODING_E_AC3: + case ENCODING_E_AC3_JOC: case ENCODING_DTS: case ENCODING_DTS_HD: case ENCODING_MP3: @@ -537,6 +544,7 @@ public final class AudioFormat implements Parcelable { case ENCODING_PCM_FLOAT: case ENCODING_AC3: case ENCODING_E_AC3: + case ENCODING_E_AC3_JOC: case ENCODING_DTS: case ENCODING_DTS_HD: case ENCODING_IEC61937: @@ -564,6 +572,7 @@ public final class AudioFormat implements Parcelable { return true; case ENCODING_AC3: case ENCODING_E_AC3: + case ENCODING_E_AC3_JOC: case ENCODING_DTS: case ENCODING_DTS_HD: case ENCODING_MP3: @@ -593,6 +602,7 @@ public final class AudioFormat implements Parcelable { return true; case ENCODING_AC3: case ENCODING_E_AC3: + case ENCODING_E_AC3_JOC: case ENCODING_DTS: case ENCODING_DTS_HD: case ENCODING_MP3: @@ -829,6 +839,7 @@ public final class AudioFormat implements Parcelable { case ENCODING_PCM_FLOAT: case ENCODING_AC3: case ENCODING_E_AC3: + case ENCODING_E_AC3_JOC: case ENCODING_DTS: case ENCODING_DTS_HD: case ENCODING_IEC61937: @@ -1044,6 +1055,7 @@ public final class AudioFormat implements Parcelable { ENCODING_PCM_FLOAT, ENCODING_AC3, ENCODING_E_AC3, + ENCODING_E_AC3_JOC, ENCODING_DTS, ENCODING_DTS_HD, ENCODING_IEC61937, diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java new file mode 100644 index 000000000000..4652c180936c --- /dev/null +++ b/media/java/android/media/AudioPresentation.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + + +/** + * The AudioPresentation class encapsulates the information that describes an audio presentation + * which is available in next generation audio content. + * + * Used by {@link MediaExtractor} {@link MediaExtractor#getAudioPresentations(int)} and + * {@link AudioTrack} {@link AudioTrack#setPresentation(AudioPresentation)} to query available + * presentations and to select one. + * + * A list of available audio presentations in a media source can be queried using + * {@link MediaExtractor#getAudioPresentations(int)}. This list can be presented to a user for + * selection. + * An AudioPresentation can be passed to an offloaded audio decoder via + * {@link AudioTrack#setPresentation(AudioPresentation)} to request decoding of the selected + * presentation. An audio stream may contain multiple presentations that differ by language, + * accessibility, end point mastering and dialogue enhancement. An audio presentation may also have + * a set of description labels in different languages to help the user to make an informed + * selection. + */ +public final class AudioPresentation { + private final int mPresentationId; + private final int mProgramId; + private final Map<String, String> mLabels; + private final String mLanguage; + + /** @hide */ + @IntDef( + value = { + MASTERING_NOT_INDICATED, + MASTERED_FOR_STEREO, + MASTERED_FOR_SURROUND, + MASTERED_FOR_3D, + MASTERED_FOR_HEADPHONE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface MasteringIndicationType {} + + private final @MasteringIndicationType int mMasteringIndication; + private final boolean mAudioDescriptionAvailable; + private final boolean mSpokenSubtitlesAvailable; + private final boolean mDialogueEnhancementAvailable; + + /** + * No preferred reproduction channel layout. + */ + public static final int MASTERING_NOT_INDICATED = 0; + /** + * Stereo speaker layout. + */ + public static final int MASTERED_FOR_STEREO = 1; + /** + * Two-dimensional (e.g. 5.1) speaker layout. + */ + public static final int MASTERED_FOR_SURROUND = 2; + /** + * Three-dimensional (e.g. 5.1.2) speaker layout. + */ + public static final int MASTERED_FOR_3D = 3; + /** + * Prerendered for headphone playback. + */ + public static final int MASTERED_FOR_HEADPHONE = 4; + + AudioPresentation(int presentationId, + int programId, + Map<String, String> labels, + String language, + @MasteringIndicationType int masteringIndication, + boolean audioDescriptionAvailable, + boolean spokenSubtitlesAvailable, + boolean dialogueEnhancementAvailable) { + this.mPresentationId = presentationId; + this.mProgramId = programId; + this.mLanguage = language; + this.mMasteringIndication = masteringIndication; + this.mAudioDescriptionAvailable = audioDescriptionAvailable; + this.mSpokenSubtitlesAvailable = spokenSubtitlesAvailable; + this.mDialogueEnhancementAvailable = dialogueEnhancementAvailable; + + this.mLabels = new HashMap<String, String>(labels); + } + + /** + * The framework uses this presentation id to select an audio presentation rendered by a + * decoder. Presentation id is typically sequential, but does not have to be. + * @hide + */ + public int getPresentationId() { + return mPresentationId; + } + + /** + * The framework uses this program id to select an audio presentation rendered by a decoder. + * Program id can be used to further uniquely identify the presentation to a decoder. + * @hide + */ + public int getProgramId() { + return mProgramId; + } + + /** + * @return a map of available text labels for this presentation. Each label is indexed by its + * locale corresponding to the language code as specified by ISO 639-2 [42]. Either ISO 639-2/B + * or ISO 639-2/T could be used. + */ + public Map<Locale, String> getLabels() { + Map<Locale, String> localeLabels = new HashMap<>(); + for (Map.Entry<String, String> entry : mLabels.entrySet()) { + localeLabels.put(new Locale(entry.getKey()), entry.getValue()); + } + return localeLabels; + } + + /** + * @return the locale corresponding to audio presentation's ISO 639-1/639-2 language code. + */ + public Locale getLocale() { + return new Locale(mLanguage); + } + + /** + * @return the mastering indication of the audio presentation. + * See {@link #MASTERING_NOT_INDICATED}, {@link #MASTERED_FOR_STEREO}, + * {@link #MASTERED_FOR_SURROUND}, {@link #MASTERED_FOR_3D}, {@link #MASTERED_FOR_HEADPHONE} + */ + @MasteringIndicationType + public int getMasteringIndication() { + return mMasteringIndication; + } + + /** + * Indicates whether an audio description for the visually impaired is available. + * @return {@code true} if audio description is available. + */ + public boolean hasAudioDescription() { + return mAudioDescriptionAvailable; + } + + /** + * Indicates whether spoken subtitles for the visually impaired are available. + * @return {@code true} if spoken subtitles are available. + */ + public boolean hasSpokenSubtitles() { + return mSpokenSubtitlesAvailable; + } + + /** + * Indicates whether dialogue enhancement is available. + * @return {@code true} if dialogue enhancement is available. + */ + public boolean hasDialogueEnhancement() { + return mDialogueEnhancementAvailable; + } +} diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 4e9ce8e23e9c..8e822a5d7a37 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -2008,6 +2008,25 @@ public class AudioTrack extends PlayerBase } /** + * Sets the audio presentation. + * If the audio presentation is invalid then {@link #ERROR_BAD_VALUE} will be returned. + * If a multi-stream decoder (MSD) is not present, or the format does not support + * multiple presentations, then {@link #ERROR_INVALID_OPERATION} will be returned. + * @param presentation see {@link AudioPresentation}. In particular, id should be set. + * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE}, + * {@link #ERROR_INVALID_OPERATION} + * @throws IllegalArgumentException if the audio presentation is null. + * @throws IllegalStateException if track is not initialized. + */ + public int setPresentation(@NonNull AudioPresentation presentation) { + if (presentation == null) { + throw new IllegalArgumentException("audio presentation is null"); + } + return native_setPresentation(presentation.getPresentationId(), + presentation.getProgramId()); + } + + /** * Sets the initialization state of the instance. This method was originally intended to be used * in an AudioTrack subclass constructor to set a subclass-specific post-initialization state. * However, subclasses of AudioTrack are no longer recommended, so this method is obsolete. @@ -3245,6 +3264,7 @@ public class AudioTrack extends PlayerBase @NonNull VolumeShaper.Operation operation); private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id); + private native final int native_setPresentation(int presentationId, int programId); //--------------------------------------------------------- // Utility methods diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java index b32e5398d0de..fcdecbad908d 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -172,6 +172,14 @@ public class MediaController2 implements AutoCloseable { } /** + * @hide + */ + @SystemApi + public PlaybackInfoProvider getProvider() { + return mProvider; + } + + /** * Get the type of playback which affects volume handling. One of: * <ul> * <li>{@link #PLAYBACK_TYPE_LOCAL}</li> @@ -199,9 +207,9 @@ public class MediaController2 implements AutoCloseable { /** * Get the type of volume control that can be used. One of: * <ul> - * <li>{@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}</li> - * <li>{@link VolumeProvider#VOLUME_CONTROL_RELATIVE}</li> - * <li>{@link VolumeProvider#VOLUME_CONTROL_FIXED}</li> + * <li>{@link VolumeProvider2#VOLUME_CONTROL_ABSOLUTE}</li> + * <li>{@link VolumeProvider2#VOLUME_CONTROL_RELATIVE}</li> + * <li>{@link VolumeProvider2#VOLUME_CONTROL_FIXED}</li> * </ul> * * @return The type of volume control that may be used with this session. @@ -472,7 +480,7 @@ public class MediaController2 implements AutoCloseable { /** * Set the volume of the output this session is playing on. The command will be ignored if it - * does not support {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. + * does not support {@link VolumeProvider2#VOLUME_CONTROL_ABSOLUTE}. * <p> * If the session is local playback, this changes the device's volume with the stream that * session's player is using. Flags will be specified for the {@link AudioManager}. @@ -494,8 +502,8 @@ public class MediaController2 implements AutoCloseable { * must be one of {@link AudioManager#ADJUST_LOWER}, * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}. * The command will be ignored if the session does not support - * {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or - * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. + * {@link VolumeProvider2#VOLUME_CONTROL_RELATIVE} or + * {@link VolumeProvider2#VOLUME_CONTROL_ABSOLUTE}. * <p> * If the session is local playback, this changes the device's volume with the stream that * session's player is using. Flags will be specified for the {@link AudioManager}. diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java index 174d6a3b879c..4919eeb4dacb 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; +import android.media.AudioPresentation; import android.media.MediaCodec; import android.media.MediaFormat; import android.media.MediaHTTPService; @@ -40,6 +41,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -396,6 +398,17 @@ final public class MediaExtractor { } /** + * Get the list of available audio presentations for the track. + * @param trackIndex index of the track. + * @return a list of available audio presentations for a given valid audio track index. + * The list will be empty if the source does not contain any audio presentations. + */ + @NonNull + public List<AudioPresentation> getAudioPresentations(int trackIndex) { + return new ArrayList<AudioPresentation>(); + } + + /** * Get the PSSH info if present. * @return a map of uuid-to-bytes, with the uuid specifying * the crypto scheme, and the bytes being the data specific to that scheme. diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java index f88f9f2eb5c1..a11768e69b9d 100644 --- a/media/java/android/media/MediaLibraryService2.java +++ b/media/java/android/media/MediaLibraryService2.java @@ -27,7 +27,6 @@ import android.media.MediaSession2.ControllerInfo; import android.media.update.ApiLoader; import android.media.update.MediaLibraryService2Provider.LibraryRootProvider; import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider; -import android.media.update.MediaSession2Provider; import android.media.update.MediaSessionService2Provider; import android.os.Bundle; @@ -63,7 +62,8 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { public static final String SERVICE_INTERFACE = "android.media.MediaLibraryService2"; /** - * Session for the media library service. + * Session for the {@link MediaLibraryService2}. Build this object with + * {@link MediaLibrarySessionBuilder} and return in {@link #onCreateSession(String)}. */ public static class MediaLibrarySession extends MediaSession2 { private final MediaLibrarySessionProvider mProvider; @@ -101,6 +101,9 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { } } + /** + * Callback for the {@link MediaLibrarySession}. + */ public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback { public MediaLibrarySessionCallback(Context context) { @@ -200,6 +203,8 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { /** * Builder for {@link MediaLibrarySession}. */ + // Override all methods just to show them with the type instead of generics in Javadoc. + // This workarounds javadoc issue described in the MediaSession2.BuilderBase. public class MediaLibrarySessionBuilder extends BuilderBase<MediaLibrarySession, MediaLibrarySessionBuilder, MediaLibrarySessionCallback> { public MediaLibrarySessionBuilder( @@ -210,6 +215,38 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { context, (MediaLibrarySessionBuilder) instance, player, callbackExecutor, callback)); } + + @Override + public MediaLibrarySessionBuilder setVolumeProvider( + @Nullable VolumeProvider2 volumeProvider) { + return super.setVolumeProvider(volumeProvider); + } + + @Override + public MediaLibrarySessionBuilder setRatingType(int type) { + return super.setRatingType(type); + } + + @Override + public MediaLibrarySessionBuilder setSessionActivity(@Nullable PendingIntent pi) { + return super.setSessionActivity(pi); + } + + @Override + public MediaLibrarySessionBuilder setId(String id) { + return super.setId(id); + } + + @Override + public MediaLibrarySessionBuilder setSessionCallback( + @NonNull Executor executor, @NonNull MediaLibrarySessionCallback callback) { + return super.setSessionCallback(executor, callback); + } + + @Override + public MediaLibrarySession build() { + return super.build(); + } } @Override @@ -229,7 +266,7 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { * This method will be called on the main thread. * * @param sessionId session id written in the AndroidManifest.xml. - * @return a new browser session + * @return a new library session * @see MediaLibrarySessionBuilder * @see #getSession() * @throws RuntimeException if returned session is invalid diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index d84eedf94820..e331b2cbf645 100644 --- a/media/java/android/media/MediaPlayer2.java +++ b/media/java/android/media/MediaPlayer2.java @@ -1671,35 +1671,6 @@ public abstract class MediaPlayer2 implements SubtitleController.Listener public abstract void deselectTrack(int index); /** - * Sets the target UDP re-transmit endpoint for the low level player. - * Generally, the address portion of the endpoint is an IP multicast - * address, although a unicast address would be equally valid. When a valid - * retransmit endpoint has been set, the media player will not decode and - * render the media presentation locally. Instead, the player will attempt - * to re-multiplex its media data using the Android@Home RTP profile and - * re-transmit to the target endpoint. Receiver devices (which may be - * either the same as the transmitting device or different devices) may - * instantiate, prepare, and start a receiver player using a setDataSource - * URL of the form... - * - * aahRX://<multicastIP>:<port> - * - * to receive, decode and render the re-transmitted content. - * - * setRetransmitEndpoint may only be called before setDataSource has been - * called; while the player is in the Idle state. - * - * @param endpoint the address and UDP port of the re-transmission target or - * null if no re-transmission is to be performed. - * @throws IllegalStateException if it is called in an invalid state - * @throws IllegalArgumentException if the retransmit endpoint is supplied, - * but invalid. - * - * {@hide} pending API council - */ - public void setRetransmitEndpoint(InetSocketAddress endpoint) { } - - /** * Releases the resources held by this {@code MediaPlayer2} object. * * It is considered good practice to call this method when you're diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java index 222c66ea4551..e3d5ac07665e 100644 --- a/media/java/android/media/MediaPlayer2Impl.java +++ b/media/java/android/media/MediaPlayer2Impl.java @@ -2964,53 +2964,6 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { } /** - * Sets the target UDP re-transmit endpoint for the low level player. - * Generally, the address portion of the endpoint is an IP multicast - * address, although a unicast address would be equally valid. When a valid - * retransmit endpoint has been set, the media player will not decode and - * render the media presentation locally. Instead, the player will attempt - * to re-multiplex its media data using the Android@Home RTP profile and - * re-transmit to the target endpoint. Receiver devices (which may be - * either the same as the transmitting device or different devices) may - * instantiate, prepare, and start a receiver player using a setDataSource - * URL of the form... - * - * aahRX://<multicastIP>:<port> - * - * to receive, decode and render the re-transmitted content. - * - * setRetransmitEndpoint may only be called before setDataSource has been - * called; while the player is in the Idle state. - * - * @param endpoint the address and UDP port of the re-transmission target or - * null if no re-transmission is to be performed. - * @throws IllegalStateException if it is called in an invalid state - * @throws IllegalArgumentException if the retransmit endpoint is supplied, - * but invalid. - * - * {@hide} pending API council - */ - @Override - public void setRetransmitEndpoint(InetSocketAddress endpoint) - throws IllegalStateException, IllegalArgumentException - { - String addrString = null; - int port = 0; - - if (null != endpoint) { - addrString = endpoint.getAddress().getHostAddress(); - port = endpoint.getPort(); - } - - int ret = native_setRetransmitEndpoint(addrString, port); - if (ret != 0) { - throw new IllegalArgumentException("Illegal re-transmit endpoint; native ret " + ret); - } - } - - private native final int native_setRetransmitEndpoint(String addrString, int port); - - /** * Releases the resources held by this {@code MediaPlayer2} object. * * It is considered good practice to call this method when you're diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 5670bd8fcba3..ae2649a70a55 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -580,8 +580,20 @@ public class MediaSession2 implements AutoCloseable { }; /** - * Base builder class for MediaSession2 and its subclass. - * + * Base builder class for MediaSession2 and its subclass. Any change in this class should be + * also applied to the subclasses {@link MediaSession2.Builder} and + * {@link MediaLibraryService2.MediaLibrarySessionBuilder}. + * <p> + * APIs here should be package private, but should have documentations for developers. + * Otherwise, javadoc will generate documentation with the generic types such as follows. + * <pre>U extends BuilderBase<T, U, C> setSessionCallback(Executor executor, C callback)</pre> + * <p> + * This class is hidden to prevent from generating test stub, which fails with + * 'unexpected bound' because it tries to auto generate stub class as follows. + * <pre>abstract static class BuilderBase< + * T extends android.media.MediaSession2, + * U extends android.media.MediaSession2.BuilderBase< + * T, U, C extends android.media.MediaSession2.SessionCallback>, C></pre> * @hide */ static abstract class BuilderBase @@ -599,9 +611,9 @@ public class MediaSession2 implements AutoCloseable { * <p> * Set {@code null} to reset. * - * @param volumeProvider The provider that will handle volume changes. Can be {@code null} + * @param volumeProvider The provider that will handle volume changes. Can be {@code null}. */ - public U setVolumeProvider(@Nullable VolumeProvider volumeProvider) { + U setVolumeProvider(@Nullable VolumeProvider2 volumeProvider) { mProvider.setVolumeProvider_impl(volumeProvider); return (U) this; } @@ -619,7 +631,7 @@ public class MediaSession2 implements AutoCloseable { * <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li> * </ul> */ - public U setRatingType(@Rating2.Style int type) { + U setRatingType(@Rating2.Style int type) { mProvider.setRatingType_impl(type); return (U) this; } @@ -631,7 +643,7 @@ public class MediaSession2 implements AutoCloseable { * * @param pi The intent to launch to show UI for this session. */ - public U setSessionActivity(@Nullable PendingIntent pi) { + U setSessionActivity(@Nullable PendingIntent pi) { mProvider.setSessionActivity_impl(pi); return (U) this; } @@ -646,7 +658,7 @@ public class MediaSession2 implements AutoCloseable { * @throws IllegalArgumentException if id is {@code null} * @return */ - public U setId(@NonNull String id) { + U setId(@NonNull String id) { mProvider.setId_impl(id); return (U) this; } @@ -658,7 +670,7 @@ public class MediaSession2 implements AutoCloseable { * @param callback session callback. * @return */ - public U setSessionCallback(@NonNull @CallbackExecutor Executor executor, + U setSessionCallback(@NonNull @CallbackExecutor Executor executor, @NonNull C callback) { mProvider.setSessionCallback_impl(executor, callback); return (U) this; @@ -671,7 +683,7 @@ public class MediaSession2 implements AutoCloseable { * @throws IllegalStateException if the session with the same id is already exists for the * package. */ - public T build() { + T build() { return mProvider.build_impl(); } } @@ -682,13 +694,44 @@ public class MediaSession2 implements AutoCloseable { * Any incoming event from the {@link MediaController2} will be handled on the thread * that created session with the {@link Builder#build()}. */ - // TODO(jaewan): Add setRatingType() - // TODO(jaewan): Add setSessionActivity() + // Override all methods just to show them with the type instead of generics in Javadoc. + // This workarounds javadoc issue described in the MediaSession2.BuilderBase. public static final class Builder extends BuilderBase<MediaSession2, Builder, SessionCallback> { public Builder(Context context, @NonNull MediaPlayerInterface player) { super((instance) -> ApiLoader.getProvider(context).createMediaSession2Builder( context, (Builder) instance, player)); } + + @Override + public Builder setVolumeProvider(@Nullable VolumeProvider2 volumeProvider) { + return super.setVolumeProvider(volumeProvider); + } + + @Override + public Builder setRatingType(@Rating2.Style int type) { + return super.setRatingType(type); + } + + @Override + public Builder setSessionActivity(@Nullable PendingIntent pi) { + return super.setSessionActivity(pi); + } + + @Override + public Builder setId(@NonNull String id) { + return super.setId(id); + } + + @Override + public Builder setSessionCallback(@NonNull Executor executor, + @Nullable SessionCallback callback) { + return super.setSessionCallback(executor, callback); + } + + @Override + public MediaSession2 build() { + return super.build(); + } } /** @@ -1035,8 +1078,8 @@ public class MediaSession2 implements AutoCloseable { * If the new player is successfully set, {@link PlaybackListener} * will be called to tell the current playback state of the new player. * <p> - * You can also specify a volume provider. If so, playback in the player is considered as - * remote playback. + * For the remote playback case which you want to handle volume by yourself, use + * {@link #setPlayer(MediaPlayerInterface, VolumeProvider2)}. * * @param player a {@link MediaPlayerInterface} that handles actual media playback in your app. * @throws IllegalArgumentException if the player is {@code null}. @@ -1051,10 +1094,10 @@ public class MediaSession2 implements AutoCloseable { * @param player a {@link MediaPlayerInterface} that handles actual media playback in your app. * @param volumeProvider a volume provider * @see #setPlayer(MediaPlayerInterface) - * @see Builder#setVolumeProvider(VolumeProvider) + * @see Builder#setVolumeProvider(VolumeProvider2) */ public void setPlayer(@NonNull MediaPlayerInterface player, - @NonNull VolumeProvider volumeProvider) { + @NonNull VolumeProvider2 volumeProvider) { mProvider.setPlayer_impl(player, volumeProvider); } diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 3eb9d529b756..fefa1ede849e 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -28,11 +28,13 @@ import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.database.Cursor; import android.media.MediaScannerConnection.MediaScannerConnectionClient; import android.net.Uri; import android.os.Environment; +import android.os.FileUtils; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; @@ -47,22 +49,17 @@ import android.util.Log; import com.android.internal.database.SortCursor; -import libcore.io.Streams; - import java.io.Closeable; import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; -import static android.content.ContentProvider.maybeAddUserId; -import static android.content.pm.PackageManager.NameNotFoundException; - /** * RingtoneManager provides access to ringtones, notification, and other types * of sounds. It manages querying the different media providers and combines the @@ -855,7 +852,7 @@ public class RingtoneManager { final Uri cacheUri = getCacheForType(type, context.getUserId()); try (InputStream in = openRingtone(context, ringtoneUri); OutputStream out = resolver.openOutputStream(cacheUri)) { - Streams.copy(in, out); + FileUtils.copy(in, out); } catch (IOException e) { Log.w(TAG, "Failed to cache ringtone: " + e); } @@ -960,7 +957,7 @@ public class RingtoneManager { // Copy contents to external ringtone storage. Throws IOException if the copy fails. try (final InputStream input = mContext.getContentResolver().openInputStream(fileUri); final OutputStream output = new FileOutputStream(outFile)) { - Streams.copy(input, output); + FileUtils.copy(input, output); } // Tell MediaScanner about the new file. Wait for it to assign a {@link Uri}. diff --git a/media/java/android/media/VolumeProvider2.java b/media/java/android/media/VolumeProvider2.java index 00746e25c247..53ba4663aaf6 100644 --- a/media/java/android/media/VolumeProvider2.java +++ b/media/java/android/media/VolumeProvider2.java @@ -32,7 +32,7 @@ import java.lang.annotation.RetentionPolicy; * {@link #setCurrentVolume(int)} each time the volume being provided changes. * <p> * You can set a volume provider on a session by calling - * {@link MediaSession2#setPlayer(MediaPlayerInterface, VolumeProvider)}. + * {@link MediaSession2#setPlayer(MediaPlayerInterface, VolumeProvider2)}. * * @hide */ diff --git a/media/java/android/media/update/FrameLayoutHelper.java b/media/java/android/media/update/FrameLayoutHelper.java deleted file mode 100644 index 983dc703a9b5..000000000000 --- a/media/java/android/media/update/FrameLayoutHelper.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media.update; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.widget.FrameLayout; - -/** - * Helper class for connecting the public API to an updatable implementation. - * - * @see ViewProvider - * - * @hide - */ -public abstract class FrameLayoutHelper<T extends ViewProvider> extends FrameLayout { - /** @hide */ - final public T mProvider; - - /** @hide */ - public FrameLayoutHelper(ProviderCreator<T> creator, - Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - mProvider = creator.createProvider(this, new SuperProvider()); - } - - /** @hide */ - // TODO @SystemApi - public T getProvider() { - return mProvider; - } - - @Override - public CharSequence getAccessibilityClassName() { - return mProvider.getAccessibilityClassName_impl(); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return mProvider.onTouchEvent_impl(ev); - } - - @Override - public boolean onTrackballEvent(MotionEvent ev) { - return mProvider.onTrackballEvent_impl(ev); - } - - @Override - public void onFinishInflate() { - mProvider.onFinishInflate_impl(); - } - - @Override - public void setEnabled(boolean enabled) { - mProvider.setEnabled_impl(enabled); - } - - @Override - protected void onAttachedToWindow() { - mProvider.onAttachedToWindow_impl(); - } - - @Override - protected void onDetachedFromWindow() { - mProvider.onDetachedFromWindow_impl(); - } - - /** @hide */ - public class SuperProvider implements ViewProvider { - @Override - public CharSequence getAccessibilityClassName_impl() { - return FrameLayoutHelper.super.getAccessibilityClassName(); - } - - @Override - public boolean onTouchEvent_impl(MotionEvent ev) { - return FrameLayoutHelper.super.onTouchEvent(ev); - } - - @Override - public boolean onTrackballEvent_impl(MotionEvent ev) { - return FrameLayoutHelper.super.onTrackballEvent(ev); - } - - @Override - public void onFinishInflate_impl() { - FrameLayoutHelper.super.onFinishInflate(); - } - - @Override - public void setEnabled_impl(boolean enabled) { - FrameLayoutHelper.super.setEnabled(enabled); - } - - @Override - public void onAttachedToWindow_impl() { - FrameLayoutHelper.super.onAttachedToWindow(); - } - - @Override - public void onDetachedFromWindow_impl() { - FrameLayoutHelper.super.onDetachedFromWindow(); - } - } - - /** @hide */ - @FunctionalInterface - public interface ProviderCreator<U extends ViewProvider> { - U createProvider(FrameLayoutHelper<U> instance, ViewProvider superProvider); - } -} diff --git a/media/java/android/media/update/MediaControlView2Provider.java b/media/java/android/media/update/MediaControlView2Provider.java index 95fe36317164..e155e5f0d8b9 100644 --- a/media/java/android/media/update/MediaControlView2Provider.java +++ b/media/java/android/media/update/MediaControlView2Provider.java @@ -18,6 +18,7 @@ package android.media.update; import android.annotation.SystemApi; import android.media.session.MediaController; +import android.util.AttributeSet; import android.view.View; /** @@ -34,12 +35,12 @@ import android.view.View; * @hide */ // TODO @SystemApi -public interface MediaControlView2Provider extends ViewProvider { +public interface MediaControlView2Provider extends ViewGroupProvider { + void initialize(AttributeSet attrs, int defStyleAttr, int defStyleRes); + void setController_impl(MediaController controller); - boolean isShowing_impl(); void setButtonVisibility_impl(int button, int visibility); void requestPlayButtonFocus_impl(); - void onVisibilityAggregated_impl(boolean isVisible); void setTimeout_impl(long timeout); long getTimeout_impl(); } diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java index 9abf34a740ff..41162e0a15ea 100644 --- a/media/java/android/media/update/MediaSession2Provider.java +++ b/media/java/android/media/update/MediaSession2Provider.java @@ -30,7 +30,7 @@ import android.media.MediaSession2.ControllerInfo; import android.media.MediaSession2.PlaylistParams; import android.media.MediaSession2.SessionCallback; import android.media.SessionToken2; -import android.media.VolumeProvider; +import android.media.VolumeProvider2; import android.os.Bundle; import android.os.ResultReceiver; @@ -44,7 +44,7 @@ import java.util.concurrent.Executor; public interface MediaSession2Provider extends TransportControlProvider { void close_impl(); void setPlayer_impl(MediaPlayerInterface player); - void setPlayer_impl(MediaPlayerInterface player, VolumeProvider volumeProvider); + void setPlayer_impl(MediaPlayerInterface player, VolumeProvider2 volumeProvider); MediaPlayerInterface getPlayer_impl(); SessionToken2 getToken_impl(); List<ControllerInfo> getConnectedControllers_impl(); @@ -116,7 +116,7 @@ public interface MediaSession2Provider extends TransportControlProvider { } interface BuilderBaseProvider<T extends MediaSession2, C extends SessionCallback> { - void setVolumeProvider_impl(VolumeProvider volumeProvider); + void setVolumeProvider_impl(VolumeProvider2 volumeProvider); void setRatingType_impl(int type); void setSessionActivity_impl(PendingIntent pi); void setId_impl(String id); diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 862a4024e36d..57f04cc88ff5 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -67,10 +67,11 @@ import java.util.concurrent.Executor; * @hide */ public interface StaticProvider { - MediaControlView2Provider createMediaControlView2( - MediaControlView2 instance, ViewProvider superProvider); - VideoView2Provider createVideoView2( - VideoView2 instance, ViewProvider superProvider, + MediaControlView2Provider createMediaControlView2(MediaControlView2 instance, + ViewGroupProvider superProvider, ViewGroupProvider privateProvider, + @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes); + VideoView2Provider createVideoView2(VideoView2 instance, + ViewGroupProvider superProvider, ViewGroupProvider privateProvider, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes); CommandProvider createMediaSession2Command(MediaSession2.Command instance, diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java index 10f03d223d9f..7251180cb6a9 100644 --- a/media/java/android/media/update/VideoView2Provider.java +++ b/media/java/android/media/update/VideoView2Provider.java @@ -16,12 +16,14 @@ package android.media.update; +import android.annotation.SystemApi; import android.media.AudioAttributes; import android.media.MediaPlayerInterface; import android.media.session.MediaController; import android.media.session.PlaybackState; import android.media.session.MediaSession; import android.net.Uri; +import android.util.AttributeSet; import android.widget.MediaControlView2; import android.widget.VideoView2; @@ -43,7 +45,9 @@ import java.util.concurrent.Executor; * @hide */ // TODO @SystemApi -public interface VideoView2Provider extends ViewProvider { +public interface VideoView2Provider extends ViewGroupProvider { + void initialize(AttributeSet attrs, int defStyleAttr, int defStyleRes); + void setMediaControlView2_impl(MediaControlView2 mediaControlView); MediaController getMediaController_impl(); MediaControlView2 getMediaControlView2_impl(); @@ -52,6 +56,9 @@ public interface VideoView2Provider extends ViewProvider { void setSpeed_impl(float speed); void setAudioFocusRequest_impl(int focusGain); void setAudioAttributes_impl(AudioAttributes attributes); + /** + * @hide + */ void setRouteAttributes_impl(List<String> routeCategories, MediaPlayerInterface player); // TODO: remove setRouteAttributes_impl with MediaSession.Callback once MediaSession2 is ready. void setRouteAttributes_impl(List<String> routeCategories, MediaSession.Callback sessionPlayer); diff --git a/media/java/android/media/update/ViewGroupHelper.java b/media/java/android/media/update/ViewGroupHelper.java new file mode 100644 index 000000000000..2c4f9b92bdac --- /dev/null +++ b/media/java/android/media/update/ViewGroupHelper.java @@ -0,0 +1,354 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.update; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +/** + * Helper class for connecting the public API to an updatable implementation. + * + * @see ViewGroupProvider + * + * @hide + */ +public abstract class ViewGroupHelper<T extends ViewGroupProvider> extends ViewGroup { + /** @hide */ + final public T mProvider; + + /** @hide */ + public ViewGroupHelper(ProviderCreator<T> creator, + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mProvider = creator.createProvider(this, new SuperProvider(), + new PrivateProvider()); + } + + /** @hide */ + // TODO @SystemApi + public T getProvider() { + return mProvider; + } + + @Override + protected void onAttachedToWindow() { + mProvider.onAttachedToWindow_impl(); + } + + @Override + protected void onDetachedFromWindow() { + mProvider.onDetachedFromWindow_impl(); + } + + @Override + public CharSequence getAccessibilityClassName() { + return mProvider.getAccessibilityClassName_impl(); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return mProvider.onTouchEvent_impl(ev); + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + return mProvider.onTrackballEvent_impl(ev); + } + + @Override + public void onFinishInflate() { + mProvider.onFinishInflate_impl(); + } + + @Override + public void setEnabled(boolean enabled) { + mProvider.setEnabled_impl(enabled); + } + + @Override + public void onVisibilityAggregated(boolean isVisible) { + mProvider.onVisibilityAggregated_impl(isVisible); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + mProvider.onLayout_impl(changed, left, top, right, bottom); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mProvider.onMeasure_impl(widthMeasureSpec, heightMeasureSpec); + } + + @Override + protected int getSuggestedMinimumWidth() { + return mProvider.getSuggestedMinimumWidth_impl(); + } + + @Override + protected int getSuggestedMinimumHeight() { + return mProvider.getSuggestedMinimumHeight_impl(); + } + + // setMeasuredDimension is final + + @Override + protected boolean checkLayoutParams(LayoutParams p) { + return mProvider.checkLayoutParams_impl(p); + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + return mProvider.generateDefaultLayoutParams_impl(); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return mProvider.generateLayoutParams_impl(attrs); + } + + @Override + protected LayoutParams generateLayoutParams(LayoutParams lp) { + return mProvider.generateLayoutParams_impl(lp); + } + + @Override + public boolean shouldDelayChildPressedState() { + return mProvider.shouldDelayChildPressedState_impl(); + } + + @Override + protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed) { + mProvider.measureChildWithMargins_impl(child, + parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed); + } + + /** @hide */ + public class SuperProvider implements ViewGroupProvider { + @Override + public CharSequence getAccessibilityClassName_impl() { + return ViewGroupHelper.super.getAccessibilityClassName(); + } + + @Override + public boolean onTouchEvent_impl(MotionEvent ev) { + return ViewGroupHelper.super.onTouchEvent(ev); + } + + @Override + public boolean onTrackballEvent_impl(MotionEvent ev) { + return ViewGroupHelper.super.onTrackballEvent(ev); + } + + @Override + public void onFinishInflate_impl() { + ViewGroupHelper.super.onFinishInflate(); + } + + @Override + public void setEnabled_impl(boolean enabled) { + ViewGroupHelper.super.setEnabled(enabled); + } + + @Override + public void onAttachedToWindow_impl() { + ViewGroupHelper.super.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow_impl() { + ViewGroupHelper.super.onDetachedFromWindow(); + } + + @Override + public void onVisibilityAggregated_impl(boolean isVisible) { + ViewGroupHelper.super.onVisibilityAggregated(isVisible); + } + + @Override + public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) { + // abstract method; no super + } + + @Override + public void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec) { + ViewGroupHelper.super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public int getSuggestedMinimumWidth_impl() { + return ViewGroupHelper.super.getSuggestedMinimumWidth(); + } + + @Override + public int getSuggestedMinimumHeight_impl() { + return ViewGroupHelper.super.getSuggestedMinimumHeight(); + } + + @Override + public void setMeasuredDimension_impl(int measuredWidth, int measuredHeight) { + ViewGroupHelper.super.setMeasuredDimension(measuredWidth, measuredHeight); + } + + @Override + public boolean checkLayoutParams_impl(LayoutParams p) { + return ViewGroupHelper.super.checkLayoutParams(p); + } + + @Override + public LayoutParams generateDefaultLayoutParams_impl() { + return ViewGroupHelper.super.generateDefaultLayoutParams(); + } + + @Override + public LayoutParams generateLayoutParams_impl(AttributeSet attrs) { + return ViewGroupHelper.super.generateLayoutParams(attrs); + } + + @Override + public LayoutParams generateLayoutParams_impl(LayoutParams lp) { + return ViewGroupHelper.super.generateLayoutParams(lp); + } + + @Override + public boolean shouldDelayChildPressedState_impl() { + return ViewGroupHelper.super.shouldDelayChildPressedState(); + } + + @Override + public void measureChildWithMargins_impl(View child, + int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed) { + ViewGroupHelper.super.measureChildWithMargins(child, + parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed); + } + } + + /** @hide */ + public class PrivateProvider implements ViewGroupProvider { + @Override + public CharSequence getAccessibilityClassName_impl() { + return ViewGroupHelper.this.getAccessibilityClassName(); + } + + @Override + public boolean onTouchEvent_impl(MotionEvent ev) { + return ViewGroupHelper.this.onTouchEvent(ev); + } + + @Override + public boolean onTrackballEvent_impl(MotionEvent ev) { + return ViewGroupHelper.this.onTrackballEvent(ev); + } + + @Override + public void onFinishInflate_impl() { + ViewGroupHelper.this.onFinishInflate(); + } + + @Override + public void setEnabled_impl(boolean enabled) { + ViewGroupHelper.this.setEnabled(enabled); + } + + @Override + public void onAttachedToWindow_impl() { + ViewGroupHelper.this.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow_impl() { + ViewGroupHelper.this.onDetachedFromWindow(); + } + + @Override + public void onVisibilityAggregated_impl(boolean isVisible) { + ViewGroupHelper.this.onVisibilityAggregated(isVisible); + } + + @Override + public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) { + ViewGroupHelper.this.onLayout(changed, left, top, right, bottom); + } + + @Override + public void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec) { + ViewGroupHelper.this.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public int getSuggestedMinimumWidth_impl() { + return ViewGroupHelper.this.getSuggestedMinimumWidth(); + } + + @Override + public int getSuggestedMinimumHeight_impl() { + return ViewGroupHelper.this.getSuggestedMinimumHeight(); + } + + @Override + public void setMeasuredDimension_impl(int measuredWidth, int measuredHeight) { + ViewGroupHelper.this.setMeasuredDimension(measuredWidth, measuredHeight); + } + + @Override + public boolean checkLayoutParams_impl(LayoutParams p) { + return ViewGroupHelper.this.checkLayoutParams(p); + } + + @Override + public LayoutParams generateDefaultLayoutParams_impl() { + return ViewGroupHelper.this.generateDefaultLayoutParams(); + } + + @Override + public LayoutParams generateLayoutParams_impl(AttributeSet attrs) { + return ViewGroupHelper.this.generateLayoutParams(attrs); + } + + @Override + public LayoutParams generateLayoutParams_impl(LayoutParams lp) { + return ViewGroupHelper.this.generateLayoutParams(lp); + } + + @Override + public boolean shouldDelayChildPressedState_impl() { + return ViewGroupHelper.this.shouldDelayChildPressedState(); + } + + @Override + public void measureChildWithMargins_impl(View child, + int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed) { + ViewGroupHelper.this.measureChildWithMargins(child, + parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed); + } + } + + /** @hide */ + @FunctionalInterface + public interface ProviderCreator<T extends ViewGroupProvider> { + T createProvider(ViewGroupHelper<T> instance, ViewGroupProvider superProvider, + ViewGroupProvider privateProvider); + } +} diff --git a/media/java/android/media/update/ViewProvider.java b/media/java/android/media/update/ViewGroupProvider.java index 0dd8f388a8fe..5f125298cd49 100644 --- a/media/java/android/media/update/ViewProvider.java +++ b/media/java/android/media/update/ViewGroupProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,10 @@ package android.media.update; import android.annotation.SystemApi; +import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup.LayoutParams; /** * Interface for connecting the public API to an updatable implementation. @@ -33,8 +36,8 @@ import android.view.MotionEvent; * @hide */ // TODO @SystemApi -public interface ViewProvider { - // TODO Add more (all?) methods from View +public interface ViewGroupProvider { + // View methods void onAttachedToWindow_impl(); void onDetachedFromWindow_impl(); CharSequence getAccessibilityClassName_impl(); @@ -42,4 +45,22 @@ public interface ViewProvider { boolean onTrackballEvent_impl(MotionEvent ev); void onFinishInflate_impl(); void setEnabled_impl(boolean enabled); + void onVisibilityAggregated_impl(boolean isVisible); + void onLayout_impl(boolean changed, int left, int top, int right, int bottom); + void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec); + int getSuggestedMinimumWidth_impl(); + int getSuggestedMinimumHeight_impl(); + void setMeasuredDimension_impl(int measuredWidth, int measuredHeight); + + // ViewGroup methods + boolean checkLayoutParams_impl(LayoutParams p); + LayoutParams generateDefaultLayoutParams_impl(); + LayoutParams generateLayoutParams_impl(AttributeSet attrs); + LayoutParams generateLayoutParams_impl(LayoutParams lp); + boolean shouldDelayChildPressedState_impl(); + void measureChildWithMargins_impl(View child, int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed); + + // ViewManager methods + // ViewParent methods } diff --git a/media/jni/android_media_AudioPresentation.h b/media/jni/android_media_AudioPresentation.h new file mode 100644 index 000000000000..71b8dacfbdfa --- /dev/null +++ b/media/jni/android_media_AudioPresentation.h @@ -0,0 +1,173 @@ +/* + * Copyright 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_MEDIA_AUDIO_PRESENTATION_H_ +#define _ANDROID_MEDIA_AUDIO_PRESENTATION_H_ + +#include "jni.h" + +#include <media/AudioPresentationInfo.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> + +#include <nativehelper/ScopedLocalRef.h> + +namespace android { + +struct JAudioPresentationInfo { + struct fields_t { + jclass clazz; + jmethodID constructID; + + // list parameters + jclass listclazz; + jmethodID listConstructId; + jmethodID listAddId; + + void init(JNIEnv *env) { + jclass lclazz = env->FindClass("android/media/AudioPresentation"); + if (lclazz == NULL) { + return; + } + + clazz = (jclass)env->NewGlobalRef(lclazz); + if (clazz == NULL) { + return; + } + + constructID = env->GetMethodID(clazz, "<init>", + "(IILjava/util/Map;Ljava/lang/String;IZZZ)V"); + env->DeleteLocalRef(lclazz); + + // list objects + jclass llistclazz = env->FindClass("java/util/ArrayList"); + CHECK(llistclazz != NULL); + listclazz = static_cast<jclass>(env->NewGlobalRef(llistclazz)); + CHECK(listclazz != NULL); + listConstructId = env->GetMethodID(listclazz, "<init>", "()V"); + CHECK(listConstructId != NULL); + listAddId = env->GetMethodID(listclazz, "add", "(Ljava/lang/Object;)Z"); + CHECK(listAddId != NULL); + env->DeleteLocalRef(llistclazz); + } + + void exit(JNIEnv *env) { + env->DeleteGlobalRef(clazz); + clazz = NULL; + env->DeleteGlobalRef(listclazz); + listclazz = NULL; + } + }; + + static status_t ConvertMessageToMap(JNIEnv *env, const sp<AMessage> &msg, jobject *map) { + ScopedLocalRef<jclass> hashMapClazz(env, env->FindClass("java/util/HashMap")); + + if (hashMapClazz.get() == NULL) { + return -EINVAL; + } + jmethodID hashMapConstructID = + env->GetMethodID(hashMapClazz.get(), "<init>", "()V"); + + if (hashMapConstructID == NULL) { + return -EINVAL; + } + jmethodID hashMapPutID = + env->GetMethodID( + hashMapClazz.get(), + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + + if (hashMapPutID == NULL) { + return -EINVAL; + } + + jobject hashMap = env->NewObject(hashMapClazz.get(), hashMapConstructID); + + for (size_t i = 0; i < msg->countEntries(); ++i) { + AMessage::Type valueType; + const char *key = msg->getEntryNameAt(i, &valueType); + + if (!strncmp(key, "android._", 9)) { + // don't expose private keys (starting with android._) + continue; + } + + jobject valueObj = NULL; + + AString val; + CHECK(msg->findString(key, &val)); + + valueObj = env->NewStringUTF(val.c_str()); + + if (valueObj != NULL) { + jstring keyObj = env->NewStringUTF(key); + + env->CallObjectMethod(hashMap, hashMapPutID, keyObj, valueObj); + + env->DeleteLocalRef(keyObj); keyObj = NULL; + env->DeleteLocalRef(valueObj); valueObj = NULL; + } + } + + *map = hashMap; + + return OK; + } + + jobject asJobject(JNIEnv *env, const fields_t& fields, const AudioPresentationInfo &info) { + jobject list = env->NewObject(fields.listclazz, fields.listConstructId); + + for (size_t i = 0; i < info.countPresentations(); ++i) { + const sp<AudioPresentation> &ap = info.getPresentation(i); + jobject jLabelObject; + + sp<AMessage> labelMessage = new AMessage(); + for (size_t i = 0; i < ap->mLabels.size(); ++i) { + labelMessage->setString(ap->mLabels.keyAt(i).string(), + ap->mLabels.valueAt(i).string()); + } + if (ConvertMessageToMap(env, labelMessage, &jLabelObject) != OK) { + return NULL; + } + jstring jLanguage = env->NewStringUTF(ap->mLanguage.string()); + + jobject jValueObj = env->NewObject(fields.clazz, fields.constructID, + static_cast<jint>(ap->mPresentationId), + static_cast<jint>(ap->mProgramId), + jLabelObject, + jLanguage, + static_cast<jint>(ap->mMasteringIndication), + static_cast<jboolean>((ap->mAudioDescriptionAvailable == 1) ? + 1 : 0), + static_cast<jboolean>((ap->mSpokenSubtitlesAvailable == 1) ? + 1 : 0), + static_cast<jboolean>((ap->mDialogueEnhancementAvailable == 1) ? + 1 : 0)); + if (jValueObj == NULL) { + env->DeleteLocalRef(jLanguage); jLanguage = NULL; + return NULL; + } + + env->CallBooleanMethod(list, fields.listAddId, jValueObj); + env->DeleteLocalRef(jValueObj); jValueObj = NULL; + env->DeleteLocalRef(jLanguage); jLanguage = NULL; + } + return list; + } +}; +} // namespace android + +#endif // _ANDROID_MEDIA_AUDIO_PRESENTATION_H_ diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp index 90ee8a6814d5..4e1599bdc7d2 100644 --- a/media/jni/android_media_MediaPlayer2.cpp +++ b/media/jni/android_media_MediaPlayer2.cpp @@ -1099,45 +1099,6 @@ static void android_media_MediaPlayer2_attachAuxEffect(JNIEnv *env, jobject thi process_media_player_call( env, thiz, mp->attachAuxEffect(effectId), NULL, NULL ); } -static jint -android_media_MediaPlayer2_setRetransmitEndpoint(JNIEnv *env, jobject thiz, - jstring addrString, jint port) { - sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); - if (mp == NULL ) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return INVALID_OPERATION; - } - - const char *cAddrString = NULL; - - if (NULL != addrString) { - cAddrString = env->GetStringUTFChars(addrString, NULL); - if (cAddrString == NULL) { // Out of memory - return NO_MEMORY; - } - } - ALOGV("setRetransmitEndpoint: %s:%d", - cAddrString ? cAddrString : "(null)", port); - - status_t ret; - if (cAddrString && (port > 0xFFFF)) { - ret = BAD_VALUE; - } else { - ret = mp->setRetransmitEndpoint(cAddrString, - static_cast<uint16_t>(port)); - } - - if (NULL != addrString) { - env->ReleaseStringUTFChars(addrString, cAddrString); - } - - if (ret == INVALID_OPERATION ) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - } - - return (jint) ret; -} - static void android_media_MediaPlayer2_setNextMediaPlayer(JNIEnv *env, jobject thiz, jobject java_player) { @@ -1418,7 +1379,6 @@ static const JNINativeMethod gMethods[] = { {"setAudioSessionId", "(I)V", (void *)android_media_MediaPlayer2_set_audio_session_id}, {"_setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer2_setAuxEffectSendLevel}, {"attachAuxEffect", "(I)V", (void *)android_media_MediaPlayer2_attachAuxEffect}, - {"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer2_setRetransmitEndpoint}, {"setNextMediaPlayer", "(Landroid/media/MediaPlayer2;)V", (void *)android_media_MediaPlayer2_setNextMediaPlayer}, // Modular DRM { "_prepareDrm", "([B[B)V", (void *)android_media_MediaPlayer2_prepareDrm }, diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index 8b01aef81c67..9f165bc97768 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -30,6 +30,7 @@ import android.content.res.ObbInfo; import android.content.res.ObbScanner; import android.os.Binder; import android.os.Environment.UserEnvironment; +import android.os.FileUtils; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; @@ -43,7 +44,6 @@ import com.android.internal.os.IParcelFileDescriptorFactory; import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; -import libcore.io.Streams; import java.io.File; import java.io.FileInputStream; @@ -260,7 +260,7 @@ public class DefaultContainerService extends IntentService { in = new FileInputStream(sourcePath); out = new ParcelFileDescriptor.AutoCloseOutputStream( target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE)); - Streams.copy(in, out); + FileUtils.copy(in, out); } finally { IoUtils.closeQuietly(out); IoUtils.closeQuietly(in); diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java index 6fe8975577a4..9a66b07fb74f 100644 --- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java +++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java @@ -94,7 +94,7 @@ public class Assistant extends NotificationAssistantService { infile = mFile.openRead(); readXml(infile); } catch (FileNotFoundException e) { - // No data yet + Log.d(TAG, "File doesn't exist or isn't readable yet"); } catch (IOException e) { Log.e(TAG, "Unable to read channel impressions", e); } catch (NumberFormatException | XmlPullParserException e) { diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java index 7c35b48310f2..db48f610471d 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java @@ -325,7 +325,8 @@ public class AssistantTest extends ServiceTestCase<Assistant> { int dismiss2 = 777; String key2 = mAssistant.getKey("pkg2", 2, "channel2"); - String xml = "<assistant version=\"1\">\n" + String xml = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" + + "<assistant version=\"1\">\n" + "<impression-set key=\"" + key1 + "\" " + "dismisses=\"" + dismiss1 + "\" views=\"" + views1 + "\" streak=\"" + streak1 + "\"/>\n" @@ -377,7 +378,6 @@ public class AssistantTest extends ServiceTestCase<Assistant> { mAssistant.insertImpressions(key2, ci2); mAssistant.insertImpressions(key3, ci3); - XmlSerializer serializer = new FastXmlSerializer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java index 79838962ef1e..c23f22648764 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java @@ -17,21 +17,20 @@ package com.android.settingslib.core.instrumentation; import android.app.Activity; -import android.content.Context; +import android.arch.lifecycle.Lifecycle.Event; +import android.arch.lifecycle.LifecycleObserver; +import android.arch.lifecycle.OnLifecycleEvent; import android.content.Intent; import android.os.SystemClock; import com.android.internal.logging.nano.MetricsProto; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnPause; -import com.android.settingslib.core.lifecycle.events.OnResume; import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN; /** * Logs visibility change of a fragment. */ -public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPause { +public class VisibilityLoggerMixin implements LifecycleObserver { private static final String TAG = "VisibilityLoggerMixin"; @@ -55,7 +54,7 @@ public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPau mMetricsFeature = metricsFeature; } - @Override + @OnLifecycleEvent(Event.ON_RESUME) public void onResume() { mVisibleTimestamp = SystemClock.elapsedRealtime(); if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) { @@ -63,7 +62,7 @@ public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPau } } - @Override + @OnLifecycleEvent(Event.ON_PAUSE) public void onPause() { mVisibleTimestamp = 0; if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java index a2648861d1d8..1ab6afe5b99d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java @@ -18,6 +18,7 @@ package com.android.settingslib.core.instrumentation; import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN; import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; @@ -30,6 +31,8 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; import com.android.internal.logging.nano.MetricsProto; import com.android.settingslib.SettingsLibRobolectricTestRunner; import com.android.settingslib.TestConfig; @@ -39,6 +42,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; @@ -110,6 +115,30 @@ public class VisibilityLoggerMixinTest { .hidden(nullable(Context.class), anyInt()); } + @Test + public void activityShouldBecomeVisibleAndHide() { + ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class); + TestActivity testActivity = ac.get(); + MockitoAnnotations.initMocks(testActivity); + ac.create().start().resume(); + verify(testActivity.mMetricsFeatureProvider, times(1)).visible(any(), anyInt(), anyInt()); + ac.pause().stop().destroy(); + verify(testActivity.mMetricsFeatureProvider, times(1)).hidden(any(), anyInt()); + } + + public static class TestActivity extends FragmentActivity { + @Mock + MetricsFeatureProvider mMetricsFeatureProvider; + + @Override + public void onCreate(Bundle savedInstanceState) { + VisibilityLoggerMixin mixin = new VisibilityLoggerMixin( + TestInstrumentable.TEST_METRIC, mMetricsFeatureProvider); + getLifecycle().addObserver(mixin); + super.onCreate(savedInstanceState); + } + } + private final class TestInstrumentable implements Instrumentable { public static final int TEST_METRIC = 12345; diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 997fe6dd44bb..100c2aa51b4d 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -25,16 +25,22 @@ android:baselineAligned="false" android:clickable="false" android:clipChildren="false" - android:clipToPadding="false" - android:paddingTop="0dp" - android:gravity="center_vertical" - android:orientation="horizontal"> + android:clipToPadding="false"> + + <View + android:id="@+id/qs_footer_divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_gravity="top" + android:background="?android:attr/dividerHorizontal"/> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_marginTop="1dp" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" + android:layout_gravity="center_vertical" android:gravity="end" > <LinearLayout diff --git a/packages/SystemUI/res/layout/screen_pinning_request_buttons_land.xml b/packages/SystemUI/res/layout/screen_pinning_request_buttons_land.xml index b5ef1d72fd8f..61fe906d65fc 100644 --- a/packages/SystemUI/res/layout/screen_pinning_request_buttons_land.xml +++ b/packages/SystemUI/res/layout/screen_pinning_request_buttons_land.xml @@ -78,7 +78,25 @@ android:id="@+id/screen_pinning_home_group" android:layout_height="@dimen/screen_pinning_request_button_width" android:layout_width="@dimen/screen_pinning_request_button_height" - android:layout_weight="0" > + android:layout_weight="0" + android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent"> + + <ImageView + android:id="@+id/screen_pinning_home_bg_light" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:scaleType="matrix" + android:src="@drawable/screen_pinning_light_bg_circ" /> + + <ImageView + android:id="@+id/screen_pinning_home_bg" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:scaleType="matrix" + android:paddingLeft="@dimen/screen_pinning_request_inner_padding" + android:paddingTop="@dimen/screen_pinning_request_inner_padding" + android:paddingBottom="@dimen/screen_pinning_request_inner_padding" + android:src="@drawable/screen_pinning_bg_circ" /> <ImageView android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout/screen_pinning_request_buttons_sea.xml b/packages/SystemUI/res/layout/screen_pinning_request_buttons_sea.xml index f3a6d44f5ec7..d1ca2ce5935c 100644 --- a/packages/SystemUI/res/layout/screen_pinning_request_buttons_sea.xml +++ b/packages/SystemUI/res/layout/screen_pinning_request_buttons_sea.xml @@ -80,7 +80,27 @@ android:id="@+id/screen_pinning_home_group" android:layout_height="@dimen/screen_pinning_request_button_width" android:layout_width="@dimen/screen_pinning_request_button_height" - android:layout_weight="0" > + android:layout_weight="0" + android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent" > + + <ImageView + android:id="@+id/screen_pinning_home_bg_light" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:scaleType="matrix" + android:layout_marginLeft="@dimen/screen_pinning_request_seascape_padding_negative" + android:src="@drawable/screen_pinning_light_bg_circ" /> + + <ImageView + android:id="@+id/screen_pinning_home_bg" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:scaleType="matrix" + android:layout_marginLeft="@dimen/screen_pinning_request_seascape_button_offset" + android:paddingRight="@dimen/screen_pinning_request_inner_padding" + android:paddingTop="@dimen/screen_pinning_request_inner_padding" + android:paddingBottom="@dimen/screen_pinning_request_inner_padding" + android:src="@drawable/screen_pinning_bg_circ" /> <ImageView android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index a9e5adfa531a..cdd417f5baf1 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -73,15 +73,18 @@ android:id="@+id/volume_row_slider_frame" android:padding="0dp" android:layout_width="@dimen/volume_dialog_panel_width" + android:layoutDirection="rtl" android:layout_height="150dp"> <SeekBar android:id="@+id/volume_row_slider" + android:clickable="true" android:padding="0dp" android:layout_margin="0dp" android:layout_width="150dp" android:layout_height="@dimen/volume_dialog_panel_width" + android:layoutDirection="rtl" android:layout_gravity="center" - android:rotation="270" /> + android:rotation="90" /> </FrameLayout> <com.android.keyguard.AlphaOptimizedImageButton diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index f9e1069cfe95..90e3b1e73454 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -16,6 +16,8 @@ package com.android.systemui.shared.system; +import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; +import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; @@ -31,6 +33,7 @@ import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.IAssistDataReceiver; import android.app.WindowConfiguration.ActivityType; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -47,6 +50,7 @@ import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.Settings; import android.util.IconDrawableFactory; import android.util.Log; import android.view.IRecentsAnimationController; @@ -436,4 +440,23 @@ public class ActivityManagerWrapper { Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e); } } + + /** + * @return whether there is currently a locked task (ie. in screen pinning). + */ + public boolean isLockToAppActive() { + try { + return ActivityManager.getService().getLockTaskModeState() != LOCK_TASK_MODE_NONE; + } catch (RemoteException e) { + return false; + } + } + + /** + * @return whether screen pinning is enabled. + */ + public boolean isLockToAppEnabled() { + final ContentResolver cr = AppGlobals.getInitialApplication().getContentResolver(); + return Settings.System.getInt(cr, Settings.System.LOCK_TO_APP_ENABLED, 0) != 0; + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 653e5000f72c..eedc50f23d60 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -707,6 +707,9 @@ public class KeyguardViewMediator extends SystemUI { && !mLockPatternUtils.isLockScreenDisabled( KeyguardUpdateMonitor.getCurrentUser()), mSecondaryDisplayShowing, true /* forceCallbacks */); + } else { + // The system's keyguard is disabled or missing. + setShowingLocked(false, mSecondaryDisplayShowing, true); } mStatusBarKeyguardViewManager = diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 6b0d592a4d01..6ccb81772db9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -36,7 +36,6 @@ import com.android.systemui.statusbar.ExpandableOutlineView; public class QSContainerImpl extends FrameLayout { private final Point mSizePoint = new Point(); - private final Path mClipPath = new Path(); private int mHeightOverride = -1; protected View mQSPanel; @@ -46,7 +45,6 @@ public class QSContainerImpl extends FrameLayout { private QSCustomizer mQSCustomizer; private View mQSFooter; private View mBackground; - private float mRadius; private int mSideMargins; public QSContainerImpl(Context context, AttributeSet attrs) { @@ -62,8 +60,6 @@ public class QSContainerImpl extends FrameLayout { mQSCustomizer = findViewById(R.id.qs_customize); mQSFooter = findViewById(R.id.qs_footer); mBackground = findViewById(R.id.quick_settings_background); - mRadius = getResources().getDimensionPixelSize( - Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); setClickable(true); @@ -115,18 +111,6 @@ public class QSContainerImpl extends FrameLayout { updateExpansion(); } - @Override - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - boolean ret; - canvas.save(); - if (child != mQSCustomizer) { - canvas.clipPath(mClipPath); - } - ret = super.drawChild(canvas, child, drawingTime); - canvas.restore(); - return ret; - } - /** * Overrides the height of this view (post-layout), so that the content is clipped to that * height and the background is set to that height. @@ -146,10 +130,6 @@ public class QSContainerImpl extends FrameLayout { mQSFooter.setTranslationY(height - mQSFooter.getHeight()); mBackground.setTop(mQSPanel.getTop()); mBackground.setBottom(height); - - ExpandableOutlineView.getRoundedRectPath(0, 0, getWidth(), height, mRadius, - mRadius, - mClipPath); } protected int calculateContainerHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index 76baee4c836f..9c87e1b12091 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -75,6 +75,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, private boolean mListening; private boolean mShowEmergencyCallsOnly; + private View mDivider; protected MultiUserSwitch mMultiUserSwitch; private ImageView mMultiUserAvatar; @@ -93,8 +94,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, @Override protected void onFinishInflate() { super.onFinishInflate(); - Resources res = getResources(); - + mDivider = findViewById(R.id.qs_footer_divider); mEdit = findViewById(android.R.id.edit); mEdit.setOnClickListener(view -> Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> @@ -162,6 +162,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, @Nullable private TouchAnimator createSettingsAlphaAnimator() { return new TouchAnimator.Builder() + .addFloat(mDivider, "alpha", 0, 1) .addFloat(mCarrierText, "alpha", 0, 1) .addFloat(mActionsContainer, "alpha", 0, 1) .build(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 1da4deb61176..409c753c147c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -415,7 +415,7 @@ public class Recents extends SystemUI final int activityType = runningTask != null ? runningTask.configuration.windowConfiguration.getActivityType() : ACTIVITY_TYPE_UNDEFINED; - boolean screenPinningActive = sSystemServicesProxy.isScreenPinningActive(); + boolean screenPinningActive = ActivityManagerWrapper.getInstance().isLockToAppActive(); boolean isRunningTaskInHomeOrRecentsStack = activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS; if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index ee1b09109d38..3f6f30bba8c4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -386,8 +386,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener public void toggleRecents(int growTarget) { // Skip preloading if the task is locked - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.isScreenPinningActive()) { + if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { return; } @@ -409,8 +408,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener MutableBoolean isHomeStackVisible = new MutableBoolean(true); long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime; + SystemServicesProxy ssp = Recents.getSystemServices(); if (ssp.isRecentsActivityVisible(isHomeStackVisible)) { - RecentsDebugFlags debugFlags = Recents.getDebugFlags(); RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); if (!launchState.launchedWithAltTab) { @@ -466,8 +465,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener public void preloadRecents() { // Skip preloading if the task is locked - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.isScreenPinningActive()) { + if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { return; } @@ -481,6 +479,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // RecentsActivity) only if there is a task to animate to. Post this to ensure that we // don't block the touch feedback on the nav bar button which triggers this. mHandler.post(() -> { + SystemServicesProxy ssp = Recents.getSystemServices(); if (!ssp.isRecentsActivityVisible(null)) { ActivityManager.RunningTaskInfo runningTask = ActivityManagerWrapper.getInstance().getRunningTask(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 613d9fbb985c..93fd34aa0519 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -379,29 +379,6 @@ public class SystemServicesProxy { } /** - * Returns a global setting. - */ - public int getGlobalSetting(Context context, String setting) { - ContentResolver cr = context.getContentResolver(); - return Settings.Global.getInt(cr, setting, 0); - } - - /** - * Returns a system setting. - */ - public int getSystemSetting(Context context, String setting) { - ContentResolver cr = context.getContentResolver(); - return Settings.System.getInt(cr, setting, 0); - } - - /** - * Returns a system property. - */ - public String getSystemProperty(String key) { - return SystemProperties.get(key); - } - - /** * Returns the smallest width/height. */ public int getDeviceSmallestWidth() { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 5be2900831b3..3cc3273c0db4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -95,6 +95,7 @@ import com.android.systemui.recents.views.grid.GridTaskView; import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; import com.android.systemui.recents.views.grid.TaskViewFocusFrame; +import com.android.systemui.shared.system.ActivityManagerWrapper; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -2187,8 +2188,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal private void readSystemFlags() { SystemServicesProxy ssp = Recents.getSystemServices(); mTouchExplorationEnabled = ssp.isTouchExplorationEnabled(); - mScreenPinningEnabled = ssp.getSystemSetting(getContext(), - Settings.System.LOCK_TO_APP_ENABLED) != 0; + mScreenPinningEnabled = ActivityManagerWrapper.getInstance().isLockToAppEnabled(); } private void updateStackActionButtonVisibility() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index f5f62b85ac97..11bdf6b3c72e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -789,7 +789,7 @@ public class CommandQueue extends IStatusBar.Stub { break; case MSG_SHOW_PINNING_TOAST_ENTER_EXIT: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).showPinningEnterExitToast(msg.arg1 != 0); + mCallbacks.get(i).showPinningEnterExitToast((Boolean) msg.obj); } break; case MSG_SHOW_PINNING_TOAST_ESCAPE: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 3dfb9130af2e..3ebeb4d45c26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -379,7 +379,7 @@ public class CarStatusBar extends StatusBar implements // Because space is usually constrained in the auto use-case, there should not be a // pinned notification when the shade has been expanded. Ensure this by removing all heads- // up notifications. - mHeadsUpManager.releaseAllImmediately(); + mHeadsUpManager.removeAllHeadsUpEntries(); super.animateExpandNotificationsPanel(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java deleted file mode 100644 index c45c5386eda4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ /dev/null @@ -1,455 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.content.res.Resources; -import android.support.v4.util.ArraySet; -import android.util.Log; -import android.util.Pools; -import android.view.View; -import android.view.ViewTreeObserver; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Dumpable; -import com.android.systemui.statusbar.ExpandableNotificationRow; -import com.android.systemui.statusbar.NotificationData; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.HashSet; -import java.util.Stack; - -/** - * A implementation of HeadsUpManager for phone and car. - */ -public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, - ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback, - OnHeadsUpChangedListener { - private static final String TAG = "HeadsUpManagerPhone"; - private static final boolean DEBUG = false; - - private final View mStatusBarWindowView; - private final int mStatusBarHeight; - private final NotificationGroupManager mGroupManager; - private final StatusBar mBar; - private final VisualStabilityManager mVisualStabilityManager; - - private boolean mReleaseOnExpandFinish; - private boolean mTrackingHeadsUp; - private HashSet<String> mSwipedOutKeys = new HashSet<>(); - private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); - private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed - = new ArraySet<>(); - private boolean mIsExpanded; - private int[] mTmpTwoArray = new int[2]; - private boolean mHeadsUpGoingAway; - private boolean mWaitingOnCollapseWhenGoingAway; - private boolean mIsObserving; - private int mStatusBarState; - - private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() { - private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>(); - - @Override - public HeadsUpEntryPhone acquire() { - if (!mPoolObjects.isEmpty()) { - return mPoolObjects.pop(); - } - return new HeadsUpEntryPhone(); - } - - @Override - public boolean release(@NonNull HeadsUpEntryPhone instance) { - instance.reset(); - mPoolObjects.push(instance); - return true; - } - }; - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Constructor: - - public HeadsUpManagerPhone(@NonNull final Context context, @NonNull View statusBarWindowView, - @NonNull NotificationGroupManager groupManager, @NonNull StatusBar bar, - @NonNull VisualStabilityManager visualStabilityManager) { - super(context); - - mStatusBarWindowView = statusBarWindowView; - mGroupManager = groupManager; - mBar = bar; - mVisualStabilityManager = visualStabilityManager; - - Resources resources = mContext.getResources(); - mStatusBarHeight = resources.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); - - addListener(new OnHeadsUpChangedListener() { - @Override - public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) { - if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged"); - updateTouchableRegionListener(); - } - }); - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Public methods: - - /** - * Decides whether a click is invalid for a notification, i.e it has not been shown long enough - * that a user might have consciously clicked on it. - * - * @param key the key of the touched notification - * @return whether the touch is invalid and should be discarded - */ - public boolean shouldSwallowClick(@NonNull String key) { - HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key); - if (entry != null && mClock.currentTimeMillis() < entry.postTime) { - return true; - } - return false; - } - - public void onExpandingFinished() { - if (mReleaseOnExpandFinish) { - releaseAllImmediately(); - mReleaseOnExpandFinish = false; - } else { - for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { - if (isHeadsUp(entry.key)) { - // Maybe the heads-up was removed already - removeHeadsUpEntry(entry); - } - } - } - mEntriesToRemoveAfterExpand.clear(); - } - - /** - * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry - * from the list even after a Heads Up Notification is gone. - */ - public void setTrackingHeadsUp(boolean trackingHeadsUp) { - mTrackingHeadsUp = trackingHeadsUp; - } - - /** - * Notify that the status bar panel gets expanded or collapsed. - * - * @param isExpanded True to notify expanded, false to notify collapsed. - */ - public void setIsPanelExpanded(boolean isExpanded) { - if (isExpanded != mIsExpanded) { - mIsExpanded = isExpanded; - if (isExpanded) { - // make sure our state is sane - mWaitingOnCollapseWhenGoingAway = false; - mHeadsUpGoingAway = false; - updateTouchableRegionListener(); - } - } - } - - /** - * Set the current state of the statusbar. - */ - public void setStatusBarState(int statusBarState) { - mStatusBarState = statusBarState; - } - - /** - * Set that we are exiting the headsUp pinned mode, but some notifications might still be - * animating out. This is used to keep the touchable regions in a sane state. - */ - public void setHeadsUpGoingAway(boolean headsUpGoingAway) { - if (headsUpGoingAway != mHeadsUpGoingAway) { - mHeadsUpGoingAway = headsUpGoingAway; - if (!headsUpGoingAway) { - waitForStatusBarLayout(); - } - updateTouchableRegionListener(); - } - } - - /** - * Notifies that a remote input textbox in notification gets active or inactive. - * @param entry The entry of the target notification. - * @param remoteInputActive True to notify active, False to notify inactive. - */ - public void setRemoteInputActive( - @NonNull NotificationData.Entry entry, boolean remoteInputActive) { - HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key); - if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) { - headsUpEntry.remoteInputActive = remoteInputActive; - if (remoteInputActive) { - headsUpEntry.removeAutoRemovalCallbacks(); - } else { - headsUpEntry.updateEntry(false /* updatePostTime */); - } - } - } - - @VisibleForTesting - public void removeMinimumDisplayTimeForTesting() { - mMinimumDisplayTime = 1; - mHeadsUpNotificationDecay = 1; - mTouchAcceptanceDelay = 1; - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // HeadsUpManager public methods overrides: - - @Override - public boolean isTrackingHeadsUp() { - return mTrackingHeadsUp; - } - - @Override - public void snooze() { - super.snooze(); - mReleaseOnExpandFinish = true; - } - - /** - * React to the removal of the notification in the heads up. - * - * @return true if the notification was removed and false if it still needs to be kept around - * for a bit since it wasn't shown long enough - */ - @Override - public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) { - if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) { - return super.removeNotification(key, ignoreEarliestRemovalTime); - } else { - HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key); - entry.removeAsSoonAsPossible(); - return false; - } - } - - public void addSwipedOutNotification(@NonNull String key) { - mSwipedOutKeys.add(key); - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Dumpable overrides: - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("HeadsUpManagerPhone state:"); - dumpInternal(fd, pw, args); - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // ViewTreeObserver.OnComputeInternalInsetsListener overrides: - - /** - * Overridden from TreeObserver. - */ - @Override - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (mIsExpanded || mBar.isBouncerShowing()) { - // The touchable region is always the full area when expanded - return; - } - if (hasPinnedHeadsUp()) { - ExpandableNotificationRow topEntry = getTopEntry().row; - if (topEntry.isChildInGroup()) { - final ExpandableNotificationRow groupSummary - = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification()); - if (groupSummary != null) { - topEntry = groupSummary; - } - } - topEntry.getLocationOnScreen(mTmpTwoArray); - int minX = mTmpTwoArray[0]; - int maxX = mTmpTwoArray[0] + topEntry.getWidth(); - int maxY = topEntry.getIntrinsicHeight(); - - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(minX, 0, maxX, maxY); - } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); - } - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // VisualStabilityManager.Callback overrides: - - @Override - public void onReorderingAllowed() { - mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); - for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { - if (isHeadsUp(entry.key)) { - // Maybe the heads-up was removed already - removeHeadsUpEntry(entry); - } - } - mEntriesToRemoveWhenReorderingAllowed.clear(); - mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true); - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // HeadsUpManager utility (protected) methods overrides: - - @Override - protected HeadsUpEntry createHeadsUpEntry() { - return mEntryPool.acquire(); - } - - @Override - protected void releaseHeadsUpEntry(HeadsUpEntry entry) { - mEntryPool.release((HeadsUpEntryPhone) entry); - } - - @Override - protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) { - return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded - || super.shouldHeadsUpBecomePinned(entry); - } - - @Override - protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { - super.dumpInternal(fd, pw, args); - pw.print(" mStatusBarState="); pw.println(mStatusBarState); - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Protected utility methods: - - @Nullable - protected HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) { - return (HeadsUpEntryPhone) getHeadsUpEntry(key); - } - - @Nullable - protected HeadsUpEntryPhone getTopHeadsUpEntryPhone() { - return (HeadsUpEntryPhone) getTopHeadsUpEntry(); - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Private utility methods: - - private boolean wasShownLongEnough(@NonNull String key) { - if (mSwipedOutKeys.contains(key)) { - // We always instantly dismiss views being manually swiped out. - mSwipedOutKeys.remove(key); - return true; - } - - HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key); - HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone(); - if (headsUpEntry != topEntry) { - return true; - } - return headsUpEntry.wasShownLongEnough(); - } - - /** - * We need to wait on the whole panel to collapse, before we can remove the touchable region - * listener. - */ - private void waitForStatusBarLayout() { - mWaitingOnCollapseWhenGoingAway = true; - mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, - int oldTop, int oldRight, int oldBottom) { - if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) { - mStatusBarWindowView.removeOnLayoutChangeListener(this); - mWaitingOnCollapseWhenGoingAway = false; - updateTouchableRegionListener(); - } - } - }); - } - - private void updateTouchableRegionListener() { - boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway - || mWaitingOnCollapseWhenGoingAway; - if (shouldObserve == mIsObserving) { - return; - } - if (shouldObserve) { - mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); - mStatusBarWindowView.requestLayout(); - } else { - mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - } - mIsObserving = shouldObserve; - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // HeadsUpEntryPhone: - - protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry { - public void setEntry(@NonNull final NotificationData.Entry entry) { - Runnable removeHeadsUpRunnable = () -> { - if (!mVisualStabilityManager.isReorderingAllowed()) { - mEntriesToRemoveWhenReorderingAllowed.add(entry); - mVisualStabilityManager.addReorderingAllowedCallback( - HeadsUpManagerPhone.this); - } else if (!mTrackingHeadsUp) { - removeHeadsUpEntry(entry); - } else { - mEntriesToRemoveAfterExpand.add(entry); - } - }; - - super.setEntry(entry, removeHeadsUpRunnable); - } - - public boolean wasShownLongEnough() { - return earliestRemovaltime < mClock.currentTimeMillis(); - } - - @Override - public void updateEntry(boolean updatePostTime) { - super.updateEntry(updatePostTime); - - if (mEntriesToRemoveAfterExpand.contains(entry)) { - mEntriesToRemoveAfterExpand.remove(entry); - } - if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { - mEntriesToRemoveWhenReorderingAllowed.remove(entry); - } - } - - @Override - public void expanded(boolean expanded) { - if (this.expanded == expanded) { - return; - } - - this.expanded = expanded; - if (expanded) { - removeAutoRemovalCallbacks(); - } else { - updateEntry(false /* updatePostTime */); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index 2bfdefe39017..c85571c1895d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -23,7 +23,7 @@ import android.view.ViewConfiguration; import com.android.systemui.Gefingerpoken; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; /** @@ -31,7 +31,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; */ public class HeadsUpTouchHelper implements Gefingerpoken { - private HeadsUpManagerPhone mHeadsUpManager; + private HeadsUpManager mHeadsUpManager; private NotificationStackScrollLayout mStackScroller; private int mTrackingPointer; private float mTouchSlop; @@ -43,7 +43,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { private NotificationPanelView mPanel; private ExpandableNotificationRow mPickedChild; - public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager, + public HeadsUpTouchHelper(HeadsUpManager headsUpManager, NotificationStackScrollLayout stackScroller, NotificationPanelView notificationPanelView) { mHeadsUpManager = headsUpManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 242be71a42ae..1239a9ea0240 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -591,11 +591,9 @@ public class NavigationBarFragment extends Fragment implements Callbacks { ButtonDispatcher backButton = mNavigationBarView.getBackButton(); backButton.setLongClickable(true); - backButton.setOnLongClickListener(this::onLongPressBackRecents); ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); homeButton.setOnTouchListener(this::onHomeTouch); - homeButton.setOnLongClickListener(this::onHomeLongClick); ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton(); accessibilityButton.setOnClickListener(this::onAccessibilityClick); @@ -605,6 +603,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton(); rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick); rotateSuggestionButton.setOnHoverListener(this::onRotateSuggestionHover); + updateScreenPinningGestures(); } private boolean onHomeTouch(View v, MotionEvent event) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index cd220a7053a2..c37dd55cc6ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -300,7 +300,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } } } - return mRecentsAnimationStarted || mGestureHelper.onInterceptTouchEvent(event); + return mGestureHelper.onInterceptTouchEvent(event) || mRecentsAnimationStarted; } public void abortCurrentGesture() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 52d005cb152a..cd2e77ae2591 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -68,12 +68,14 @@ import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackStateAnimator; import java.util.List; +import java.util.Collection; public class NotificationPanelView extends PanelView implements ExpandableView.OnHeightChangedListener, @@ -1569,7 +1571,7 @@ public class NotificationPanelView extends PanelView implements private void updatePanelExpanded() { boolean isExpanded = !isFullyCollapsed(); if (mPanelExpanded != isExpanded) { - mHeadsUpManager.setIsPanelExpanded(isExpanded); + mHeadsUpManager.setIsExpanded(isExpanded); mStatusBar.setPanelExpanded(isExpanded); mPanelExpanded = isExpanded; } @@ -2336,7 +2338,7 @@ public class NotificationPanelView extends PanelView implements } @Override - public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { + public void setHeadsUpManager(HeadsUpManager headsUpManager) { super.setHeadsUpManager(headsUpManager); mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller, this); @@ -2628,8 +2630,8 @@ public class NotificationPanelView extends PanelView implements } } - public void setPulsing(boolean pulsing) { - mKeyguardStatusView.setPulsing(pulsing); + public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) { + mKeyguardStatusView.setPulsing(pulsing != null); positionClockAndNotifications(); mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1] + mKeyguardStatusView.getClockBottom()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 6daabede7f32..2b7e4747a837 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -50,7 +50,7 @@ import com.android.systemui.classifier.FalsingManager; import com.android.systemui.doze.DozeLog; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; +import com.android.systemui.statusbar.policy.HeadsUpManager; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -75,7 +75,7 @@ public abstract class PanelView extends FrameLayout { } protected StatusBar mStatusBar; - protected HeadsUpManagerPhone mHeadsUpManager; + protected HeadsUpManager mHeadsUpManager; private float mPeekHeight; private float mHintDistance; @@ -1252,7 +1252,7 @@ public abstract class PanelView extends FrameLayout { */ protected abstract int getClearAllHeight(); - public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { + public void setHeadsUpManager(HeadsUpManager headsUpManager) { mHeadsUpManager = headsUpManager; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 3777a6c9409e..1bf719ae68af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -208,7 +208,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -220,7 +219,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; @@ -811,14 +809,15 @@ public class StatusBar extends SystemUI implements DemoMode, .commit(); mIconController = Dependency.get(StatusBarIconController.class); - mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this, - mVisualStabilityManager); + mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager); + mHeadsUpManager.setBar(this); mHeadsUpManager.addListener(this); mHeadsUpManager.addListener(mNotificationPanel); mHeadsUpManager.addListener(mGroupManager); mHeadsUpManager.addListener(mVisualStabilityManager); mNotificationPanel.setHeadsUpManager(mHeadsUpManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); + mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager); putComponent(HeadsUpManager.class, mHeadsUpManager); mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager); @@ -1349,8 +1348,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPerformRemoveNotification(StatusBarNotification n) { - if (mStackScroller.hasPulsingNotifications() && - !mHeadsUpManager.hasHeadsUpNotifications()) { + if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) { // We were showing a pulse for a notification, but no notifications are pulsing anymore. // Finish the pulse. mDozeScrimController.pulseOutNow(); @@ -2099,8 +2097,9 @@ public class StatusBar extends SystemUI implements DemoMode, } public void maybeEscalateHeadsUp() { - mHeadsUpManager.getAllEntries().forEach(entry -> { - final StatusBarNotification sbn = entry.notification; + Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries(); + for (HeadsUpManager.HeadsUpEntry entry : entries) { + final StatusBarNotification sbn = entry.entry.notification; final Notification notification = sbn.getNotification(); if (notification.fullScreenIntent != null) { if (DEBUG) { @@ -2110,11 +2109,11 @@ public class StatusBar extends SystemUI implements DemoMode, EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, sbn.getKey()); notification.fullScreenIntent.send(); - entry.notifyFullScreenIntentLaunched(); + entry.entry.notifyFullScreenIntentLaunched(); } catch (PendingIntent.CanceledException e) { } } - }); + } mHeadsUpManager.releaseAllImmediately(); } @@ -4659,22 +4658,24 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPulseStarted() { callback.onPulseStarted(); - if (mHeadsUpManager.hasHeadsUpNotifications()) { + Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries = + mHeadsUpManager.getAllEntries(); + if (!pulsingEntries.isEmpty()) { // Only pulse the stack scroller if there's actually something to show. // Otherwise just show the always-on screen. - setPulsing(true); + setPulsing(pulsingEntries); } } @Override public void onPulseFinished() { callback.onPulseFinished(); - setPulsing(false); + setPulsing(null); } - private void setPulsing(boolean pulsing) { + private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) { mNotificationPanel.setPulsing(pulsing); - mVisualStabilityManager.setPulsing(pulsing); + mVisualStabilityManager.setPulsing(pulsing != null); mIgnoreTouchWhilePulsing = false; } }, reason); @@ -4822,7 +4823,7 @@ public class StatusBar extends SystemUI implements DemoMode, // for heads up notifications - protected HeadsUpManagerPhone mHeadsUpManager; + protected HeadsUpManager mHeadsUpManager; private AboveShelfObserver mAboveShelfObserver; @@ -4925,7 +4926,7 @@ public class StatusBar extends SystemUI implements DemoMode, // Release the HUN notification to the shade. if (isPresenterFullyCollapsed()) { - HeadsUpUtil.setIsClickedHeadsUpNotification(row, true); + HeadsUpManager.setIsClickedNotification(row, true); } // // In most cases, when FLAG_AUTO_CANCEL is set, the notification will diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index a2b896dc015b..53dfb244c776 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -16,68 +16,118 @@ package com.android.systemui.statusbar.policy; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; -import android.os.SystemClock; import android.os.Handler; import android.os.Looper; -import android.util.ArrayMap; +import android.os.SystemClock; import android.provider.Settings; +import android.support.v4.util.ArraySet; +import android.util.ArrayMap; import android.util.Log; +import android.util.Pools; +import android.view.View; +import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.Iterator; -import java.util.stream.Stream; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.Stack; /** * A manager which handles heads up notifications which is a special mode where * they simply peek from the top of the screen. */ -public class HeadsUpManager { +public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener, + VisualStabilityManager.Callback { private static final String TAG = "HeadsUpManager"; private static final boolean DEBUG = false; private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; + private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag; - protected final Clock mClock = new Clock(); - protected final Context mContext; - protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); - protected final Handler mHandler = new Handler(Looper.getMainLooper()); - - protected int mHeadsUpNotificationDecay; - protected int mMinimumDisplayTime; - protected int mTouchAcceptanceDelay; - protected int mSnoozeLengthMs; - protected boolean mHasPinnedNotification; - protected int mUser; + private final int mHeadsUpNotificationDecay; + private final int mMinimumDisplayTime; - private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); + private final int mTouchAcceptanceDelay; private final ArrayMap<String, Long> mSnoozedPackages; - private final ContentObserver mSettingsObserver; + private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); + private final int mDefaultSnoozeLengthMs; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() { + + private Stack<HeadsUpEntry> mPoolObjects = new Stack<>(); - public HeadsUpManager(@NonNull final Context context) { + @Override + public HeadsUpEntry acquire() { + if (!mPoolObjects.isEmpty()) { + return mPoolObjects.pop(); + } + return new HeadsUpEntry(); + } + + @Override + public boolean release(HeadsUpEntry instance) { + instance.reset(); + mPoolObjects.push(instance); + return true; + } + }; + + private final View mStatusBarWindowView; + private final int mStatusBarHeight; + private final Context mContext; + private final NotificationGroupManager mGroupManager; + private StatusBar mBar; + private int mSnoozeLengthMs; + private ContentObserver mSettingsObserver; + private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); + private HashSet<String> mSwipedOutKeys = new HashSet<>(); + private int mUser; + private Clock mClock; + private boolean mReleaseOnExpandFinish; + private boolean mTrackingHeadsUp; + private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); + private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed + = new ArraySet<>(); + private boolean mIsExpanded; + private boolean mHasPinnedNotification; + private int[] mTmpTwoArray = new int[2]; + private boolean mHeadsUpGoingAway; + private boolean mWaitingOnCollapseWhenGoingAway; + private boolean mIsObserving; + private boolean mRemoteInputActive; + private float mExpandedHeight; + private VisualStabilityManager mVisualStabilityManager; + private int mStatusBarState; + + public HeadsUpManager(final Context context, View statusBarWindowView, + NotificationGroupManager groupManager) { mContext = context; - Resources resources = context.getResources(); - mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); - mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); + Resources resources = mContext.getResources(); mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); mSnoozedPackages = new ArrayMap<>(); - int defaultSnoozeLengthMs = - resources.getInteger(R.integer.heads_up_default_snooze_length_ms); + mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms); + mSnoozeLengthMs = mDefaultSnoozeLengthMs; + mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); + mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); + mClock = new Clock(); mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(), - SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs); + SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs); mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { @@ -92,26 +142,47 @@ public class HeadsUpManager { context.getContentResolver().registerContentObserver( Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false, mSettingsObserver); + mStatusBarWindowView = statusBarWindowView; + mGroupManager = groupManager; + mStatusBarHeight = resources.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); } - /** - * Adds an OnHeadUpChangedListener to observe events. - */ - public void addListener(@NonNull OnHeadsUpChangedListener listener) { + private void updateTouchableRegionListener() { + boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway + || mWaitingOnCollapseWhenGoingAway; + if (shouldObserve == mIsObserving) { + return; + } + if (shouldObserve) { + mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); + mStatusBarWindowView.requestLayout(); + } else { + mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + } + mIsObserving = shouldObserve; + } + + public void setBar(StatusBar bar) { + mBar = bar; + } + + public void addListener(OnHeadsUpChangedListener listener) { mListeners.add(listener); } - /** - * Removes the OnHeadUpChangedListener from the observer list. - */ - public void removeListener(@NonNull OnHeadsUpChangedListener listener) { + public void removeListener(OnHeadsUpChangedListener listener) { mListeners.remove(listener); } + public StatusBar getBar() { + return mBar; + } + /** * Called when posting a new notification to the heads up. */ - public void showNotification(@NonNull NotificationData.Entry headsUp) { + public void showNotification(NotificationData.Entry headsUp) { if (DEBUG) Log.v(TAG, "showNotification"); addHeadsUpEntry(headsUp); updateNotification(headsUp, true); @@ -121,7 +192,7 @@ public class HeadsUpManager { /** * Called when updating or posting a notification to the heads up. */ - public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) { + public void updateNotification(NotificationData.Entry headsUp, boolean alert) { if (DEBUG) Log.v(TAG, "updateNotification"); headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); @@ -133,13 +204,14 @@ public class HeadsUpManager { // with the groupmanager return; } - headsUpEntry.updateEntry(true /* updatePostTime */); + headsUpEntry.updateEntry(); setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp)); } } - private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) { - HeadsUpEntry headsUpEntry = createHeadsUpEntry(); + private void addHeadsUpEntry(NotificationData.Entry entry) { + HeadsUpEntry headsUpEntry = mEntryPool.acquire(); + // This will also add the entry to the sortedList headsUpEntry.setEntry(entry); mHeadsUpEntries.put(entry.key, headsUpEntry); @@ -151,17 +223,16 @@ public class HeadsUpManager { entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } - protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) { - return hasFullScreenIntent(entry); + private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) { + return mStatusBarState != StatusBarState.KEYGUARD + && !mIsExpanded || hasFullScreenIntent(entry); } - protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) { + private boolean hasFullScreenIntent(NotificationData.Entry entry) { return entry.notification.getNotification().fullScreenIntent != null; } - protected void setEntryPinned( - @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) { - if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned); + private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) { ExpandableNotificationRow row = headsUpEntry.entry.row; if (row.isPinned() != isPinned) { row.setPinned(isPinned); @@ -176,35 +247,33 @@ public class HeadsUpManager { } } - protected void removeHeadsUpEntry(NotificationData.Entry entry) { + private void removeHeadsUpEntry(NotificationData.Entry entry) { HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key); - onHeadsUpEntryRemoved(remove); - releaseHeadsUpEntry(remove); - } - - protected void onHeadsUpEntryRemoved(HeadsUpEntry remove) { - NotificationData.Entry entry = remove.entry; entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); entry.row.setHeadsUp(false); setEntryPinned(remove, false /* isPinned */); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } + mEntryPool.release(remove); + } + + public void removeAllHeadsUpEntries() { + for (String key : mHeadsUpEntries.keySet()) { + removeHeadsUpEntry(mHeadsUpEntries.get(key).entry); + } } - protected void updatePinnedMode() { + private void updatePinnedMode() { boolean hasPinnedNotification = hasPinnedNotificationInternal(); if (hasPinnedNotification == mHasPinnedNotification) { return; } - if (DEBUG) { - Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " + - hasPinnedNotification); - } mHasPinnedNotification = hasPinnedNotification; if (mHasPinnedNotification) { MetricsLogger.count(mContext, "note_peek", 1); } + updateTouchableRegionListener(); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); } @@ -216,36 +285,47 @@ public class HeadsUpManager { * @return true if the notification was removed and false if it still needs to be kept around * for a bit since it wasn't shown long enough */ - public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) { - if (DEBUG) Log.v(TAG, "removeNotification"); - releaseImmediately(key); - return true; + public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) { + if (DEBUG) Log.v(TAG, "remove"); + if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) { + releaseImmediately(key); + return true; + } else { + getHeadsUpEntry(key).removeAsSoonAsPossible(); + return false; + } + } + + private boolean wasShownLongEnough(String key) { + HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); + HeadsUpEntry topEntry = getTopEntry(); + if (mSwipedOutKeys.contains(key)) { + // We always instantly dismiss views being manually swiped out. + mSwipedOutKeys.remove(key); + return true; + } + if (headsUpEntry != topEntry) { + return true; + } + return headsUpEntry.wasShownLongEnough(); } - /** - * Returns if the given notification is in the Heads Up Notification list or not. - */ public boolean isHeadsUp(String key) { return mHeadsUpEntries.containsKey(key); } /** - * Pushes any current Heads Up notification down into the shade. + * Push any current Heads Up notification down into the shade. */ public void releaseAllImmediately() { if (DEBUG) Log.v(TAG, "releaseAllImmediately"); - Iterator<HeadsUpEntry> iterator = mHeadsUpEntries.values().iterator(); - while (iterator.hasNext()) { - HeadsUpEntry entry = iterator.next(); - iterator.remove(); - onHeadsUpEntryRemoved(entry); + ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet()); + for (String key : keys) { + releaseImmediately(key); } } - /** - * Pushes the given Heads Up notification down into the shade. - */ - public void releaseImmediately(@NonNull String key) { + public void releaseImmediately(String key) { HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); if (headsUpEntry == null) { return; @@ -254,14 +334,11 @@ public class HeadsUpManager { removeHeadsUpEntry(shadeEntry); } - /** - * Returns if the given notification is snoozed or not. - */ - public boolean isSnoozed(@NonNull String packageName) { + public boolean isSnoozed(String packageName) { final String key = snoozeKey(packageName, mUser); Long snoozedUntil = mSnoozedPackages.get(key); if (snoozedUntil != null) { - if (snoozedUntil > mClock.currentTimeMillis()) { + if (snoozedUntil > SystemClock.elapsedRealtime()) { if (DEBUG) Log.v(TAG, key + " snoozed"); return true; } @@ -270,61 +347,33 @@ public class HeadsUpManager { return false; } - /** - * Snoozes all current Heads Up Notifications. - */ public void snooze() { for (String key : mHeadsUpEntries.keySet()) { HeadsUpEntry entry = mHeadsUpEntries.get(key); String packageName = entry.entry.notification.getPackageName(); mSnoozedPackages.put(snoozeKey(packageName, mUser), - mClock.currentTimeMillis() + mSnoozeLengthMs); + SystemClock.elapsedRealtime() + mSnoozeLengthMs); } + mReleaseOnExpandFinish = true; } - private static String snoozeKey(@NonNull String packageName, int user) { + private static String snoozeKey(String packageName, int user) { return user + "," + packageName; } - protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) { + private HeadsUpEntry getHeadsUpEntry(String key) { return mHeadsUpEntries.get(key); } - /** - * Returns the entry of given Heads Up Notification. - * - * @param key Key of heads up notification - */ - public NotificationData.Entry getEntry(@NonNull String key) { - HeadsUpEntry entry = mHeadsUpEntries.get(key); - return entry != null ? entry.entry : null; - } - - /** - * Returns the stream of all current Heads Up Notifications. - */ - @NonNull - public Stream<NotificationData.Entry> getAllEntries() { - return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry); - } - - /** - * Returns the top Heads Up Notification, which appeares to show at first. - */ - @Nullable - public NotificationData.Entry getTopEntry() { - HeadsUpEntry topEntry = getTopHeadsUpEntry(); - return (topEntry != null) ? topEntry.entry : null; + public NotificationData.Entry getEntry(String key) { + return mHeadsUpEntries.get(key).entry; } - /** - * Returns if any heads up notification is available or not. - */ - public boolean hasHeadsUpNotifications() { - return !mHeadsUpEntries.isEmpty(); + public Collection<HeadsUpEntry> getAllEntries() { + return mHeadsUpEntries.values(); } - protected HeadsUpEntry getTopHeadsUpEntry() { + public HeadsUpEntry getTopEntry() { if (mHeadsUpEntries.isEmpty()) { return null; } @@ -338,21 +387,56 @@ public class HeadsUpManager { } /** - * Sets the current user. + * Decides whether a click is invalid for a notification, i.e it has not been shown long enough + * that a user might have consciously clicked on it. + * + * @param key the key of the touched notification + * @return whether the touch is invalid and should be discarded */ + public boolean shouldSwallowClick(String key) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + if (entry != null && mClock.currentTimeMillis() < entry.postTime) { + return true; + } + return false; + } + + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { + if (mIsExpanded || mBar.isBouncerShowing()) { + // The touchable region is always the full area when expanded + return; + } + if (mHasPinnedNotification) { + ExpandableNotificationRow topEntry = getTopEntry().entry.row; + if (topEntry.isChildInGroup()) { + final ExpandableNotificationRow groupSummary + = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification()); + if (groupSummary != null) { + topEntry = groupSummary; + } + } + topEntry.getLocationOnScreen(mTmpTwoArray); + int minX = mTmpTwoArray[0]; + int maxX = mTmpTwoArray[0] + topEntry.getWidth(); + int maxY = topEntry.getIntrinsicHeight(); + + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(minX, 0, maxX, maxY); + } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); + } + } + public void setUser(int user) { mUser = user; } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("HeadsUpManager state:"); - dumpInternal(fd, pw, args); - } - - protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay); pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); - pw.print(" now="); pw.println(mClock.currentTimeMillis()); + pw.print(" now="); pw.println(SystemClock.elapsedRealtime()); pw.print(" mUser="); pw.println(mUser); for (HeadsUpEntry entry: mHeadsUpEntries.values()) { pw.print(" HeadsUpEntry="); pw.println(entry.entry); @@ -365,9 +449,6 @@ public class HeadsUpManager { } } - /** - * Returns if there are any pinned Heads Up Notifications or not. - */ public boolean hasPinnedHeadsUp() { return mHasPinnedNotification; } @@ -383,8 +464,14 @@ public class HeadsUpManager { } /** - * Unpins all pinned Heads Up Notifications. + * Notifies that a notification was swiped out and will be removed. + * + * @param key the notification key */ + public void addSwipedOutNotification(String key) { + mSwipedOutKeys.add(key); + } + public void unpinAll() { for (String key : mHeadsUpEntries.keySet()) { HeadsUpEntry entry = mHeadsUpEntries.get(key); @@ -394,13 +481,60 @@ public class HeadsUpManager { } } + public void onExpandingFinished() { + if (mReleaseOnExpandFinish) { + releaseAllImmediately(); + mReleaseOnExpandFinish = false; + } else { + for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { + if (isHeadsUp(entry.key)) { + // Maybe the heads-up was removed already + removeHeadsUpEntry(entry); + } + } + } + mEntriesToRemoveAfterExpand.clear(); + } + + public void setTrackingHeadsUp(boolean trackingHeadsUp) { + mTrackingHeadsUp = trackingHeadsUp; + } + + public boolean isTrackingHeadsUp() { + return mTrackingHeadsUp; + } + + public void setIsExpanded(boolean isExpanded) { + if (isExpanded != mIsExpanded) { + mIsExpanded = isExpanded; + if (isExpanded) { + // make sure our state is sane + mWaitingOnCollapseWhenGoingAway = false; + mHeadsUpGoingAway = false; + updateTouchableRegionListener(); + } + } + } + /** - * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as - * well. + * @return the height of the top heads up notification when pinned. This is different from the + * intrinsic height, which also includes whether the notification is system expanded and + * is mainly used when dragging down from a heads up notification. */ - public boolean isTrackingHeadsUp() { - // Might be implemented in subclass. - return false; + public int getTopHeadsUpPinnedHeight() { + HeadsUpEntry topEntry = getTopEntry(); + if (topEntry == null || topEntry.entry == null) { + return 0; + } + ExpandableNotificationRow row = topEntry.entry.row; + if (row.isChildInGroup()) { + final ExpandableNotificationRow groupSummary + = mGroupManager.getGroupSummary(row.getStatusBarNotification()); + if (groupSummary != null) { + row = groupSummary; + } + } + return row.getPinnedHeadsUpHeight(); } /** @@ -419,67 +553,147 @@ public class HeadsUpManager { } /** - * Sets an entry to be expanded and therefore stick in the heads up area if it's pinned - * until it's collapsed again. + * Set that we are exiting the headsUp pinned mode, but some notifications might still be + * animating out. This is used to keep the touchable regions in a sane state. */ + public void setHeadsUpGoingAway(boolean headsUpGoingAway) { + if (headsUpGoingAway != mHeadsUpGoingAway) { + mHeadsUpGoingAway = headsUpGoingAway; + if (!headsUpGoingAway) { + waitForStatusBarLayout(); + } + updateTouchableRegionListener(); + } + } + + /** + * We need to wait on the whole panel to collapse, before we can remove the touchable region + * listener. + */ + private void waitForStatusBarLayout() { + mWaitingOnCollapseWhenGoingAway = true; + mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, + int oldTop, int oldRight, int oldBottom) { + if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) { + mStatusBarWindowView.removeOnLayoutChangeListener(this); + mWaitingOnCollapseWhenGoingAway = false; + updateTouchableRegionListener(); + } + } + }); + } + + public static void setIsClickedNotification(View child, boolean clicked) { + child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null); + } + + public static boolean isClickedHeadsUpNotification(View child) { + Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION); + return clicked != null && clicked; + } + + public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) { + HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); + if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) { + headsUpEntry.remoteInputActive = remoteInputActive; + if (remoteInputActive) { + headsUpEntry.removeAutoRemovalCallbacks(); + } else { + headsUpEntry.updateEntry(false /* updatePostTime */); + } + } + } /** * Set an entry to be expanded and therefore stick in the heads up area if it's pinned * until it's collapsed again. */ - public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) { - HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); - if (headsUpEntry != null && entry.row.isPinned()) { - headsUpEntry.expanded(expanded); + public void setExpanded(NotificationData.Entry entry, boolean expanded) { + HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); + if (headsUpEntry != null && headsUpEntry.expanded != expanded && entry.row.isPinned()) { + headsUpEntry.expanded = expanded; + if (expanded) { + headsUpEntry.removeAutoRemovalCallbacks(); + } else { + headsUpEntry.updateEntry(false /* updatePostTime */); + } + } + } + + @Override + public void onReorderingAllowed() { + mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); + for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { + if (isHeadsUp(entry.key)) { + // Maybe the heads-up was removed already + removeHeadsUpEntry(entry); + } } + mEntriesToRemoveWhenReorderingAllowed.clear(); + mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true); } - @NonNull - protected HeadsUpEntry createHeadsUpEntry() { - return new HeadsUpEntry(); + public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) { + mVisualStabilityManager = visualStabilityManager; } - protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) { - // Do nothing for HeadsUpEntry. + public void setStatusBarState(int statusBarState) { + mStatusBarState = statusBarState; } /** * This represents a notification and how long it is in a heads up mode. It also manages its * lifecycle automatically when created. */ - protected class HeadsUpEntry implements Comparable<HeadsUpEntry> { - @Nullable public NotificationData.Entry entry; + public class HeadsUpEntry implements Comparable<HeadsUpEntry> { + public NotificationData.Entry entry; public long postTime; - public boolean remoteInputActive; public long earliestRemovaltime; - public boolean expanded; - private Runnable mRemoveHeadsUpRunnable; + public boolean remoteInputActive; + public boolean expanded; - public void setEntry(@Nullable final NotificationData.Entry entry) { - setEntry(entry, null); - } - - public void setEntry(@Nullable final NotificationData.Entry entry, - @Nullable Runnable removeHeadsUpRunnable) { + public void setEntry(final NotificationData.Entry entry) { this.entry = entry; - this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable; // The actual post time will be just after the heads-up really slided in postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay; - updateEntry(true /* updatePostTime */); + mRemoveHeadsUpRunnable = new Runnable() { + @Override + public void run() { + if (!mVisualStabilityManager.isReorderingAllowed()) { + mEntriesToRemoveWhenReorderingAllowed.add(entry); + mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this); + } else if (!mTrackingHeadsUp) { + removeHeadsUpEntry(entry); + } else { + mEntriesToRemoveAfterExpand.add(entry); + } + } + }; + updateEntry(); } - public void updateEntry(boolean updatePostTime) { - if (DEBUG) Log.v(TAG, "updateEntry"); + public void updateEntry() { + updateEntry(true); + } + public void updateEntry(boolean updatePostTime) { long currentTime = mClock.currentTimeMillis(); earliestRemovaltime = currentTime + mMinimumDisplayTime; if (updatePostTime) { postTime = Math.max(postTime, currentTime); } removeAutoRemovalCallbacks(); - + if (mEntriesToRemoveAfterExpand.contains(entry)) { + mEntriesToRemoveAfterExpand.remove(entry); + } + if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { + mEntriesToRemoveWhenReorderingAllowed.remove(entry); + } if (!isSticky()) { long finishTime = postTime + mHeadsUpNotificationDecay; long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime); @@ -493,7 +707,7 @@ public class HeadsUpManager { } @Override - public int compareTo(@NonNull HeadsUpEntry o) { + public int compareTo(HeadsUpEntry o) { boolean isPinned = entry.row.isPinned(); boolean otherPinned = o.entry.row.isPinned(); if (isPinned && !otherPinned) { @@ -520,29 +734,26 @@ public class HeadsUpManager { : -1; } - public void expanded(boolean expanded) { - this.expanded = expanded; + public void removeAutoRemovalCallbacks() { + mHandler.removeCallbacks(mRemoveHeadsUpRunnable); } - public void reset() { - entry = null; - expanded = false; - remoteInputActive = false; - removeAutoRemovalCallbacks(); - mRemoveHeadsUpRunnable = null; + public boolean wasShownLongEnough() { + return earliestRemovaltime < mClock.currentTimeMillis(); } - public void removeAutoRemovalCallbacks() { - if (mRemoveHeadsUpRunnable != null) - mHandler.removeCallbacks(mRemoveHeadsUpRunnable); + public void removeAsSoonAsPossible() { + removeAutoRemovalCallbacks(); + mHandler.postDelayed(mRemoveHeadsUpRunnable, + earliestRemovaltime - mClock.currentTimeMillis()); } - public void removeAsSoonAsPossible() { - if (mRemoveHeadsUpRunnable != null) { - removeAutoRemovalCallbacks(); - mHandler.postDelayed(mRemoveHeadsUpRunnable, - earliestRemovaltime - mClock.currentTimeMillis()); - } + public void reset() { + removeAutoRemovalCallbacks(); + entry = null; + mRemoveHeadsUpRunnable = null; + expanded = false; + remoteInputActive = false; } } @@ -551,4 +762,5 @@ public class HeadsUpManager { return SystemClock.elapsedRealtime(); } } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java deleted file mode 100644 index 1e3c123cfbc6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.policy; - -import android.view.View; - -import com.android.systemui.R; - -/** - * A class of utility static methods for heads up notifications. - */ -public final class HeadsUpUtil { - private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag; - - /** - * Set the given view as clicked or not-clicked. - * @param view The view to be set the flag to. - * @param clicked True to set as clicked. False to not-clicked. - */ - public static void setIsClickedHeadsUpNotification(View view, boolean clicked) { - view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null); - } - - /** - * Check if the given view has the flag of "clicked notification" - * @param view The view to be checked. - * @return True if the view has clicked. False othrewise. - */ - public static boolean isClickedHeadsUpNotification(View view) { - Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION); - return clicked != null && clicked; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java index d7a810eca02e..424858a86e58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -64,7 +64,7 @@ public class AmbientState { private boolean mPanelTracking; private boolean mExpansionChanging; private boolean mPanelFullWidth; - private boolean mPulsing; + private Collection<HeadsUpManager.HeadsUpEntry> mPulsing; private boolean mUnlockHintRunning; private boolean mQsCustomizerShowing; private int mIntrinsicPadding; @@ -315,18 +315,23 @@ public class AmbientState { } public boolean hasPulsingNotifications() { - return mPulsing; + return mPulsing != null; } - public void setPulsing(boolean hasPulsing) { + public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> hasPulsing) { mPulsing = hasPulsing; } public boolean isPulsing(NotificationData.Entry entry) { - if (!mPulsing || mHeadsUpManager == null) { + if (mPulsing == null) { return false; } - return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry)); + for (HeadsUpManager.HeadsUpEntry e : mPulsing) { + if (e.entry == entry) { + return true; + } + } + return false; } public boolean isPanelTracking() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 1b55a5b0325f..c114a6f5a6d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -92,11 +92,10 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.policy.HeadsUpUtil; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ScrollAdapter; import android.support.v4.graphics.ColorUtils; @@ -289,7 +288,7 @@ public class NotificationStackScrollLayout extends ViewGroup private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>(); private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations = new HashSet<>(); - private HeadsUpManagerPhone mHeadsUpManager; + private HeadsUpManager mHeadsUpManager; private boolean mTrackingHeadsUp; private ScrimController mScrimController; private boolean mForceNoOverlappingRendering; @@ -359,7 +358,7 @@ public class NotificationStackScrollLayout extends ViewGroup } }; private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); - private boolean mPulsing; + private Collection<HeadsUpManager.HeadsUpEntry> mPulsing; private boolean mDrawBackgroundAsSrc; private boolean mFadingOut; private boolean mParentNotFullyVisible; @@ -691,7 +690,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmHeightAndPadding() { - if (mPulsing) { + if (mPulsing != null) { mTopPadding = mClockBottom; } else { mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding; @@ -921,27 +920,6 @@ public class NotificationStackScrollLayout extends ViewGroup } /** - * @return the height of the top heads up notification when pinned. This is different from the - * intrinsic height, which also includes whether the notification is system expanded and - * is mainly used when dragging down from a heads up notification. - */ - private int getTopHeadsUpPinnedHeight() { - NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry(); - if (topEntry == null) { - return 0; - } - ExpandableNotificationRow row = topEntry.row; - if (row.isChildInGroup()) { - final ExpandableNotificationRow groupSummary - = mGroupManager.getGroupSummary(row.getStatusBarNotification()); - if (groupSummary != null) { - row = groupSummary; - } - } - return row.getPinnedHeadsUpHeight(); - } - - /** * @return the position from where the appear transition ends when expanding. * Measured in absolute height. */ @@ -952,7 +930,7 @@ public class NotificationStackScrollLayout extends ViewGroup int minNotificationsForShelf = 1; if (mTrackingHeadsUp || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) { - appearPosition = getTopHeadsUpPinnedHeight(); + appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight(); minNotificationsForShelf = 2; } else { appearPosition = 0; @@ -1220,9 +1198,9 @@ public class NotificationStackScrollLayout extends ViewGroup if (slidingChild instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; if (!mIsExpanded && row.isHeadsUp() && row.isPinned() - && mHeadsUpManager.getTopEntry().row != row + && mHeadsUpManager.getTopEntry().entry.row != row && mGroupManager.getGroupSummary( - mHeadsUpManager.getTopEntry().row.getStatusBarNotification()) + mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification()) != row) { continue; } @@ -2142,7 +2120,7 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public boolean hasPulsingNotifications() { - return mPulsing; + return mPulsing != null; } private void updateScrollability() { @@ -2775,7 +2753,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private boolean isClickedHeadsUp(View child) { - return HeadsUpUtil.isClickedHeadsUpNotification(child); + return HeadsUpManager.isClickedHeadsUpNotification(child); } /** @@ -4280,7 +4258,7 @@ public class NotificationStackScrollLayout extends ViewGroup mAnimationFinishedRunnables.add(runnable); } - public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { + public void setHeadsUpManager(HeadsUpManager headsUpManager) { mHeadsUpManager = headsUpManager; mAmbientState.setHeadsUpManager(headsUpManager); } @@ -4348,8 +4326,8 @@ public class NotificationStackScrollLayout extends ViewGroup return mIsExpanded; } - public void setPulsing(boolean pulsing, int clockBottom) { - if (!mPulsing && !pulsing) { + public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing, int clockBottom) { + if (mPulsing == null && pulsing == null) { return; } mPulsing = pulsing; @@ -4488,7 +4466,7 @@ public class NotificationStackScrollLayout extends ViewGroup pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s" + " alpha:%f scrollY:%d]", this.getClass().getSimpleName(), - mPulsing ? "T":"f", + mPulsing != null ?"T":"f", mAmbientState.isQsCustomizerShowing() ? "T":"f", getVisibility() == View.VISIBLE ? "visible" : getVisibility() == View.GONE ? "gone" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java index 04a7bd79c6ca..682b8493e913 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java @@ -30,7 +30,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; -import com.android.systemui.statusbar.policy.HeadsUpUtil; +import com.android.systemui.statusbar.policy.HeadsUpManager; /** * A state of a view. This can be used to apply a set of view properties to a view with @@ -582,7 +582,7 @@ public class ViewState { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - HeadsUpUtil.setIsClickedHeadsUpNotification(child, false); + HeadsUpManager.setIsClickedNotification(child, false); child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); child.setTag(TAG_START_TRANSLATION_Y, null); child.setTag(TAG_END_TRANSLATION_Y, null); diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java index f50a28766d70..4c69594a937b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java @@ -133,11 +133,11 @@ public class VolumeUiLayout extends FrameLayout { for (int i = 0; i < rowCount; i++) { View row = rows.getChildAt(i); if (to == ROTATION_SEASCAPE) { - rotateSeekBars(row, to, 180); - } else if (to == ROTATION_LANDSCAPE) { rotateSeekBars(row, to, 0); + } else if (to == ROTATION_LANDSCAPE) { + rotateSeekBars(row, to, 180); } else { - rotateSeekBars(row, to, 270); + rotateSeekBars(row, to, 90); } rotate(row, from, to, true); } @@ -309,12 +309,6 @@ public class VolumeUiLayout extends FrameLayout { return super.getOutlineProvider(); } - @Override - public void setPressed(boolean pressed) - { - // Ignore presses because it activates the seekbar thumb unnecessarily. - } - public void setOutsideTouchListener(OnClickListener onClickListener) { mHasOutsideTouch = true; requestLayout(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index f3c1171f650c..6e7477fbac38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -32,7 +32,6 @@ import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationInflaterTest; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -52,7 +51,7 @@ public class NotificationTestHelper { public NotificationTestHelper(Context context) { mContext = context; mInstrumentation = InstrumentationRegistry.getInstrumentation(); - mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null); + mHeadsUpManager = new HeadsUpManager(mContext, null, mGroupManager); } public ExpandableNotificationRow createRow() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java deleted file mode 100644 index 28f941779043..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone; - -import android.app.ActivityManager; -import android.app.Notification; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.view.View; -import android.service.notification.StatusBarNotification; -import android.support.test.filters.SmallTest; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; - -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.ExpandableNotificationRow; -import com.android.systemui.statusbar.NotificationData; -import com.android.systemui.statusbar.StatusBarIconView; -import com.android.systemui.statusbar.notification.VisualStabilityManager; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.assertFalse; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class HeadsUpManagerPhoneTest extends SysuiTestCase { - @Rule public MockitoRule rule = MockitoJUnit.rule(); - - private static final String TEST_PACKAGE_NAME = "test"; - private static final int TEST_UID = 0; - - private HeadsUpManagerPhone mHeadsUpManager; - - private NotificationData.Entry mEntry; - private StatusBarNotification mSbn; - - private final Handler mHandler = new Handler(Looper.getMainLooper()); - - @Mock private NotificationGroupManager mGroupManager; - @Mock private View mStatusBarWindowView; - @Mock private StatusBar mBar; - @Mock private ExpandableNotificationRow mRow; - @Mock private VisualStabilityManager mVSManager; - - @Before - public void setUp() { - when(mVSManager.isReorderingAllowed()).thenReturn(true); - - mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarWindowView, mGroupManager, mBar, mVSManager); - - Notification.Builder n = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setContentTitle("Title") - .setContentText("Text"); - mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, - 0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0); - - mEntry = new NotificationData.Entry(mSbn); - mEntry.row = mRow; - mEntry.expandedIcon = mock(StatusBarIconView.class); - } - - @Test - public void testBasicOperations() { - // Check the initial state. - assertNull(mHeadsUpManager.getEntry(mEntry.key)); - assertNull(mHeadsUpManager.getTopEntry()); - assertEquals(0, mHeadsUpManager.getAllEntries().count()); - assertFalse(mHeadsUpManager.hasHeadsUpNotifications()); - - // Add a notification. - mHeadsUpManager.showNotification(mEntry); - - assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); - assertEquals(mEntry, mHeadsUpManager.getTopEntry()); - assertEquals(1, mHeadsUpManager.getAllEntries().count()); - assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); - - // Update the notification. - mHeadsUpManager.updateNotification(mEntry, false); - - assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); - assertEquals(mEntry, mHeadsUpManager.getTopEntry()); - assertEquals(1, mHeadsUpManager.getAllEntries().count()); - assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); - - // Remove but defer, since the notification is visible on display. - mHeadsUpManager.removeNotification(mEntry.key, false); - - assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); - assertEquals(mEntry, mHeadsUpManager.getTopEntry()); - assertEquals(1, mHeadsUpManager.getAllEntries().count()); - assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 31442af5a04c..bdf9b1f6da9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -86,8 +86,8 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; @@ -110,7 +110,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private UnlockMethodCache mUnlockMethodCache; @Mock private KeyguardIndicationController mKeyguardIndicationController; @Mock private NotificationStackScrollLayout mStackScroller; - @Mock private HeadsUpManagerPhone mHeadsUpManager; + @Mock private HeadsUpManager mHeadsUpManager; @Mock private SystemServicesProxy mSystemServicesProxy; @Mock private NotificationPanelView mNotificationPanelView; @Mock private IStatusBarService mBarService; @@ -588,7 +588,7 @@ public class StatusBarTest extends SysuiTestCase { static class TestableStatusBar extends StatusBar { public TestableStatusBar(StatusBarKeyguardViewManager man, UnlockMethodCache unlock, KeyguardIndicationController key, - NotificationStackScrollLayout stack, HeadsUpManagerPhone hum, + NotificationStackScrollLayout stack, HeadsUpManager hum, PowerManager pm, NotificationPanelView panelView, IStatusBarService barService, NotificationListener notificationListener, NotificationLogger notificationLogger, @@ -650,7 +650,7 @@ public class StatusBarTest extends SysuiTestCase { public void setUpForTest(NotificationPresenter presenter, NotificationListContainer listContainer, Callback callback, - HeadsUpManagerPhone headsUpManager, + HeadsUpManager headsUpManager, NotificationData notificationData) { super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager); mNotificationData = notificationData; diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java index c18ed732f244..f7bb0655b46e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java @@ -44,11 +44,13 @@ import com.android.systemui.statusbar.policy.BluetoothController; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +@Ignore @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -87,7 +89,7 @@ public class OutputChooserDialogTest extends SysuiTestCase { public void tearDown() throws Exception { TestableLooper.get(this).processAllMessages(); } - +/* @Test public void testClickMediaRouterItemConnectsMedia() { mDialog.show(); @@ -137,7 +139,7 @@ public class OutputChooserDialogTest extends SysuiTestCase { .getText().toString().contains("Phone")); mDialog.dismiss(); } - +*/ @Test public void testNoMediaScanIfInCall() { mDialog.setIsInCall(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 4888fb284a35..43d60e41a6b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -48,6 +48,7 @@ import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -55,6 +56,7 @@ import org.mockito.MockitoAnnotations; import java.util.function.Predicate; +@Ignore @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -111,7 +113,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { + " failed test", condition.test(view)); } } - +/* @Test public void testContentDescriptions() { mDialog.show(SHOW_REASON_UNKNOWN); @@ -218,4 +220,5 @@ public class VolumeDialogImplTest extends SysuiTestCase { verify(mController, times(1)).setRingerMode(RINGER_MODE_NORMAL, false); verify(mController, times(1)).setStreamVolume(STREAM_RING, 0); } + */ } diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java deleted file mode 100644 index 4ab2063196d4..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java +++ /dev/null @@ -1,220 +0,0 @@ -/** - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.net.Uri; -import android.provider.Settings; -import android.service.notification.Condition; -import android.service.notification.ZenModeConfig; -import android.support.test.annotation.UiThreadTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.test.FlakyTest; -import android.view.LayoutInflater; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.policy.ZenModeController; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -@Ignore -@SmallTest -@RunWith(AndroidJUnit4.class) -public class ZenModePanelTest extends SysuiTestCase { - - ZenModePanel mPanel; - ZenModeController mController; - Uri mForeverId; - - @Before - public void setup() throws Exception { - final LayoutInflater layoutInflater = LayoutInflater.from(mContext); - mPanel = (ZenModePanel) layoutInflater.inflate(com.android.systemui.R.layout.zen_mode_panel, - null); - mController = mock(ZenModeController.class); - mForeverId = Condition.newId(mContext).appendPath("forever").build(); - - mPanel.init(mController); - } - - private void assertProperConditionTagTypes(boolean hasAlarm) { - final int N = mPanel.getVisibleConditions(); - assertEquals(hasAlarm ? 3 : 2, N); - - assertEquals(mForeverId, mPanel.getConditionTagAt(0).condition.id); - assertTrue(ZenModeConfig.isValidCountdownConditionId( - mPanel.getConditionTagAt(1).condition.id)); - assertFalse(ZenModeConfig.isValidCountdownToAlarmConditionId( - mPanel.getConditionTagAt(1).condition.id)); - if (hasAlarm) { - assertTrue(ZenModeConfig.isValidCountdownToAlarmConditionId( - mPanel.getConditionTagAt(2).condition.id)); - } - } - - @Test - public void testHandleUpdateConditions_foreverSelected_alarmExists() { - Condition forever = new Condition(mForeverId, "", Condition.STATE_TRUE); - - when(mController.getNextAlarm()).thenReturn(System.currentTimeMillis() + 1000); - - mPanel.handleUpdateConditions(forever); - assertProperConditionTagTypes(true); - assertTrue(mPanel.getConditionTagAt(0).rb.isChecked()); - } - - @Test - public void testHandleUpdateConditions_foreverSelected_noAlarm() { - Uri foreverId = Condition.newId(mContext).appendPath("forever").build(); - Condition forever = new Condition(foreverId, "", Condition.STATE_TRUE); - - when(mController.getNextAlarm()).thenReturn((long) 0); - - mPanel.handleUpdateConditions(forever); - assertProperConditionTagTypes(false); - assertEquals(foreverId, mPanel.getConditionTagAt(0).condition.id); - } - - @Test - public void testHandleUpdateConditions_countdownSelected_alarmExists() { - Uri foreverId = Condition.newId(mContext).appendPath("forever").build(); - - Condition countdown = new Condition(ZenModeConfig.toCountdownConditionId( - System.currentTimeMillis() + (3 * 60 * 60 * 1000) + 4000, false), - "", Condition.STATE_TRUE); - - when(mController.getNextAlarm()).thenReturn(System.currentTimeMillis() + 1000); - - mPanel.handleUpdateConditions(countdown); - assertProperConditionTagTypes(true); - assertTrue(mPanel.getConditionTagAt(1).rb.isChecked()); - } - - @Test - public void testHandleUpdateConditions_countdownSelected_noAlarm() { - Uri foreverId = Condition.newId(mContext).appendPath("forever").build(); - - Condition countdown = new Condition(ZenModeConfig.toCountdownConditionId( - System.currentTimeMillis() + (3 * 60 * 60 * 1000) + 4000, false), - "", Condition.STATE_TRUE); - - when(mController.getNextAlarm()).thenReturn((long) 0); - - mPanel.handleUpdateConditions(countdown); - assertProperConditionTagTypes(false); - assertTrue(mPanel.getConditionTagAt(1).rb.isChecked()); - } - - @Test - public void testHandleUpdateConditions_nextAlarmSelected() { - Uri foreverId = Condition.newId(mContext).appendPath("forever").build(); - - Condition alarm = new Condition(ZenModeConfig.toCountdownConditionId( - System.currentTimeMillis() + 1000, true), - "", Condition.STATE_TRUE); - when(mController.getNextAlarm()).thenReturn(System.currentTimeMillis() + 9000); - - mPanel.handleUpdateConditions(alarm); - - assertProperConditionTagTypes(true); - assertEquals(alarm, mPanel.getConditionTagAt(2).condition); - assertTrue(mPanel.getConditionTagAt(2).rb.isChecked()); - } - - @Test - public void testHandleUpdateConditions_foreverSelected_alarmConditionDoesNotChangeIfAttached() { - Uri foreverId = Condition.newId(mContext).appendPath("forever").build(); - Condition forever = new Condition(foreverId, "", Condition.STATE_TRUE); - - Condition alarm = new Condition(ZenModeConfig.toCountdownConditionId( - System.currentTimeMillis() + 9000, true), - "", Condition.STATE_TRUE); - when(mController.getNextAlarm()).thenReturn(System.currentTimeMillis() + 1000); - - mPanel.handleUpdateConditions(alarm); - mPanel.setAttached(true); - mPanel.handleUpdateConditions(forever); - - assertProperConditionTagTypes(true); - assertEquals(alarm, mPanel.getConditionTagAt(2).condition); - assertTrue(mPanel.getConditionTagAt(0).rb.isChecked()); - } - - @Test - public void testHandleUpdateConditions_foreverSelected_timeConditionDoesNotChangeIfAttached() { - Uri foreverId = Condition.newId(mContext).appendPath("forever").build(); - Condition forever = new Condition(foreverId, "", Condition.STATE_TRUE); - - Condition countdown = new Condition(ZenModeConfig.toCountdownConditionId( - System.currentTimeMillis() + (3 * 60 * 60 * 1000) + 4000, false), - "", Condition.STATE_TRUE); - when(mController.getNextAlarm()).thenReturn((long) 0); - - mPanel.handleUpdateConditions(countdown); - mPanel.setAttached(true); - mPanel.handleUpdateConditions(forever); - - assertProperConditionTagTypes(false); - assertEquals(countdown, mPanel.getConditionTagAt(1).condition); - assertTrue(mPanel.getConditionTagAt(0).rb.isChecked()); - } - - @Test - @UiThreadTest - public void testHandleUpdateManualRule_stillSelectedAfterModeChange() { - ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); - - Condition alarm = new Condition(ZenModeConfig.toCountdownConditionId( - System.currentTimeMillis() + 1000, true), - "", Condition.STATE_TRUE); - - rule.condition = alarm; - rule.conditionId = alarm.id; - rule.enabled = true; - rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; - - mPanel.handleUpdateManualRule(rule); - - assertProperConditionTagTypes(true); - assertEquals(alarm, mPanel.getConditionTagAt(2).condition); - assertTrue(mPanel.getConditionTagAt(2).rb.isChecked()); - - assertEquals(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, - mPanel.getSelectedZen(Settings.Global.ZEN_MODE_OFF)); - - rule.zenMode = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS; - - mPanel.handleUpdateManualRule(rule); - - assertProperConditionTagTypes(true); - assertEquals(alarm, mPanel.getConditionTagAt(2).condition); - assertTrue(mPanel.getConditionTagAt(2).rb.isChecked()); - - assertEquals(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, - mPanel.getSelectedZen(Settings.Global.ZEN_MODE_OFF)); - } -} diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto index f6a54af89196..c77dcc01b7a1 100644 --- a/proto/src/wifi.proto +++ b/proto/src/wifi.proto @@ -63,7 +63,7 @@ message WifiLog { // Number scans that returned at least one result. optional int32 num_non_empty_scan_results = 13; - // Number of scans that were one time. + // Number of single scans requests. optional int32 num_oneshot_scans = 14; // Number of repeated background scans that were scheduled to the chip. @@ -376,6 +376,9 @@ message WifiLog { // Wifi power statistics optional WifiPowerStats wifi_power_stats = 92; + + // Number of connectivity single scan requests. + optional int32 num_connectivity_oneshot_scans = 93; } // Information that gets logged for every WiFi connection. @@ -1155,4 +1158,4 @@ message WifiPowerStats { // Amount of time wifi is in tx (ms) optional int64 tx_time_ms = 5; -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 219facd0b002..0502117cd73a 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -34,7 +34,7 @@ option java_package com.android.server 2731 power_soft_sleep_requested (savedwaketimems|2) # Power save state has changed. See BatterySaverController.java for the details. 2739 battery_saver_mode (prevOffOrOn|1|5),(nowOffOrOn|1|5),(interactive|1|5),(features|3|5) -27390 battery_saving_stats (batterySaver|1|5),(interactive|1|5),(doze|1|5),(delta_duration|2|3),(delta_battery_drain|1|6),(total_duration|2|3),(total_battery_drain|1|6) +27390 battery_saving_stats (batterySaver|1|5),(interactive|1|5),(doze|1|5),(delta_duration|2|3),(delta_battery_drain|1|1),(delta_battery_drain_percent|1|6),(total_duration|2|3),(total_battery_drain|1|1),(total_battery_drain_percent|1|6) # # Leave IDs through 2740 for more power logs (2730 used by battery_discharge above) diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 1dd92f3130a7..65e90bad41a7 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -1397,23 +1397,6 @@ public class LocationManagerService extends ILocationManager.Stub { } /** - * Returns "true" if access to the specified location provider is allowed by the specified - * user's settings. Access to all location providers is forbidden to non-location-provider - * processes belonging to background users. - * - * @param provider the name of the location provider - * @param uid the requestor's UID - * @param userId the user id to query - */ - private boolean isAllowedByUserSettingsLockedForUser( - String provider, int uid, int userId) { - if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) { - return false; - } - return isLocationProviderEnabledForUser(provider, userId); - } - - /** * Returns the permission string associated with the specified resolution level. * * @param resolutionLevel the resolution level @@ -2585,143 +2568,6 @@ public class LocationManagerService extends ILocationManager.Stub { } /** - * Method for enabling or disabling location. - * - * @param enabled true to enable location. false to disable location - * @param userId the user id to set - */ - @Override - public void setLocationEnabledForUser(boolean enabled, int userId) { - // Check INTERACT_ACROSS_USERS permission if userId is not current user id. - checkInteractAcrossUsersPermission(userId); - - // Enable or disable all location providers. Fused provider and passive provider are - // excluded. - synchronized (mLock) { - for(String provider : getAllProvidersForLocationSettings()) { - setProviderEnabledForUser(provider, enabled, userId); - } - } - } - - /** - * Returns the current enabled/disabled status of location - * - * @param userId the user id to query - * @return true if location is enabled. false if location is disabled. - */ - @Override - public boolean isLocationEnabledForUser(int userId) { - // Check INTERACT_ACROSS_USERS permission if userId is not current user id. - checkInteractAcrossUsersPermission(userId); - - // If at least one location provider is enabled, return true. Fused provider and passive - // provider are excluded. - synchronized (mLock) { - for (String provider : getAllProvidersForLocationSettings()) { - if (isProviderEnabledForUser(provider, userId)) { - return true; - } - } - return false; - } - } - - @Override - public boolean isProviderEnabled(String provider) { - return isProviderEnabledForUser(provider, UserHandle.getCallingUserId()); - } - - /** - * Method for determining if a location provider is enabled. - * - * @param provider the location provider to query - * @param userId the user id to query - * @return true if the provider is enabled - */ - @Override - public boolean isProviderEnabledForUser(String provider, int userId) { - // Check INTERACT_ACROSS_USERS permission if userId is not current user id. - checkInteractAcrossUsersPermission(userId); - - // Fused provider is accessed indirectly via criteria rather than the provider-based APIs, - // so we discourage its use - if (LocationManager.FUSED_PROVIDER.equals(provider)) return false; - - int uid = Binder.getCallingUid(); - long identity = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - LocationProviderInterface p = mProvidersByName.get(provider); - return p != null - && isAllowedByUserSettingsLockedForUser(provider, uid, userId); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Method for enabling or disabling a single location provider. - * - * @param provider the name of the provider - * @param enabled true to enable the provider. false to disable the provider - * @param userId the user id to set - * @return true if the value was set successfully. false on failure. - */ - @Override - public boolean setProviderEnabledForUser( - String provider, boolean enabled, int userId) { - mContext.enforceCallingPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS, - "Requires WRITE_SECURE_SETTINGS permission"); - - // Check INTERACT_ACROSS_USERS permission if userId is not current user id. - checkInteractAcrossUsersPermission(userId); - - final long identity = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - // to ensure thread safety, we write the provider name with a '+' or '-' - // and let the SettingsProvider handle it rather than reading and modifying - // the list of enabled providers. - if (enabled) { - provider = "+" + provider; - } else { - provider = "-" + provider; - } - return Settings.Secure.putStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - provider, - userId); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Return all location providers except fused provider and passive provider. These two - * providers are not generating location by themselves, but only echo locations from other - * providers. - * - * @return All location providers except fused provider and passive provider, including - * providers that are not permitted to be accessed by the calling activity or are - * currently disabled. - */ - private List<String> getAllProvidersForLocationSettings() { - List<String> providersForSettings = new ArrayList<>(mProviders.size()); - for (String provider : getAllProviders()) { - if (provider.equals(LocationManager.PASSIVE_PROVIDER)) { - continue; - } - providersForSettings.add(provider); - } - return providersForSettings; - } - - /** * Read location provider status from Settings.Secure * * @param provider the location provider to query @@ -2742,23 +2588,6 @@ public class LocationManagerService extends ILocationManager.Stub { } /** - * Method for checking INTERACT_ACROSS_USERS permission if specified user id is not the same as - * current user id - * - * @param userId the user id to get or set value - */ - private void checkInteractAcrossUsersPermission(int userId) { - int uid = Binder.getCallingUid(); - if (UserHandle.getUserId(uid) != userId) { - if (ActivityManager.checkComponentPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS, uid, -1, true) - != PERMISSION_GRANTED) { - throw new SecurityException("Requires INTERACT_ACROSS_USERS permission"); - } - } - } - - /** * Returns "true" if the UID belongs to a bound location provider. * * @param uid the uid diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 6747be340d46..6743484b91c4 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -35,6 +35,7 @@ import android.os.UserHandle; import android.telephony.CellInfo; import android.telephony.CellLocation; import android.telephony.DisconnectCause; +import android.telephony.LocationAccessPolicy; import android.telephony.PhoneStateListener; import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; @@ -93,7 +94,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { IPhoneStateListener callback; IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback; - int callerUserId; + int callerUid; + int callerPid; int events; @@ -117,7 +119,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { + " callback=" + callback + " onSubscriptionsChangedListenererCallback=" + onSubscriptionsChangedListenerCallback - + " callerUserId=" + callerUserId + " subId=" + subId + " phoneId=" + phoneId + + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId + " events=" + Integer.toHexString(events) + " canReadPhoneState=" + canReadPhoneState + "}"; } @@ -356,6 +358,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { public void addOnSubscriptionsChangedListener(String callingPackage, IOnSubscriptionsChangedListener callback) { int callerUserId = UserHandle.getCallingUserId(); + mContext.getSystemService(AppOpsManager.class) + .checkPackage(Binder.getCallingUid(), callingPackage); if (VDBG) { log("listen oscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId + " callback=" + callback @@ -399,7 +403,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { r.onSubscriptionsChangedListenerCallback = callback; r.callingPackage = callingPackage; - r.callerUserId = callerUserId; + r.callerUid = Binder.getCallingUid(); + r.callerPid = Binder.getCallingPid(); r.events = 0; r.canReadPhoneState = true; // permission has been enforced above if (DBG) { @@ -470,6 +475,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private void listen(String callingPackage, IPhoneStateListener callback, int events, boolean notifyNow, int subId) { int callerUserId = UserHandle.getCallingUserId(); + mContext.getSystemService(AppOpsManager.class) + .checkPackage(Binder.getCallingUid(), callingPackage); if (VDBG) { log("listen: E pkg=" + callingPackage + " events=0x" + Integer.toHexString(events) + " notifyNow=" + notifyNow + " subId=" + subId + " myUserId=" @@ -514,7 +521,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { r.callback = callback; r.callingPackage = callingPackage; - r.callerUserId = callerUserId; + r.callerUid = Binder.getCallingUid(); + r.callerPid = Binder.getCallingPid(); boolean isPhoneStateEvent = (events & (CHECK_PHONE_STATE_PERMISSION_MASK | ENFORCE_PHONE_STATE_PERMISSION_MASK)) != 0; r.canReadPhoneState = isPhoneStateEvent && canReadPhoneState(callingPackage); @@ -572,8 +580,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("listen: mCellLocation = " + mCellLocation[phoneId]); - r.callback.onCellLocationChanged( - new Bundle(mCellLocation[phoneId])); + if (checkLocationAccess(r)) { + r.callback.onCellLocationChanged( + new Bundle(mCellLocation[phoneId])); + } } catch (RemoteException ex) { remove(r.binder); } @@ -619,7 +629,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = " + mCellInfo.get(phoneId)); - r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); + if (checkLocationAccess(r)) { + r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); + } } catch (RemoteException ex) { remove(r.binder); } @@ -979,7 +991,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellInfo.set(phoneId, cellInfo); for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) && - idMatch(r.subId, subId, phoneId)) { + idMatch(r.subId, subId, phoneId) && + checkLocationAccess(r)) { try { if (DBG_LOC) { log("notifyCellInfo: mCellInfo=" + cellInfo + " r=" + r); @@ -1262,7 +1275,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellLocation[phoneId] = cellLocation; for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) && - idMatch(r.subId, subId, phoneId)) { + idMatch(r.subId, subId, phoneId) && + checkLocationAccess(r)) { try { if (DBG_LOC) { log("notifyCellLocation: cellLocation=" + cellLocation @@ -1706,10 +1720,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { boolean valid = false; try { foregroundUser = ActivityManager.getCurrentUser(); - valid = r.callerUserId == foregroundUser && r.matchPhoneStateListenerEvent(events); + valid = UserHandle.getUserId(r.callerUid) == foregroundUser + && r.matchPhoneStateListenerEvent(events); if (DBG | DBG_LOC) { log("validateEventsAndUserLocked: valid=" + valid - + " r.callerUserId=" + r.callerUserId + " foregroundUser=" + foregroundUser + + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser + " r.events=" + r.events + " events=" + events); } } finally { @@ -1741,6 +1756,16 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + private boolean checkLocationAccess(Record r) { + long token = Binder.clearCallingIdentity(); + try { + return LocationAccessPolicy.canAccessCellLocation(mContext, + r.callingPackage, r.callerUid, r.callerPid); + } finally { + Binder.restoreCallingIdentity(token); + } + } + private void checkPossibleMissNotify(Record r, int phoneId) { int events = r.events; @@ -1788,7 +1813,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = " + mCellInfo.get(phoneId)); } - r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); + if (checkLocationAccess(r)) { + r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); + } } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -1836,7 +1863,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("checkPossibleMissNotify: onCellLocationChanged mCellLocation = " + mCellLocation[phoneId]); - r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId])); + if (checkLocationAccess(r)) { + r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId])); + } } catch (RemoteException ex) { mRemoveList.add(r.binder); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5b8b6912cd19..8d1632a3cef6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -802,6 +802,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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); } @@ -4027,9 +4028,13 @@ public class ActivityManagerService extends IActivityManager.Stub runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; } String genDebugInfoProperty = SystemProperties.get("debug.generate-debug-info"); - if ("true".equals(genDebugInfoProperty)) { + if ("1".equals(genDebugInfoProperty) || "true".equals(genDebugInfoProperty)) { runtimeFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; } + String genMiniDebugInfoProperty = SystemProperties.get("dalvik.vm.minidebuginfo"); + if ("1".equals(genMiniDebugInfoProperty) || "true".equals(genMiniDebugInfoProperty)) { + runtimeFlags |= Zygote.DEBUG_GENERATE_MINI_DEBUG_INFO; + } if ("1".equals(SystemProperties.get("debug.jni.logging"))) { runtimeFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING; } @@ -4331,7 +4336,9 @@ public class ActivityManagerService extends IActivityManager.Stub final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); StatsLog.write(StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED, component.userId, component.realActivity.getPackageName(), - component.realActivity.getShortClassName(), resumed ? 1 : 0); + component.realActivity.getShortClassName(), resumed ? + StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__ACTIVITY__MOVE_TO_FOREGROUND : + StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__ACTIVITY__MOVE_TO_BACKGROUND); if (resumed) { if (mUsageStatsService != null) { mUsageStatsService.reportEvent(component.realActivity, component.userId, @@ -12844,7 +12851,8 @@ public class ActivityManagerService extends IActivityManager.Stub } // TODO: Where should the corresponding '1' (start) write go? - StatsLog.write(StatsLog.DEVICE_ON_STATUS_CHANGED, 0); + StatsLog.write(StatsLog.DEVICE_ON_STATUS_CHANGED, + StatsLog.DEVICE_ON_STATUS_CHANGED__STATE__OFF); boolean timedout = false; diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index f0c90e0f1fab..24a77c7693d0 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -115,6 +115,7 @@ final class ActivityManagerShellCommand extends ShellCommand { private int mActivityType; private int mTaskId; private boolean mIsTaskOverlay; + private boolean mIsLockTask; final boolean mDumping; @@ -278,6 +279,7 @@ final class ActivityManagerShellCommand extends ShellCommand { mActivityType = ACTIVITY_TYPE_UNDEFINED; mTaskId = INVALID_TASK_ID; mIsTaskOverlay = false; + mIsLockTask = false; return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() { @Override @@ -334,6 +336,8 @@ final class ActivityManagerShellCommand extends ShellCommand { mTaskId = Integer.parseInt(getNextArgRequired()); } else if (opt.equals("--task-overlay")) { mIsTaskOverlay = true; + } else if (opt.equals("--lock-task")) { + mIsLockTask = true; } else { return false; } @@ -429,13 +433,22 @@ final class ActivityManagerShellCommand extends ShellCommand { options.setLaunchActivityType(mActivityType); } if (mTaskId != INVALID_TASK_ID) { - options = ActivityOptions.makeBasic(); + if (options == null) { + options = ActivityOptions.makeBasic(); + } options.setLaunchTaskId(mTaskId); if (mIsTaskOverlay) { options.setTaskOverlay(true, true /* canResume */); } } + android.util.Log.d("bfranz", "I was here: " + mIsLockTask); + if (mIsLockTask) { + if (options == null) { + options = ActivityOptions.makeBasic(); + } + options.setLockTaskMode(true); + } if (mWaitOption) { result = mInterface.startActivityAndWait(null, null, intent, mimeType, null, null, 0, mStartFlags, profilerInfo, diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 0d96468761be..ea52782027ba 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -325,39 +325,38 @@ public final class BatteryStatsService extends IBatteryStats.Stub void noteProcessStart(String name, int uid) { synchronized (mStats) { mStats.noteProcessStartLocked(name, uid); - // TODO: decide where this should be and use a constant instead of a literal. - StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 1); + StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, + StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED__EVENT__PROCESS_STARTED); } } void noteProcessCrash(String name, int uid) { synchronized (mStats) { mStats.noteProcessCrashLocked(name, uid); - // TODO: decide where this should be and use a constant instead of a literal. - StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 2); + StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, + StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED__EVENT__PROCESS_CRASHED); } } void noteProcessAnr(String name, int uid) { synchronized (mStats) { mStats.noteProcessAnrLocked(name, uid); - // TODO: decide where this should be and use a constant instead of a literal. - StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 3); + StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, + StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED__EVENT__PROCESS_ANRED); } } void noteProcessFinish(String name, int uid) { synchronized (mStats) { mStats.noteProcessFinishLocked(name, uid); - // TODO: decide where this should be and use a constant instead of a literal. - StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 0); + StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, + StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED__EVENT__PROCESS_FINISHED); } } /** @param state Process state from ActivityManager.java. */ void noteUidProcessState(int uid, int state) { synchronized (mStats) { - // TODO: remove this once we figure out properly where and how StatsLog.write(StatsLog.UID_PROCESS_STATE_CHANGED, uid, ActivityManager.processStateAmToProto(state)); @@ -603,7 +602,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub enforceCallingPermission(); if (DBG) Slog.d(TAG, "begin noteScreenState"); synchronized (mStats) { - // TODO: remove this once we figure out properly where and how StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, state); mStats.noteScreenStateLocked(state); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 6816fd13d669..b27f1ec3c653 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -479,6 +479,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mScreenBrightnessForVr = getScreenBrightnessForVrSetting(); mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); mTemporaryScreenBrightness = -1; + mPendingScreenBrightnessSetting = -1; mTemporaryAutoBrightnessAdjustment = Float.NaN; } @@ -1476,6 +1477,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mPendingScreenBrightnessSetting = -1; return false; } + mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting; mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting; mPendingScreenBrightnessSetting = -1; return true; @@ -1484,7 +1486,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void notifyBrightnessChanged(int brightness, boolean userInitiated, boolean hadUserDataPoint) { final float brightnessInNits = convertToNits(brightness); - if (brightnessInNits >= 0.0f && mAutomaticBrightnessController != null) { + if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f + && mAutomaticBrightnessController != null) { // We only want to track changes on devices that can actually map the display backlight // values into a physical brightness unit since the value provided by the API is in // nits and not using the arbitrary backlight units. diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index e0baeee01c69..401c05e80307 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -862,7 +862,8 @@ public final class JobSchedulerService extends com.android.server.SystemService } startTrackingJobLocked(jobStatus, toCancel); StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED, - uId, null, jobStatus.getBatteryName(), 2); + uId, null, jobStatus.getBatteryName(), + StatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__SCHEDULED); // If the job is immediately ready to run, then we can just immediately // put it in the pending list and try to schedule it. This is especially diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 28fa86b3a893..2cb8c48a3850 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -383,8 +383,8 @@ public class LockSettingsService extends ILockSettings.Stub { return KeyStore.getInstance(); } - public RecoverableKeyStoreManager getRecoverableKeyStoreManager() { - return RecoverableKeyStoreManager.getInstance(mContext); + public RecoverableKeyStoreManager getRecoverableKeyStoreManager(KeyStore keyStore) { + return RecoverableKeyStoreManager.getInstance(mContext, keyStore); } public IStorageManager getStorageManager() { @@ -413,7 +413,7 @@ public class LockSettingsService extends ILockSettings.Stub { mInjector = injector; mContext = injector.getContext(); mKeyStore = injector.getKeyStore(); - mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager(); + mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager(mKeyStore); mHandler = injector.getHandler(); mStrongAuth = injector.getStrongAuth(); mActivityManager = injector.getActivityManager(); @@ -2064,6 +2064,16 @@ public class LockSettingsService extends ILockSettings.Stub { return mRecoverableKeyStoreManager.generateAndStoreKey(alias); } + @Override + public String generateKey(@NonNull String alias, byte[] account) throws RemoteException { + return mRecoverableKeyStoreManager.generateKey(alias, account); + } + + @Override + public String getKey(@NonNull String alias) throws RemoteException { + return mRecoverableKeyStoreManager.getKey(alias); + } + private static final String[] VALID_SETTINGS = new String[] { LockPatternUtils.LOCKOUT_PERMANENT_KEY, LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE, diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java index 59132da7d4c7..285e722886c2 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java @@ -16,10 +16,13 @@ package com.android.server.locksettings.recoverablekeystore; +import java.io.IOException; +import java.security.cert.CertificateException; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; /** @@ -27,6 +30,7 @@ import java.security.UnrecoverableKeyException; */ public class KeyStoreProxyImpl implements KeyStoreProxy { + private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; private final KeyStore mKeyStore; /** @@ -57,4 +61,21 @@ public class KeyStoreProxyImpl implements KeyStoreProxy { public void deleteEntry(String alias) throws KeyStoreException { mKeyStore.deleteEntry(alias); } + + /** + * Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked + * {@link KeyStore#load(KeyStore.LoadStoreParameter)}. + * + * @throws KeyStoreException if there was a problem getting or initializing the key store. + */ + public static KeyStore getAndLoadAndroidKeyStore() throws KeyStoreException { + KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER); + try { + keyStore.load(/*param=*/ null); + } catch (CertificateException | IOException | NoSuchAlgorithmException e) { + // Should never happen. + throw new KeyStoreException("Unable to load keystore.", e); + } + return keyStore; + } } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index ec72b221931f..fda6cdf33e0c 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -37,21 +37,23 @@ import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; import android.security.keystore.recovery.RecoveryController; import android.security.keystore.recovery.WrappedApplicationKey; +import android.security.KeyStore; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; +import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; import java.security.InvalidKeyException; -import java.security.KeyStoreException; import java.security.KeyFactory; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; -import java.security.UnrecoverableKeyException; import java.security.spec.InvalidKeySpecException; +import java.security.UnrecoverableKeyException; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.HashMap; @@ -82,18 +84,23 @@ public class RecoverableKeyStoreManager { private final RecoverableKeyGenerator mRecoverableKeyGenerator; private final RecoverySnapshotStorage mSnapshotStorage; private final PlatformKeyManager mPlatformKeyManager; + private final KeyStore mKeyStore; + private final ApplicationKeyStorage mApplicationKeyStorage; /** * Returns a new or existing instance. * * @hide */ - public static synchronized RecoverableKeyStoreManager getInstance(Context context) { + public static synchronized RecoverableKeyStoreManager + getInstance(Context context, KeyStore keystore) { if (mInstance == null) { RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context); PlatformKeyManager platformKeyManager; + ApplicationKeyStorage applicationKeyStorage; try { platformKeyManager = PlatformKeyManager.getInstance(context, db); + applicationKeyStorage = ApplicationKeyStorage.getInstance(keystore); } catch (NoSuchAlgorithmException e) { // Impossible: all algorithms must be supported by AOSP throw new RuntimeException(e); @@ -103,12 +110,14 @@ public class RecoverableKeyStoreManager { mInstance = new RecoverableKeyStoreManager( context.getApplicationContext(), + keystore, db, new RecoverySessionStorage(), Executors.newSingleThreadExecutor(), new RecoverySnapshotStorage(), new RecoverySnapshotListenersStorage(), - platformKeyManager); + platformKeyManager, + applicationKeyStorage); } return mInstance; } @@ -116,19 +125,23 @@ public class RecoverableKeyStoreManager { @VisibleForTesting RecoverableKeyStoreManager( Context context, + KeyStore keystore, RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySessionStorage recoverySessionStorage, ExecutorService executorService, RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage listenersStorage, - PlatformKeyManager platformKeyManager) { + PlatformKeyManager platformKeyManager, + ApplicationKeyStorage applicationKeyStorage) { mContext = context; + mKeyStore = keystore; mDatabase = recoverableKeyStoreDb; mRecoverySessionStorage = recoverySessionStorage; mExecutorService = executorService; mListenersStorage = listenersStorage; mSnapshotStorage = snapshotStorage; mPlatformKeyManager = platformKeyManager; + mApplicationKeyStorage = applicationKeyStorage; try { mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase); @@ -406,6 +419,7 @@ public class RecoverableKeyStoreManager { } /** + * Deprecated * Generates a key named {@code alias} in the recoverable store for the calling uid. Then * returns the raw key material. * @@ -450,9 +464,55 @@ public class RecoverableKeyStoreManager { boolean wasRemoved = mDatabase.removeKey(uid, alias); if (wasRemoved) { mDatabase.setShouldCreateSnapshot(userId, uid, true); + mApplicationKeyStorage.deleteEntry(userId, uid, alias); } } + /** + * Generates a key named {@code alias} in caller's namespace. + * The key is stored in system service keystore namespace. + * + * @return grant alias, which caller can use to access the key. + */ + public String generateKey(@NonNull String alias, byte[] account) throws RemoteException { + int uid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); + + PlatformEncryptionKey encryptionKey; + try { + encryptionKey = mPlatformKeyManager.getEncryptKey(userId); + } catch (NoSuchAlgorithmException e) { + // Impossible: all algorithms must be supported by AOSP + throw new RuntimeException(e); + } catch (KeyStoreException | UnrecoverableKeyException e) { + throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); + } catch (InsecureUserException e) { + throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); + } + + try { + byte[] secretKey = + mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias); + mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey); + String grantAlias = mApplicationKeyStorage.getGrantAlias(userId, uid, alias); + return grantAlias; + } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) { + throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); + } + } + + /** + * Gets a key named {@code alias} in caller's namespace. + * + * @return grant alias, which caller can use to access the key. + */ + public String getKey(@NonNull String alias) throws RemoteException { + int uid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); + String grantAlias = mApplicationKeyStorage.getGrantAlias(userId, uid, alias); + return grantAlias; + } + private byte[] decryptRecoveryKey( RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse) throws RemoteException, ServiceSpecificException { diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java new file mode 100644 index 000000000000..600a534facf9 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java @@ -0,0 +1,113 @@ +/* + * 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.storage; + +import static android.security.keystore.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR; + +import android.annotation.Nullable; +import android.os.ServiceSpecificException; +import android.security.Credentials; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeyProtection; +import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.KeyStore; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.locksettings.recoverablekeystore.KeyStoreProxy; +import com.android.server.locksettings.recoverablekeystore.KeyStoreProxyImpl; + +import java.security.KeyStore.SecretKeyEntry; +import java.security.KeyStoreException; +import javax.crypto.spec.SecretKeySpec; + +/** + * Storage for Application keys in LockSettings service KeyStore namespace. + * + * <p> Uses KeyStore's grant mechanism to make keys usable by application process without + * revealing key material + */ +public class ApplicationKeyStorage { + private static final String APPLICATION_KEY_ALIAS_PREFIX = + "com.android.server.locksettings.recoverablekeystore/application/"; + + KeyStoreProxy mKeyStore; + KeyStore mKeystoreService; + + public static ApplicationKeyStorage getInstance(KeyStore keystoreService) + throws KeyStoreException { + return new ApplicationKeyStorage( + new KeyStoreProxyImpl(KeyStoreProxyImpl.getAndLoadAndroidKeyStore()), + keystoreService); + } + + @VisibleForTesting + ApplicationKeyStorage(KeyStoreProxy keyStore, KeyStore keystoreService) { + mKeyStore = keyStore; + mKeystoreService = keystoreService; + } + + /** + * Returns grant alias, valid in Applications namespace. + */ + public @Nullable String getGrantAlias(int userId, int uid, String alias) { + // Aliases used by {@link KeyStore} are different than used by public API. + // {@code USER_PRIVATE_KEY} prefix is used secret keys. + String keystoreAlias = Credentials.USER_PRIVATE_KEY + getInternalAlias(userId, uid, alias); + return mKeystoreService.grant(keystoreAlias, uid); + } + + public void setSymmetricKeyEntry(int userId, int uid, String alias, byte[] secretKey) + throws KeyStoreException { + try { + mKeyStore.setEntry( + getInternalAlias(userId, uid, alias), + new SecretKeyEntry( + new SecretKeySpec(secretKey, KeyProperties.KEY_ALGORITHM_AES)), + new KeyProtection.Builder( + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .build()); + } catch (KeyStoreException e) { + throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); + } + } + + public void deleteEntry(int userId, int uid, String alias) { + try { + mKeyStore.deleteEntry(getInternalAlias(userId, uid, alias)); + } catch (KeyStoreException e) { + throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); + } + } + + /** + * Returns the alias in locksettins service's KeyStore namespace used for given application key. + * + * <p>These IDs look as follows: + * {@code com.security.recoverablekeystore/application/<userId>/<uid>/<alias>} + * + * @param userId The ID of the user + * @param uid The uid + * @param alias - alias in application's namespace + * @return The alias. + */ + private String getInternalAlias(int userId, int uid, String alias) { + return APPLICATION_KEY_ALIAS_PREFIX + userId + "/" + uid + "/" + alias; + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java index d96671c5cd9d..79fd496ad11c 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java @@ -28,7 +28,7 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKe * Helper for creating the recoverable key database. */ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { - private static final int DATABASE_VERSION = 1; + private static final int DATABASE_VERSION = 2; private static final String DATABASE_NAME = "recoverablekeystore.db"; private static final String SQL_CREATE_KEYS_ENTRY = diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 502760a2cfc4..fd435f952c10 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -771,7 +771,7 @@ abstract public class ManagedServices { * Called whenever packages change, the user switches, or the secure setting * is altered. (For example in response to USER_SWITCHED in our broadcast receiver) */ - private void rebindServices(boolean forceRebind) { + protected void rebindServices(boolean forceRebind) { if (DEBUG) Slog.d(TAG, "rebindServices"); final int[] userIds = mUserProfiles.getCurrentProfileIds(); final int nUserIds = userIds.length; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 727e7ee2ac18..b25124ac4562 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2891,6 +2891,7 @@ public class NotificationManagerService extends SystemService { // Backup/restore interface @Override public byte[] getBackupPayload(int user) { + checkCallerIsSystem(); if (DBG) Slog.d(TAG, "getBackupPayload u=" + user); //TODO: http://b/22388012 if (user != UserHandle.USER_SYSTEM) { @@ -2911,6 +2912,7 @@ public class NotificationManagerService extends SystemService { @Override public void applyRestore(byte[] payload, int user) { + checkCallerIsSystem(); if (DBG) Slog.d(TAG, "applyRestore u=" + user + " payload=" + (payload != null ? new String(payload, StandardCharsets.UTF_8) : null)); if (payload == null) { @@ -5687,6 +5689,12 @@ public class NotificationManagerService extends SystemService { mListeners.unregisterService(removed.service, removed.userid); } + @Override + public void onUserUnlocked(int user) { + if (DEBUG) Slog.d(TAG, "onUserUnlocked u=" + user); + rebindServices(true); + } + public void onNotificationEnqueued(final NotificationRecord r) { final StatusBarNotification sbn = r.sbn; TrimCache trimCache = new TrimCache(sbn); diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index c02331da4ed6..5060c4df655e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -693,7 +693,7 @@ public class PackageManagerServiceUtils { InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile)); OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/); ) { - Streams.copy(fileIn, fileOut); + FileUtils.copy(fileIn, fileOut); Os.chmod(dstFile.getAbsolutePath(), 0644); return PackageManager.INSTALL_SUCCEEDED; } catch (IOException e) { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 92fd9041b32d..b53d83b1291c 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -88,6 +88,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsService; import com.android.internal.logging.MetricsLogger; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; @@ -387,7 +388,9 @@ public class UserManagerService extends IUserManager.Stub { } final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT); final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL); - setQuietModeEnabled(userHandle, false, target); + // Call setQuietModeEnabled on bg thread to avoid ANR + BackgroundThread.getHandler() + .post(() -> setQuietModeEnabled(userHandle, false, target)); } }; diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java index 946635043c12..b0b07ea767f6 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java @@ -32,6 +32,8 @@ import java.io.PrintWriter; /** * This class keeps track of battery drain rate. * + * TODO: The use of the terms "percent" and "level" in this class is not standard. Fix it. + * * Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java */ @@ -96,8 +98,12 @@ public class BatterySavingStats { public int startBatteryLevel; public int endBatteryLevel; + public int startBatteryPercent; + public int endBatteryPercent; + public long totalTimeMillis; public int totalBatteryDrain; + public int totalBatteryDrainPercent; public long totalMinutes() { return totalTimeMillis / 60_000; @@ -110,14 +116,26 @@ public class BatterySavingStats { return (double) totalBatteryDrain / (totalTimeMillis / (60.0 * 60 * 1000)); } + public double drainPercentPerHour() { + if (totalTimeMillis == 0) { + return 0; + } + return (double) totalBatteryDrainPercent / (totalTimeMillis / (60.0 * 60 * 1000)); + } + @VisibleForTesting String toStringForTest() { return "{" + totalMinutes() + "m," + totalBatteryDrain + "," - + String.format("%.2f", drainPerHour()) + "}"; + + String.format("%.2f", drainPerHour()) + "uA/H," + + String.format("%.2f", drainPercentPerHour()) + "%" + + "}"; } } @VisibleForTesting + static final String COUNTER_POWER_PERCENT_PREFIX = "battery_saver_stats_percent_"; + + @VisibleForTesting static final String COUNTER_POWER_MILLIAMPS_PREFIX = "battery_saver_stats_milliamps_"; @VisibleForTesting @@ -166,6 +184,9 @@ public class BatterySavingStats { private BatteryManagerInternal getBatteryManagerInternal() { if (mBatteryManagerInternal == null) { mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); + if (mBatteryManagerInternal == null) { + Slog.wtf(TAG, "BatteryManagerInternal not initialized"); + } } return mBatteryManagerInternal; } @@ -229,12 +250,20 @@ public class BatterySavingStats { int injectBatteryLevel() { final BatteryManagerInternal bmi = getBatteryManagerInternal(); if (bmi == null) { - Slog.wtf(TAG, "BatteryManagerInternal not initialized"); return 0; } return bmi.getBatteryChargeCounter(); } + @VisibleForTesting + int injectBatteryPercent() { + final BatteryManagerInternal bmi = getBatteryManagerInternal(); + if (bmi == null) { + return 0; + } + return bmi.getBatteryLevel(); + } + /** * Called from the outside whenever any of the states changes, when the device is not plugged * in. @@ -262,33 +291,39 @@ public class BatterySavingStats { } final long now = injectCurrentTime(); final int batteryLevel = injectBatteryLevel(); + final int batteryPercent = injectBatteryPercent(); - endLastStateLocked(now, batteryLevel); - startNewStateLocked(newState, now, batteryLevel); - mMetricsLoggerHelper.transitionState(newState, now, batteryLevel); + endLastStateLocked(now, batteryLevel, batteryPercent); + startNewStateLocked(newState, now, batteryLevel, batteryPercent); + mMetricsLoggerHelper.transitionState(newState, now, batteryLevel, batteryPercent); } - private void endLastStateLocked(long now, int batteryLevel) { + private void endLastStateLocked(long now, int batteryLevel, int batteryPercent) { if (mCurrentState < 0) { return; } final Stat stat = getStat(mCurrentState); stat.endBatteryLevel = batteryLevel; + stat.endBatteryPercent = batteryPercent; stat.endTime = now; final long deltaTime = stat.endTime - stat.startTime; final int deltaDrain = stat.startBatteryLevel - stat.endBatteryLevel; + final int deltaPercent = stat.startBatteryPercent - stat.endBatteryPercent; stat.totalTimeMillis += deltaTime; stat.totalBatteryDrain += deltaDrain; + stat.totalBatteryDrainPercent += deltaPercent; if (DEBUG) { Slog.d(TAG, "State summary: " + stateToString(mCurrentState) + ": " + (deltaTime / 1_000) + "s " + "Start level: " + stat.startBatteryLevel + "uA " + "End level: " + stat.endBatteryLevel + "uA " - + deltaDrain + "uA"); + + "Start percent: " + stat.startBatteryPercent + "% " + + "End percent: " + stat.endBatteryPercent + "% " + + "Drain " + deltaDrain + "uA"); } EventLogTags.writeBatterySavingStats( BatterySaverState.fromIndex(mCurrentState), @@ -296,12 +331,14 @@ public class BatterySavingStats { DozeState.fromIndex(mCurrentState), deltaTime, deltaDrain, + deltaPercent, stat.totalTimeMillis, - stat.totalBatteryDrain); + stat.totalBatteryDrain, + stat.totalBatteryDrainPercent); } - private void startNewStateLocked(int newState, long now, int batteryLevel) { + private void startNewStateLocked(int newState, long now, int batteryLevel, int batteryPercent) { if (DEBUG) { Slog.d(TAG, "New state: " + stateToString(newState)); } @@ -313,6 +350,7 @@ public class BatterySavingStats { final Stat stat = getStat(mCurrentState); stat.startBatteryLevel = batteryLevel; + stat.startBatteryPercent = batteryPercent; stat.startTime = now; stat.endTime = 0; } @@ -325,7 +363,7 @@ public class BatterySavingStats { indent = indent + " "; pw.print(indent); - pw.println("Battery Saver: Off On"); + pw.println("Battery Saver: Off On"); dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr", DozeState.NOT_DOZING, "NonDoze"); dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr", @@ -357,12 +395,14 @@ public class BatterySavingStats { final Stat offStat = getStat(BatterySaverState.OFF, interactiveState, dozeState); final Stat onStat = getStat(BatterySaverState.ON, interactiveState, dozeState); - pw.println(String.format("%6dm %6dmA %8.1fmA/h %6dm %6dmA %8.1fmA/h", + pw.println(String.format("%6dm %6dmA (%3d%%) %8.1fmA/h %6dm %6dmA (%3d%%) %8.1fmA/h", offStat.totalMinutes(), offStat.totalBatteryDrain / 1000, + offStat.totalBatteryDrainPercent, offStat.drainPerHour() / 1000.0, onStat.totalMinutes(), onStat.totalBatteryDrain / 1000, + onStat.totalBatteryDrainPercent, onStat.drainPerHour() / 1000.0)); } @@ -371,12 +411,13 @@ public class BatterySavingStats { private int mLastState = STATE_NOT_INITIALIZED; private long mStartTime; private int mStartBatteryLevel; + private int mStartPercent; private static final int STATE_CHANGE_DETECT_MASK = (BatterySaverState.MASK << BatterySaverState.SHIFT) | (InteractiveState.MASK << InteractiveState.SHIFT); - public void transitionState(int newState, long now, int batteryLevel) { + public void transitionState(int newState, long now, int batteryLevel, int batteryPercent) { final boolean stateChanging = ((mLastState >= 0) ^ (newState >= 0)) || (((mLastState ^ newState) & STATE_CHANGE_DETECT_MASK) != 0); @@ -384,11 +425,13 @@ public class BatterySavingStats { if (mLastState >= 0) { final long deltaTime = now - mStartTime; final int deltaBattery = mStartBatteryLevel - batteryLevel; + final int deltaPercent = mStartPercent - batteryPercent; - report(mLastState, deltaTime, deltaBattery); + report(mLastState, deltaTime, deltaBattery, deltaPercent); } mStartTime = now; mStartBatteryLevel = batteryLevel; + mStartPercent = batteryPercent; } mLastState = newState; } @@ -405,9 +448,10 @@ public class BatterySavingStats { } } - void report(int state, long deltaTimeMs, int deltaBatteryUa) { + void report(int state, long deltaTimeMs, int deltaBatteryUa, int deltaPercent) { final String suffix = getCounterSuffix(state); mMetricsLogger.count(COUNTER_POWER_MILLIAMPS_PREFIX + suffix, deltaBatteryUa / 1000); + mMetricsLogger.count(COUNTER_POWER_PERCENT_PREFIX + suffix, deltaPercent); mMetricsLogger.count(COUNTER_TIME_SECONDS_PREFIX + suffix, (int) (deltaTimeMs / 1000)); } } diff --git a/services/core/java/com/android/server/slice/SliceFullAccessList.java b/services/core/java/com/android/server/slice/SliceFullAccessList.java index 591e809ad3d1..5e0cd035a67d 100644 --- a/services/core/java/com/android/server/slice/SliceFullAccessList.java +++ b/services/core/java/com/android/server/slice/SliceFullAccessList.java @@ -63,6 +63,15 @@ public class SliceFullAccessList { pkgs.add(pkg); } + public void removeGrant(String pkg, int userId) { + ArraySet<String> pkgs = mFullAccessPkgs.get(userId, null); + if (pkgs == null) { + pkgs = new ArraySet<>(); + mFullAccessPkgs.put(userId, pkgs); + } + pkgs.remove(pkg); + } + public void writeXml(XmlSerializer out) throws IOException { out.startTag(null, TAG_LIST); out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION)); diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index 5db0fc0d9e00..c4871dfe8d5c 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -31,9 +31,11 @@ import android.app.slice.ISliceListener; import android.app.slice.ISliceManager; import android.app.slice.SliceManager; import android.app.slice.SliceSpec; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.database.ContentObserver; @@ -92,6 +94,7 @@ public class SliceManagerService extends ISliceManager.Stub { private final Handler mHandler; private final ContentObserver mObserver; private final AtomicFile mSliceAccessFile; + @GuardedBy("mAccessList") private final SliceFullAccessList mAccessList; public SliceManagerService(Context context) { @@ -127,11 +130,19 @@ public class SliceManagerService extends ISliceManager.Stub { InputStream input = mSliceAccessFile.openRead(); XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); parser.setInput(input, Encoding.UTF_8.name()); - mAccessList.readXml(parser); + synchronized (mAccessList) { + mAccessList.readXml(parser); + } } catch (IOException | XmlPullParserException e) { Slog.d(TAG, "Can't read slice access file", e); } } + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler); } /// ----- Lifecycle stuff ----- @@ -223,7 +234,9 @@ public class SliceManagerService extends ISliceManager.Stub { getContext().enforceCallingOrSelfPermission(permission.MANAGE_SLICE_PERMISSIONS, "Slice granting requires MANAGE_SLICE_PERMISSIONS"); if (allSlices) { - mAccessList.grantFullAccess(pkg, Binder.getCallingUserHandle().getIdentifier()); + synchronized (mAccessList) { + mAccessList.grantFullAccess(pkg, Binder.getCallingUserHandle().getIdentifier()); + } mHandler.post(mSaveAccessList); } else { synchronized (mLock) { @@ -245,6 +258,13 @@ public class SliceManagerService extends ISliceManager.Stub { } /// ----- internal code ----- + private void removeFullAccess(String pkg, int userId) { + synchronized (mAccessList) { + mAccessList.removeGrant(pkg, userId); + } + mHandler.post(mSaveAccessList); + } + protected void removePinnedSlice(Uri uri) { synchronized (mLock) { mPinnedSlicesByUri.remove(uri).destroy(); @@ -444,7 +464,9 @@ public class SliceManagerService extends ISliceManager.Stub { } private boolean isGrantedFullAccess(String pkg, int userId) { - return mAccessList.hasFullAccess(pkg, userId); + synchronized (mAccessList) { + return mAccessList.hasFullAccess(pkg, userId); + } } private static ServiceThread createHandler() { @@ -469,7 +491,9 @@ public class SliceManagerService extends ISliceManager.Stub { try { XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer(); out.setOutput(stream, Encoding.UTF_8.name()); - mAccessList.writeXml(out); + synchronized (mAccessList) { + mAccessList.writeXml(out); + } out.flush(); mSliceAccessFile.finishWrite(stream); } catch (IOException | XmlPullParserException e) { @@ -480,6 +504,35 @@ public class SliceManagerService extends ISliceManager.Stub { } }; + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + if (userId == UserHandle.USER_NULL) { + Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent); + return; + } + Uri data = intent.getData(); + String pkg = data != null ? data.getSchemeSpecificPart() : null; + if (pkg == null) { + Slog.w(TAG, "Intent broadcast does not contain package name: " + intent); + return; + } + switch (intent.getAction()) { + case Intent.ACTION_PACKAGE_REMOVED: + final boolean replacing = + intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + if (!replacing) { + removeFullAccess(pkg, userId); + } + break; + case Intent.ACTION_PACKAGE_DATA_CLEARED: + removeFullAccess(pkg, userId); + break; + } + } + }; + public static class Lifecycle extends SystemService { private SliceManagerService mService; diff --git a/services/core/java/com/android/server/wm/AlertWindowNotification.java b/services/core/java/com/android/server/wm/AlertWindowNotification.java index 3f320793826f..b00e595db7a1 100644 --- a/services/core/java/com/android/server/wm/AlertWindowNotification.java +++ b/services/core/java/com/android/server/wm/AlertWindowNotification.java @@ -72,20 +72,23 @@ class AlertWindowNotification { } /** Cancels the notification */ - void cancel() { + void cancel(boolean deleteChannel) { // We can't call into NotificationManager with WM lock held since it might call into AM. // So, we post a message to do it later. - mService.mH.post(this::onCancelNotification); + mService.mH.post(() -> onCancelNotification(deleteChannel)); } /** Don't call with the window manager lock held! */ - private void onCancelNotification() { + private void onCancelNotification(boolean deleteChannel) { if (!mPosted) { // Notification isn't currently posted... return; } mPosted = false; mNotificationManager.cancel(mNotificationTag, NOTIFICATION_ID); + if (deleteChannel) { + mNotificationManager.deleteNotificationChannel(mNotificationTag); + } } /** Don't call with the window manager lock held! */ @@ -146,8 +149,12 @@ class AlertWindowNotification { final String nameChannel = context.getString(R.string.alert_windows_notification_channel_name, appName); - final NotificationChannel channel = - new NotificationChannel(mNotificationTag, nameChannel, IMPORTANCE_MIN); + + NotificationChannel channel = mNotificationManager.getNotificationChannel(mNotificationTag); + if (channel != null) { + return; + } + channel = new NotificationChannel(mNotificationTag, nameChannel, IMPORTANCE_MIN); channel.enableLights(false); channel.enableVibration(false); channel.setBlockableSystem(true); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 7674b5e9ed2c..2512dbd6a12b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -662,7 +662,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mWallpaperController.updateWallpaperVisibility(); } - w.handleWindowMovedIfNeeded(mPendingTransaction); + w.handleWindowMovedIfNeeded(); final WindowStateAnimator winAnimator = w.mWinAnimator; @@ -1542,11 +1542,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * Callback used to trigger bounds update after configuration change and get ids of stacks whose * bounds were updated. */ - void updateStackBoundsAfterConfigChange(@NonNull List<Integer> changedStackList) { + void updateStackBoundsAfterConfigChange(@NonNull List<TaskStack> changedStackList) { for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) { final TaskStack stack = mTaskStackContainers.getChildAt(i); if (stack.updateBoundsAfterConfigChange()) { - changedStackList.add(stack.mStackId); + changedStackList.add(stack); } } @@ -3599,8 +3599,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } private final class AboveAppWindowContainers extends NonAppWindowContainers { - private final Dimmer mDimmer = new Dimmer(this); - private final Rect mTmpDimBoundsRect = new Rect(); AboveAppWindowContainers(String name, WindowManagerService service) { super(name, service); } @@ -3632,22 +3630,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo imeContainer.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE); } } - - @Override - Dimmer getDimmer() { - return mDimmer; - } - - @Override - void prepareSurfaces() { - mDimmer.resetDimStates(); - super.prepareSurfaces(); - getBounds(mTmpDimBoundsRect); - - if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { - scheduleAnimation(); - } - } } /** @@ -3679,6 +3661,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo }; private final String mName; + private final Dimmer mDimmer = new Dimmer(this); + private final Rect mTmpDimBoundsRect = new Rect(); + NonAppWindowContainers(String name, WindowManagerService service) { super(service); mName = name; @@ -3722,6 +3707,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo String getName() { return mName; } + + @Override + Dimmer getDimmer() { + return mDimmer; + } + + @Override + void prepareSurfaces() { + mDimmer.resetDimStates(); + super.prepareSurfaces(); + getBounds(mTmpDimBoundsRect); + + if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { + scheduleAnimation(); + } + } } private class NonMagnifiableWindowContainers extends NonAppWindowContainers { diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 8269a3b783c4..5bc739ee33b2 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -92,14 +92,21 @@ class RemoteAnimationController { onAnimationFinished(); return; } - mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS); - try { - mRemoteAnimationAdapter.getRunner().onAnimationStart(createAnimations(), - mFinishedCallback); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to start remote animation", e); - onAnimationFinished(); - } + + // Scale the timeout with the animator scale the controlling app is using. + mHandler.postDelayed(mTimeoutRunnable, + (long) (TIMEOUT_MS * mService.getCurrentAnimatorScale())); + + final RemoteAnimationTarget[] animations = createAnimations(); + mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { + try { + mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, + mFinishedCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to start remote animation", e); + onAnimationFinished(); + } + }); } private RemoteAnimationTarget[] createAnimations() { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 68c89955d2e6..8d1a82250206 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -47,6 +47,7 @@ import com.android.server.EventLogTags; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; import static android.app.AppOpsManager.MODE_ALLOWED; @@ -126,7 +127,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { boolean mOrientationChangeComplete = true; boolean mWallpaperActionPending = false; - private final ArrayList<Integer> mChangedStackList = new ArrayList(); + private final ArrayList<TaskStack> mTmpStackList = new ArrayList(); + private final ArrayList<Integer> mTmpStackIds = new ArrayList<>(); // State for the RemoteSurfaceTrace system used in testing. If this is enabled SurfaceControl // instances will be replaced with an instance that writes a binary representation of all @@ -333,7 +335,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { /** * Set new display override config and return array of ids of stacks that were changed during - * update. If called for the default display, global configuration will also be updated. + * update. If called for the default display, global configuration will also be updated. Stacks + * that are marked for deferred removal are excluded from the returned array. */ int[] setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration, int displayId) { final DisplayContent displayContent = getDisplayContent(displayId); @@ -346,24 +349,42 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { if (!configChanged) { return null; } + displayContent.onOverrideConfigurationChanged(newConfiguration); + mTmpStackList.clear(); if (displayId == DEFAULT_DISPLAY) { // Override configuration of the default display duplicates global config. In this case // we also want to update the global config. - return setGlobalConfigurationIfNeeded(newConfiguration); + setGlobalConfigurationIfNeeded(newConfiguration, mTmpStackList); } else { - return updateStackBoundsAfterConfigChange(displayId); + updateStackBoundsAfterConfigChange(displayId, mTmpStackList); } + + mTmpStackIds.clear(); + final int stackCount = mTmpStackList.size(); + + for (int i = 0; i < stackCount; ++i) { + final TaskStack stack = mTmpStackList.get(i); + + // We only include stacks that are not marked for removal as they do not exist outside + // of WindowManager at this point. + if (!stack.mDeferRemoval) { + mTmpStackIds.add(stack.mStackId); + } + } + + return mTmpStackIds.isEmpty() ? null : ArrayUtils.convertToIntArray(mTmpStackIds); } - private int[] setGlobalConfigurationIfNeeded(Configuration newConfiguration) { + private void setGlobalConfigurationIfNeeded(Configuration newConfiguration, + List<TaskStack> changedStacks) { final boolean configChanged = getConfiguration().diff(newConfiguration) != 0; if (!configChanged) { - return null; + return; } onConfigurationChanged(newConfiguration); - return updateStackBoundsAfterConfigChange(); + updateStackBoundsAfterConfigChange(changedStacks); } @Override @@ -378,26 +399,18 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { * Callback used to trigger bounds update after configuration change and get ids of stacks whose * bounds were updated. */ - private int[] updateStackBoundsAfterConfigChange() { - mChangedStackList.clear(); - + private void updateStackBoundsAfterConfigChange(List<TaskStack> changedStacks) { final int numDisplays = mChildren.size(); for (int i = 0; i < numDisplays; ++i) { final DisplayContent dc = mChildren.get(i); - dc.updateStackBoundsAfterConfigChange(mChangedStackList); + dc.updateStackBoundsAfterConfigChange(changedStacks); } - - return mChangedStackList.isEmpty() ? null : ArrayUtils.convertToIntArray(mChangedStackList); } /** Same as {@link #updateStackBoundsAfterConfigChange()} but only for a specific display. */ - private int[] updateStackBoundsAfterConfigChange(int displayId) { - mChangedStackList.clear(); - + private void updateStackBoundsAfterConfigChange(int displayId, List<TaskStack> changedStacks) { final DisplayContent dc = getDisplayContent(displayId); - dc.updateStackBoundsAfterConfigChange(mChangedStackList); - - return mChangedStackList.isEmpty() ? null : ArrayUtils.convertToIntArray(mChangedStackList); + dc.updateStackBoundsAfterConfigChange(changedStacks); } private void prepareFreezingTaskBounds() { @@ -599,6 +612,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces"); } + mService.mAnimator.executeAfterPrepareSurfacesRunnables(); + final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked; // If we are ready to perform an app transition, check through all of the app tokens to be diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 04ae38ec33b1..f09a294be75b 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -547,7 +547,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { if (allowed) { mAlertWindowNotification.post(); } else { - mAlertWindowNotification.cancel(); + mAlertWindowNotification.cancel(false /* deleteChannel */); } } } @@ -586,7 +586,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { if (mAlertWindowNotification == null) { return; } - mAlertWindowNotification.cancel(); + mAlertWindowNotification.cancel(true /* deleteChannel */); mAlertWindowNotification = null; } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index cec13abd823d..b0d42f21fac3 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -92,6 +92,7 @@ public class WindowAnimator { * executed and the corresponding transaction is closed and applied. */ private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>(); + private boolean mInExecuteAfterPrepareSurfacesRunnables; WindowAnimator(final WindowManagerService service) { mService = service; @@ -438,7 +439,13 @@ public class WindowAnimator { scheduleAnimation(); } - private void executeAfterPrepareSurfacesRunnables() { + void executeAfterPrepareSurfacesRunnables() { + + // Don't even think about to start recursing! + if (mInExecuteAfterPrepareSurfacesRunnables) { + return; + } + mInExecuteAfterPrepareSurfacesRunnables = true; // Traverse in order they were added. final int size = mAfterPrepareSurfacesRunnables.size(); @@ -446,5 +453,6 @@ public class WindowAnimator { mAfterPrepareSurfacesRunnables.get(i).run(); } mAfterPrepareSurfacesRunnables.clear(); + mInExecuteAfterPrepareSurfacesRunnables = false; } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 066e4e6a8c67..d565a6a7a476 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -24,8 +24,6 @@ import static android.Manifest.permission.RESTRICTED_VR_ACCESS; import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_USER_HANDLE; @@ -125,7 +123,6 @@ import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IAssistDataReceiver; -import android.app.WindowConfiguration; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -2466,6 +2463,7 @@ public class WindowManagerService extends IWindowManager.Stub mWaitingForConfig = false; mLastFinishedFreezeSource = "new-config"; } + return mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, displayId); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 36e3612bf10a..a9f2e03a16c6 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1758,7 +1758,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * listeners and optionally animate it. Simply checking a change of position is not enough, * because being move due to dock divider is not a trigger for animation. */ - void handleWindowMovedIfNeeded(Transaction t) { + void handleWindowMovedIfNeeded() { if (!hasMoved()) { return; } @@ -1776,7 +1776,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP && !isDragResizing() && !adjustedForMinimizedDockOrIme && getWindowConfiguration().hasMovementAnimations() && !mWinAnimator.mLastHidden) { - startMoveAnimation(t, left, top); + startMoveAnimation(left, top); } //TODO (multidisplay): Accessibility supported only for the default display. @@ -4360,7 +4360,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP commitPendingTransaction(); } - private void startMoveAnimation(Transaction t, int left, int top) { + private void startMoveAnimation(int left, int top) { if (DEBUG_ANIM) Slog.v(TAG, "Setting move animation on " + this); final Point oldPosition = new Point(); final Point newPosition = new Point(); @@ -4369,7 +4369,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final AnimationAdapter adapter = new LocalAnimationAdapter( new MoveAnimationSpec(oldPosition.x, oldPosition.y, newPosition.x, newPosition.y), mService.mSurfaceAnimationRunner); - startAnimation(t, adapter); + startAnimation(getPendingTransaction(), adapter); } private void startAnimation(Transaction t, AnimationAdapter adapter) { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index c863aabb905f..473a813c3838 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -39,6 +39,7 @@ import android.Manifest; import android.os.Binder; import android.os.ServiceSpecificException; import android.os.UserHandle; +import android.security.KeyStore; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; @@ -49,6 +50,7 @@ import android.support.test.filters.SmallTest; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; @@ -135,6 +137,8 @@ public class RecoverableKeyStoreManagerTest { @Mock private RecoverySnapshotListenersStorage mMockListenersStorage; @Mock private KeyguardManager mKeyguardManager; @Mock private PlatformKeyManager mPlatformKeyManager; + @Mock private KeyStore mKeyStore; + @Mock private ApplicationKeyStorage mApplicationKeyStorage; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; private File mDatabaseFile; @@ -164,12 +168,14 @@ public class RecoverableKeyStoreManagerTest { mRecoverableKeyStoreManager = new RecoverableKeyStoreManager( mMockContext, + mKeyStore, mRecoverableKeyStoreDb, mRecoverySessionStorage, Executors.newSingleThreadExecutor(), mRecoverySnapshotStorage, mMockListenersStorage, - mPlatformKeyManager); + mPlatformKeyManager, + mApplicationKeyStorage); } @After diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 56d4b7e8c1e8..857925b3ed17 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -5149,7 +5149,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .forAllShortcuts(si -> { switch (package1DisabledReason) { case ShortcutInfo.DISABLED_REASON_VERSION_LOWER: - assertEquals("This shortcut requires latest app", + assertEquals("App version downgraded, or isn’t compatible" + + " with this shortcut", si.getDisabledMessage()); break; case ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH: diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java index c3714c85e895..f7516b2c3694 100644 --- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java +++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java @@ -63,6 +63,11 @@ public class BatterySavingStatsTest { return mBatteryLevel; } + @Override + int injectBatteryPercent() { + return mBatteryLevel / 10; + } + void assertDumpable() { final ByteArrayOutputStream out = new ByteArrayOutputStream(); dump(new PrintWriter(out), ""); // Just make sure it won't crash. @@ -102,7 +107,7 @@ public class BatterySavingStatsTest { target.assertDumpable(); target.advanceClock(1); - target.drainBattery(2); + target.drainBattery(200); target.transitionState( BatterySaverState.OFF, @@ -110,7 +115,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(4); - target.drainBattery(1); + target.drainBattery(100); target.transitionState( BatterySaverState.OFF, @@ -118,7 +123,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(2); - target.drainBattery(5); + target.drainBattery(500); target.transitionState( BatterySaverState.OFF, @@ -126,7 +131,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(4); - target.drainBattery(1); + target.drainBattery(100); target.transitionState( BatterySaverState.OFF, @@ -134,7 +139,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(2); - target.drainBattery(5); + target.drainBattery(500); target.transitionState( BatterySaverState.OFF, @@ -142,7 +147,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(3); - target.drainBattery(1); + target.drainBattery(100); target.transitionState( BatterySaverState.OFF, @@ -150,7 +155,7 @@ public class BatterySavingStatsTest { DozeState.LIGHT); target.advanceClock(5); - target.drainBattery(1); + target.drainBattery(100); target.transitionState( BatterySaverState.OFF, @@ -158,7 +163,7 @@ public class BatterySavingStatsTest { DozeState.DEEP); target.advanceClock(1); - target.drainBattery(2); + target.drainBattery(200); target.transitionState( BatterySaverState.ON, @@ -166,7 +171,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(1); - target.drainBattery(3); + target.drainBattery(300); target.transitionState( BatterySaverState.OFF, @@ -174,7 +179,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(3); - target.drainBattery(5); + target.drainBattery(500); target.transitionState( BatterySaverState.ON, @@ -182,12 +187,12 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(3); - target.drainBattery(5); + target.drainBattery(500); target.startCharging(); target.advanceClock(5); - target.drainBattery(10); + target.drainBattery(1000); target.transitionState( BatterySaverState.ON, @@ -195,25 +200,25 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(5); - target.drainBattery(1); + target.drainBattery(100); target.startCharging(); target.assertDumpable(); assertEquals( - "BS=0,I=0,D=0:{4m,10,150.00}\n" + - "BS=1,I=0,D=0:{0m,0,0.00}\n" + - "BS=0,I=1,D=0:{14m,8,34.29}\n" + - "BS=1,I=1,D=0:{9m,9,60.00}\n" + - "BS=0,I=0,D=1:{5m,1,12.00}\n" + - "BS=1,I=0,D=1:{0m,0,0.00}\n" + - "BS=0,I=1,D=1:{0m,0,0.00}\n" + - "BS=1,I=1,D=1:{0m,0,0.00}\n" + - "BS=0,I=0,D=2:{1m,2,120.00}\n" + - "BS=1,I=0,D=2:{0m,0,0.00}\n" + - "BS=0,I=1,D=2:{0m,0,0.00}\n" + - "BS=1,I=1,D=2:{0m,0,0.00}", + "BS=0,I=0,D=0:{4m,1000,15000.00uA/H,1500.00%}\n" + + "BS=1,I=0,D=0:{0m,0,0.00uA/H,0.00%}\n" + + "BS=0,I=1,D=0:{14m,800,3428.57uA/H,342.86%}\n" + + "BS=1,I=1,D=0:{9m,900,6000.00uA/H,600.00%}\n" + + "BS=0,I=0,D=1:{5m,100,1200.00uA/H,120.00%}\n" + + "BS=1,I=0,D=1:{0m,0,0.00uA/H,0.00%}\n" + + "BS=0,I=1,D=1:{0m,0,0.00uA/H,0.00%}\n" + + "BS=1,I=1,D=1:{0m,0,0.00uA/H,0.00%}\n" + + "BS=0,I=0,D=2:{1m,200,12000.00uA/H,1200.00%}\n" + + "BS=1,I=0,D=2:{0m,0,0.00uA/H,0.00%}\n" + + "BS=0,I=1,D=2:{0m,0,0.00uA/H,0.00%}\n" + + "BS=1,I=1,D=2:{0m,0,0.00uA/H,0.00%}", target.toDebugString()); } @@ -245,6 +250,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "01", 2); + assertMetricsLog(BatterySavingStats.COUNTER_POWER_PERCENT_PREFIX + "01", 200); assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "01", 60); target.advanceClock(1); @@ -277,15 +283,17 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "00", 2 * 3); + assertMetricsLog(BatterySavingStats.COUNTER_POWER_PERCENT_PREFIX + "00", 200 * 3); assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "00", 60 * 3); target.advanceClock(10); - target.drainBattery(10_000); + target.drainBattery(10000); reset(mMetricsLogger); target.startCharging(); assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "11", 10); + assertMetricsLog(BatterySavingStats.COUNTER_POWER_PERCENT_PREFIX + "11", 1000); assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "11", 60 * 10); target.advanceClock(1); @@ -305,6 +313,7 @@ public class BatterySavingStatsTest { target.startCharging(); assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "10", 2); + assertMetricsLog(BatterySavingStats.COUNTER_POWER_PERCENT_PREFIX + "10", 200); assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "10", 60); } } diff --git a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java index f860195bd6ea..26a7313b71d8 100644 --- a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -19,12 +19,12 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import android.graphics.Point; import android.graphics.Rect; -import android.platform.test.annotations.Postsubmit; import android.support.test.filters.FlakyTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -137,6 +137,32 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { } @Test + public void testTimeout_scaled() throws Exception { + sWm.setAnimationScale(2, 5.0f); + try{ + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + mController.goodToGo(); + + mClock.fastForward(2500); + mHandler.timeAdvance(); + + verify(mMockRunner, never()).onAnimationCancelled(); + + mClock.fastForward(10000); + mHandler.timeAdvance(); + + verify(mMockRunner).onAnimationCancelled(); + verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + } finally { + sWm.setAnimationScale(2, 1.0f); + } + + } + + @Test public void testZeroAnimations() throws Exception { mController.goodToGo(); verifyZeroInteractions(mMockRunner); diff --git a/services/tests/servicestests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/RootWindowContainerTests.java new file mode 100644 index 000000000000..51b019a4d61e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/RootWindowContainerTests.java @@ -0,0 +1,50 @@ +package com.android.server.wm; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import android.content.res.Configuration; +import android.graphics.Rect; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import static org.junit.Assert.assertTrue; + +/** + * Tests for the {@link RootWindowContainer} class. + * + * Build/Install/Run: + * atest FrameworksServicesTests:com.android.server.wm.RootWindowContainerTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class RootWindowContainerTests extends WindowTestsBase { + @Test + public void testSetDisplayOverrideConfigurationIfNeeded() throws Exception { + // Add first stack we expect to be updated with configuration change. + final TaskStack stack = createTaskStackOnDisplay(mDisplayContent); + stack.getOverrideConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 5, 5)); + + // Add second task that will be set for deferred removal that should not be returned + // with the configuration change. + final TaskStack deferredDeletedStack = createTaskStackOnDisplay(mDisplayContent); + deferredDeletedStack.getOverrideConfiguration().windowConfiguration.setBounds( + new Rect(0, 0, 5, 5)); + deferredDeletedStack.mDeferRemoval = true; + + final Configuration override = new Configuration( + mDisplayContent.getOverrideConfiguration()); + override.windowConfiguration.setBounds(new Rect(0, 0, 10, 10)); + + // Set display override. + final int[] results = sWm.mRoot.setDisplayOverrideConfigurationIfNeeded(override, + mDisplayContent.getDisplayId()); + + // Ensure only first stack is returned. + assertTrue(results.length == 1); + assertTrue(results[0] == stack.mStackId); + } +} diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java index 99eb8468c20d..e36586eba26c 100644 --- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -210,6 +210,9 @@ public class ShortcutManagerTestUtils { runCommand(instrumentation, "cmd package set-home-activity --user " + instrumentation.getContext().getUserId() + " " + component, result -> result.contains("Success")); + runCommand(instrumentation, "cmd shortcut clear-default-launcher --user " + + instrumentation.getContext().getUserId(), + result -> result.contains("Success")); } public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) { 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 9ae6f00f44ca..6b6df294e47c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -117,7 +117,7 @@ import java.util.Set; public class NotificationManagerServiceTest extends UiServiceTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; private final int mUid = Binder.getCallingUid(); - private NotificationManagerService mService; + private TestableNotificationManagerService mService; private INotificationManager mBinderService; private NotificationManagerInternal mInternalService; @Mock @@ -152,17 +152,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Use a Testable subclass so we can simulate calls from the system without failing. private static class TestableNotificationManagerService extends NotificationManagerService { + int countSystemChecks = 0; + public TestableNotificationManagerService(Context context) { super(context); } @Override protected boolean isCallingUidSystem() { + countSystemChecks++; return true; } @Override protected boolean isCallerSystemOrPhone() { + countSystemChecks++; return true; } @@ -2429,4 +2433,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setUid(user2.sbn.getUid()) .setLastNotified(user2.sbn.getPostTime()))); } + + @Test + public void testRestore() throws Exception { + int systemChecks = mService.countSystemChecks; + mBinderService.applyRestore(null, UserHandle.USER_SYSTEM); + assertEquals(1, mService.countSystemChecks - systemChecks); + } + + @Test + public void testBackup() throws Exception { + int systemChecks = mService.countSystemChecks; + mBinderService.getBackupPayload(1); + assertEquals(1, mService.countSystemChecks - systemChecks); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java index 7c14d083d3c9..b784c6006a86 100644 --- a/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java +++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java @@ -67,6 +67,15 @@ public class SliceFullAccessListTest extends UiServiceTestCase { } @Test + public void testRemoveAccess() { + mAccessList.grantFullAccess("pkg", 0); + assertTrue(mAccessList.hasFullAccess("pkg", 0)); + + mAccessList.removeGrant("pkg", 0); + assertFalse(mAccessList.hasFullAccess("pkg", 0)); + } + + @Test public void testSerialization() throws XmlPullParserException, IOException { mAccessList.grantFullAccess("pkg", 0); mAccessList.grantFullAccess("pkg1", 0); diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index fcfc5931ac7b..95eb14ada354 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -134,6 +134,25 @@ public final class PhoneAccount implements Parcelable { "android.telecom.extra.LOG_SELF_MANAGED_CALLS"; /** + * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which + * indicates whether calls for a {@link PhoneAccount} should generate a "call recording tone" + * when the user is recording audio on the device. + * <p> + * The call recording tone is played over the telephony audio stream so that the remote party + * has an audible indication that it is possible their call is being recorded using a call + * recording app on the device. + * <p> + * This extra only has an effect for calls placed via Telephony (e.g. + * {@link #CAPABILITY_SIM_SUBSCRIPTION}). + * <p> + * The call recording tone is a 1400 hz tone which repeats every 15 seconds while recording is + * in progress. + * @hide + */ + public static final String EXTRA_PLAY_CALL_RECORDING_TONE = + "android.telecom.extra.PLAY_CALL_RECORDING_TONE"; + + /** * Flag indicating that this {@code PhoneAccount} can act as a connection manager for * other connections. The {@link ConnectionService} associated with this {@code PhoneAccount} * will be allowed to manage phone calls including using its own proprietary phone-call diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index a34e9f9481fa..63ab76677dc7 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -77,6 +77,14 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; + /** + * Boolean indicating if the "Call barring" item is visible in the Call Settings menu. + * true means visible. false means gone. + * @hide + */ + public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = + "call_barring_visibility_bool"; + /** * Flag indicating whether the Phone app should ignore EVENT_SIM_NETWORK_LOCKED * events from the Sim. @@ -146,6 +154,15 @@ public class CarrierConfigManager { public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool"; /** + * Determines if the carrier requires that a tone be played to the remote party when an app is + * recording audio during a call (e.g. using a call recording app). + * <p> + * Note: This requires the Telephony config_supports_telephony_audio_device overlay to be true + * in order to work. + * @hide + */ + public static final String KEY_PLAY_CALL_RECORDING_TONE_BOOL = "play_call_recording_tone_bool"; + /** * Determines if the carrier requires converting the destination number before sending out an * SMS. Certain networks and numbering plans require different formats. */ @@ -1381,6 +1398,14 @@ public class CarrierConfigManager { public static final String KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO = "video_calls_can_be_hd_audio"; /** + * When true, indicates that the HD audio icon in the in-call screen should be shown for + * GSM/CDMA calls. + * @hide + */ + public static final String KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO = + "gsm_cdma_calls_can_be_hd_audio"; + + /** * Whether system apps are allowed to use fallback if carrier video call is not available. * Defaults to {@code true}. * @@ -1793,6 +1818,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_ADDITIONAL_CALL_SETTING_BOOL, true); sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL, false); sDefaults.putBoolean(KEY_ALLOW_LOCAL_DTMF_TONES_BOOL, true); + sDefaults.putBoolean(KEY_PLAY_CALL_RECORDING_TONE_BOOL, false); sDefaults.putBoolean(KEY_APN_EXPAND_BOOL, true); sDefaults.putBoolean(KEY_AUTO_RETRY_ENABLED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_SETTINGS_ENABLE_BOOL, false); @@ -1833,6 +1859,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_HIDE_SIM_LOCK_SETTINGS_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONED_BOOL, false); + sDefaults.putBoolean(KEY_CALL_BARRING_VISIBILITY_BOOL, false); sDefaults.putBoolean(KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL, false); sDefaults.putBoolean(KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL, false); sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true); @@ -2033,6 +2060,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL, true); sDefaults.putBoolean(KEY_WIFI_CALLS_CAN_BE_HD_AUDIO, true); sDefaults.putBoolean(KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO, true); + sDefaults.putBoolean(KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO, false); sDefaults.putBoolean(KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL, true); sDefaults.putStringArray(KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY, null); diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java index dfaaab918012..ece1ee378170 100644 --- a/telephony/java/android/telephony/CellSignalStrengthCdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java @@ -36,48 +36,14 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements private int mEvdoEcio; // This value is the EVDO Ec/Io private int mEvdoSnr; // Valid values are 0-8. 8 is the highest signal to noise ratio - /** - * Empty constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthCdma() { setDefaultValues(); } - /** - * Constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthCdma(int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr) { - initialize(cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr); - } - - /** - * Copy constructors - * - * @param s Source SignalStrength - * - * @hide - */ - public CellSignalStrengthCdma(CellSignalStrengthCdma s) { - copyFrom(s); - } - - /** - * Initialize all the values - * - * @param cdmaDbm - * @param cdmaEcio - * @param evdoDbm - * @param evdoEcio - * @param evdoSnr - * - * @hide - */ - public void initialize(int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr) { mCdmaDbm = cdmaDbm; mCdmaEcio = cdmaEcio; mEvdoDbm = evdoDbm; @@ -85,9 +51,12 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements mEvdoSnr = evdoSnr; } - /** - * @hide - */ + /** @hide */ + public CellSignalStrengthCdma(CellSignalStrengthCdma s) { + copyFrom(s); + } + + /** @hide */ protected void copyFrom(CellSignalStrengthCdma s) { mCdmaDbm = s.mCdmaDbm; mCdmaEcio = s.mCdmaEcio; @@ -96,9 +65,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements mEvdoSnr = s.mEvdoSnr; } - /** - * @hide - */ + /** @hide */ @Override public CellSignalStrengthCdma copy() { return new CellSignalStrengthCdma(this); diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index f68d2cad1226..8687cd1c454b 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -34,80 +34,40 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P private static final int GSM_SIGNAL_STRENGTH_GOOD = 8; private static final int GSM_SIGNAL_STRENGTH_MODERATE = 5; - private int mSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5 + private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5 private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 - private int mTimingAdvance; + private int mTimingAdvance; // range from 0-219 or Integer.MAX_VALUE if unknown - /** - * Empty constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthGsm() { setDefaultValues(); } - /** - * Constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthGsm(int ss, int ber) { - initialize(ss, ber); - } - - /** - * Copy constructors - * - * @param s Source SignalStrength - * - * @hide - */ - public CellSignalStrengthGsm(CellSignalStrengthGsm s) { - copyFrom(s); + this(ss, ber, Integer.MAX_VALUE); } - /** - * Initialize all the values - * - * @param ss SignalStrength as ASU value - * @param ber is Bit Error Rate - * - * @hide - */ - public void initialize(int ss, int ber) { + /** @hide */ + public CellSignalStrengthGsm(int ss, int ber, int ta) { mSignalStrength = ss; mBitErrorRate = ber; - mTimingAdvance = Integer.MAX_VALUE; + mTimingAdvance = ta; } - /** - * Initialize all the values - * - * @param ss SignalStrength as ASU value - * @param ber is Bit Error Rate - * @param ta timing advance - * - * @hide - */ - public void initialize(int ss, int ber, int ta) { - mSignalStrength = ss; - mBitErrorRate = ber; - mTimingAdvance = ta; + /** @hide */ + public CellSignalStrengthGsm(CellSignalStrengthGsm s) { + copyFrom(s); } - /** - * @hide - */ + /** @hide */ protected void copyFrom(CellSignalStrengthGsm s) { mSignalStrength = s.mSignalStrength; mBitErrorRate = s.mBitErrorRate; mTimingAdvance = s.mTimingAdvance; } - /** - * @hide - */ + /** @hide */ @Override public CellSignalStrengthGsm copy() { return new CellSignalStrengthGsm(this); diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index 6ffc8b6e497c..f009fb145fc2 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -37,50 +37,15 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P private int mCqi; private int mTimingAdvance; - /** - * Empty constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthLte() { setDefaultValues(); } - /** - * Constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthLte(int signalStrength, int rsrp, int rsrq, int rssnr, int cqi, int timingAdvance) { - initialize(signalStrength, rsrp, rsrq, rssnr, cqi, timingAdvance); - } - - /** - * Copy constructors - * - * @param s Source SignalStrength - * - * @hide - */ - public CellSignalStrengthLte(CellSignalStrengthLte s) { - copyFrom(s); - } - - /** - * Initialize all the values - * - * @param lteSignalStrength - * @param rsrp - * @param rsrq - * @param rssnr - * @param cqi - * - * @hide - */ - public void initialize(int lteSignalStrength, int rsrp, int rsrq, int rssnr, int cqi, - int timingAdvance) { - mSignalStrength = lteSignalStrength; + mSignalStrength = signalStrength; mRsrp = rsrp; mRsrq = rsrq; mRssnr = rssnr; @@ -88,25 +53,12 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P mTimingAdvance = timingAdvance; } - /** - * Initialize from the SignalStrength structure. - * - * @param ss - * - * @hide - */ - public void initialize(SignalStrength ss, int timingAdvance) { - mSignalStrength = ss.getLteSignalStrength(); - mRsrp = ss.getLteRsrp(); - mRsrq = ss.getLteRsrq(); - mRssnr = ss.getLteRssnr(); - mCqi = ss.getLteCqi(); - mTimingAdvance = timingAdvance; + /** @hide */ + public CellSignalStrengthLte(CellSignalStrengthLte s) { + copyFrom(s); } - /** - * @hide - */ + /** @hide */ protected void copyFrom(CellSignalStrengthLte s) { mSignalStrength = s.mSignalStrength; mRsrp = s.mRsrp; @@ -116,9 +68,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P mTimingAdvance = s.mTimingAdvance; } - /** - * @hide - */ + /** @hide */ @Override public CellSignalStrengthLte copy() { return new CellSignalStrengthLte(this); diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java index 2cd56b8500a6..dd32a960db91 100644 --- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java @@ -34,62 +34,32 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements private static final int WCDMA_SIGNAL_STRENGTH_GOOD = 8; private static final int WCDMA_SIGNAL_STRENGTH_MODERATE = 5; - private int mSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5 - private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 + private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5 + private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 - /** - * Empty constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthWcdma() { setDefaultValues(); } - /** - * Constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthWcdma(int ss, int ber) { - initialize(ss, ber); + mSignalStrength = ss; + mBitErrorRate = ber; } - /** - * Copy constructors - * - * @param s Source SignalStrength - * - * @hide - */ + /** @hide */ public CellSignalStrengthWcdma(CellSignalStrengthWcdma s) { copyFrom(s); } - /** - * Initialize all the values - * - * @param ss SignalStrength as ASU value - * @param ber is Bit Error Rate - * - * @hide - */ - public void initialize(int ss, int ber) { - mSignalStrength = ss; - mBitErrorRate = ber; - } - - /** - * @hide - */ + /** @hide */ protected void copyFrom(CellSignalStrengthWcdma s) { mSignalStrength = s.mSignalStrength; mBitErrorRate = s.mBitErrorRate; } - /** - * @hide - */ + /** @hide */ @Override public CellSignalStrengthWcdma copy() { return new CellSignalStrengthWcdma(this); diff --git a/telephony/java/android/telephony/LocationAccessPolicy.java b/telephony/java/android/telephony/LocationAccessPolicy.java new file mode 100644 index 000000000000..b362df9ff677 --- /dev/null +++ b/telephony/java/android/telephony/LocationAccessPolicy.java @@ -0,0 +1,160 @@ +/* + * 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 android.telephony; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.location.LocationManager; +import android.os.Binder; +import android.os.Build; +import android.os.Process; +import android.os.Trace; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.util.SparseBooleanArray; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Helper for performing location access checks. + * @hide + */ +public final class LocationAccessPolicy { + /** + * API to determine if the caller has permissions to get cell location. + * + * @param pkgName Package name of the application requesting access + * @param uid The uid of the package + * @param pid The pid of the package + * @return boolean true or false if permissions is granted + */ + public static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName, + int uid, int pid) throws SecurityException { + Trace.beginSection("TelephonyLocationCheck"); + try { + // Always allow the phone process to access location. This avoid breaking legacy code + // that rely on public-facing APIs to access cell location, and it doesn't create a + // info leak risk because the cell location is stored in the phone process anyway. + if (uid == Process.PHONE_UID) { + return true; + } + + // We always require the location permission and also require the + // location mode to be on for non-legacy apps. Legacy apps are + // required to be in the foreground to at least mitigate the case + // where a legacy app the user is not using tracks their location. + // Granting ACCESS_FINE_LOCATION to an app automatically grants it + // ACCESS_COARSE_LOCATION. + + if (context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, pid, uid) == + PackageManager.PERMISSION_DENIED) { + return false; + } + final int opCode = AppOpsManager.permissionToOpCode( + Manifest.permission.ACCESS_COARSE_LOCATION); + if (opCode != AppOpsManager.OP_NONE && context.getSystemService(AppOpsManager.class) + .noteOpNoThrow(opCode, uid, pkgName) != AppOpsManager.MODE_ALLOWED) { + return false; + } + if (!isLocationModeEnabled(context, UserHandle.getUserId(uid)) + && !isLegacyForeground(context, pkgName, uid)) { + return false; + } + // If the user or profile is current, permission is granted. + // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. + return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context); + } finally { + Trace.endSection(); + } + } + + private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { + int locationMode = Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, userId); + return locationMode != Settings.Secure.LOCATION_MODE_OFF + && locationMode != Settings.Secure.LOCATION_MODE_SENSORS_ONLY; + } + + private static boolean isLegacyForeground(@NonNull Context context, @NonNull String pkgName, + int uid) { + long token = Binder.clearCallingIdentity(); + try { + return isLegacyVersion(context, pkgName) && isForegroundApp(context, uid); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private static boolean isLegacyVersion(@NonNull Context context, @NonNull String pkgName) { + try { + if (context.getPackageManager().getApplicationInfo(pkgName, 0) + .targetSdkVersion <= Build.VERSION_CODES.O) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + // In case of exception, assume known app (more strict checking) + // Note: This case will never happen since checkPackage is + // called to verify validity before checking app's version. + } + return false; + } + + private static boolean isForegroundApp(@NonNull Context context, int uid) { + final ActivityManager am = context.getSystemService(ActivityManager.class); + return am.getUidImportance(uid) <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; + } + + private static boolean checkInteractAcrossUsersFull(@NonNull Context context) { + return context.checkCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + == PackageManager.PERMISSION_GRANTED; + } + + private static boolean isCurrentProfile(@NonNull Context context, int uid) { + long token = Binder.clearCallingIdentity(); + try { + final int currentUser = ActivityManager.getCurrentUser(); + final int callingUserId = UserHandle.getUserId(uid); + if (callingUserId == currentUser) { + return true; + } else { + List<UserInfo> userProfiles = context.getSystemService( + UserManager.class).getProfiles(currentUser); + for (UserInfo user : userProfiles) { + if (user.id == callingUserId) { + return true; + } + } + } + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } +} diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index debf43da79b4..34f2dac028c7 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -612,9 +612,9 @@ public class SubscriptionManager { * onSubscriptionsChanged overridden. */ public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { - String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; + String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>"; if (DBG) { - logd("register OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug + logd("register OnSubscriptionsChangedListener pkgName=" + pkgName + " listener=" + listener); } try { @@ -623,7 +623,7 @@ public class SubscriptionManager { ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( "telephony.registry")); if (tr != null) { - tr.addOnSubscriptionsChangedListener(pkgForDebug, listener.callback); + tr.addOnSubscriptionsChangedListener(pkgName, listener.callback); } } catch (RemoteException ex) { // Should not happen diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java index dc5ba0cc83b5..fe63aa1bf79b 100644 --- a/wifi/java/android/net/wifi/RttManager.java +++ b/wifi/java/android/net/wifi/RttManager.java @@ -7,22 +7,21 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; +import android.content.pm.PackageManager; +import android.net.wifi.rtt.RangingRequest; +import android.net.wifi.rtt.RangingResult; +import android.net.wifi.rtt.RangingResultCallback; +import android.net.wifi.rtt.WifiRttManager; import android.os.Looper; -import android.os.Message; -import android.os.Messenger; import android.os.Parcel; import android.os.Parcelable; -import android.os.RemoteException; import android.util.Log; -import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; +import java.util.List; + /** @hide */ @SystemApi @SystemService(Context.WIFI_RTT_SERVICE) @@ -175,7 +174,8 @@ public class RttManager { @Deprecated @SuppressLint("Doclava125") public Capabilities getCapabilities() { - return new Capabilities(); + throw new UnsupportedOperationException( + "getCapabilities is not supported in the adaptation layer"); } /** @@ -316,16 +316,7 @@ public class RttManager { @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) public RttCapabilities getRttCapabilities() { - synchronized (mCapabilitiesLock) { - if (mRttCapabilities == null) { - try { - mRttCapabilities = mService.getRttCapabilities(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - return mRttCapabilities; - } + return mRttCapabilities; } /** specifies parameters for RTT request */ @@ -972,69 +963,6 @@ public class RttManager { } } - private boolean rttParamSanity(RttParams params, int index) { - if (mRttCapabilities == null) { - if(getRttCapabilities() == null) { - Log.e(TAG, "Can not get RTT capabilities"); - throw new IllegalStateException("RTT chip is not working"); - } - } - - if (params.deviceType != RTT_PEER_TYPE_AP) { - return false; - } else if (params.requestType != RTT_TYPE_ONE_SIDED && params.requestType != - RTT_TYPE_TWO_SIDED) { - Log.e(TAG, "Request " + index + ": Illegal Request Type: " + params.requestType); - return false; - } else if (params.requestType == RTT_TYPE_ONE_SIDED && - !mRttCapabilities.oneSidedRttSupported) { - Log.e(TAG, "Request " + index + ": One side RTT is not supported"); - return false; - } else if (params.requestType == RTT_TYPE_TWO_SIDED && - !mRttCapabilities.twoSided11McRttSupported) { - Log.e(TAG, "Request " + index + ": two side RTT is not supported"); - return false; - } else if(params.bssid == null || params.bssid.isEmpty()) { - Log.e(TAG,"No BSSID in params"); - return false; - } else if ( params.numberBurst != 0 ) { - Log.e(TAG, "Request " + index + ": Illegal number of burst: " + params.numberBurst); - return false; - } else if (params.numSamplesPerBurst <= 0 || params.numSamplesPerBurst > 31) { - Log.e(TAG, "Request " + index + ": Illegal sample number per burst: " + - params.numSamplesPerBurst); - return false; - } else if (params.numRetriesPerMeasurementFrame < 0 || - params.numRetriesPerMeasurementFrame > 3) { - Log.e(TAG, "Request " + index + ": Illegal measurement frame retry number:" + - params.numRetriesPerMeasurementFrame); - return false; - } else if(params.numRetriesPerFTMR < 0 || - params.numRetriesPerFTMR > 3) { - Log.e(TAG, "Request " + index + ": Illegal FTMR frame retry number:" + - params.numRetriesPerFTMR); - return false; - } else if (params.LCIRequest && !mRttCapabilities.lciSupported) { - Log.e(TAG, "Request " + index + ": LCI is not supported"); - return false; - } else if (params.LCRRequest && !mRttCapabilities.lcrSupported) { - Log.e(TAG, "Request " + index + ": LCR is not supported"); - return false; - } else if (params.burstTimeout < 1 || - (params.burstTimeout > 11 && params.burstTimeout != 15)){ - Log.e(TAG, "Request " + index + ": Illegal burst timeout: " + params.burstTimeout); - return false; - } else if ((params.preamble & mRttCapabilities.preambleSupported) == 0) { - Log.e(TAG, "Request " + index + ": Do not support this preamble: " + params.preamble); - return false; - } else if ((params.bandwidth & mRttCapabilities.bwSupported) == 0) { - Log.e(TAG, "Request " + index + ": Do not support this bandwidth: " + params.bandwidth); - return false; - } - - return true; - } - /** * Request to start an RTT ranging * @@ -1045,24 +973,72 @@ public class RttManager { */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startRanging(RttParams[] params, RttListener listener) { - int index = 0; - for(RttParams rttParam : params) { - if (!rttParamSanity(rttParam, index)) { - throw new IllegalArgumentException("RTT Request Parameter Illegal"); + Log.i(TAG, "Send RTT request to RTT Service"); + + if (!mNewService.isAvailable()) { + listener.onFailure(REASON_NOT_AVAILABLE, ""); + return; + } + + RangingRequest.Builder builder = new RangingRequest.Builder(); + for (RttParams rttParams : params) { + if (rttParams.deviceType != RTT_PEER_TYPE_AP) { + listener.onFailure(REASON_INVALID_REQUEST, "Only AP peers are supported"); + return; + } + + ScanResult reconstructed = new ScanResult(); + reconstructed.BSSID = rttParams.bssid; + if (rttParams.requestType == RTT_TYPE_TWO_SIDED) { + reconstructed.setFlag(ScanResult.FLAG_80211mc_RESPONDER); } - index++; + reconstructed.channelWidth = rttParams.channelWidth; + reconstructed.frequency = rttParams.frequency; + reconstructed.centerFreq0 = rttParams.centerFreq0; + reconstructed.centerFreq1 = rttParams.centerFreq1; + builder.addResponder( + android.net.wifi.rtt.ResponderConfig.fromScanResult(reconstructed)); + } + try { + mNewService.startRanging(builder.build(), new RangingResultCallback() { + @Override + public void onRangingFailure(int code) { + int localCode = REASON_UNSPECIFIED; + if (code == STATUS_CODE_FAIL_RTT_NOT_AVAILABLE) { + localCode = REASON_NOT_AVAILABLE; + } + listener.onFailure(localCode, ""); + } + + @Override + public void onRangingResults(List<RangingResult> results) { + RttResult[] legacyResults = new RttResult[results.size()]; + int i = 0; + for (RangingResult result : results) { + legacyResults[i] = new RttResult(); + legacyResults[i].status = result.getStatus(); + legacyResults[i].bssid = result.getMacAddress().toString(); + legacyResults[i].distance = result.getDistanceMm() / 10; + legacyResults[i].distanceStandardDeviation = + result.getDistanceStdDevMm() / 10; + legacyResults[i].rssi = result.getRssi(); + legacyResults[i].ts = result.getRangingTimestampUs(); + } + listener.onSuccess(legacyResults); + } + }, null); + } catch (IllegalArgumentException e) { + Log.e(TAG, "startRanging: invalid arguments - " + e); + listener.onFailure(REASON_INVALID_REQUEST, e.getMessage()); + } catch (SecurityException e) { + Log.e(TAG, "startRanging: security exception - " + e); + listener.onFailure(REASON_PERMISSION_DENIED, e.getMessage()); } - validateChannel(); - ParcelableRttParams parcelableParams = new ParcelableRttParams(params); - Log.i(TAG, "Send RTT request to RTT Service"); - mAsyncChannel.sendMessage(CMD_OP_START_RANGING, - 0, putListener(listener), parcelableParams); } @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopRanging(RttListener listener) { - validateChannel(); - mAsyncChannel.sendMessage(CMD_OP_STOP_RANGING, 0, removeListener(listener)); + Log.e(TAG, "stopRanging: unsupported operation - nop"); } /** @@ -1095,12 +1071,8 @@ public class RttManager { */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void enableResponder(ResponderCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - validateChannel(); - int key = putListenerIfAbsent(callback); - mAsyncChannel.sendMessage(CMD_OP_ENABLE_RESPONDER, 0, key); + throw new UnsupportedOperationException( + "enableResponder is not supported in the adaptation layer"); } /** @@ -1115,16 +1087,8 @@ public class RttManager { */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void disableResponder(ResponderCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - validateChannel(); - int key = removeListener(callback); - if (key == INVALID_KEY) { - Log.e(TAG, "responder not enabled yet"); - return; - } - mAsyncChannel.sendMessage(CMD_OP_DISABLE_RESPONDER, 0, key); + throw new UnsupportedOperationException( + "disableResponder is not supported in the adaptation layer"); } /** @@ -1238,17 +1202,9 @@ public class RttManager { /** @hide */ public static final int CMD_OP_REG_BINDER = BASE + 9; - private static final int INVALID_KEY = 0; - private final Context mContext; - private final IRttManager mService; - private final SparseArray mListenerMap = new SparseArray(); - private final Object mListenerMapLock = new Object(); - private final Object mCapabilitiesLock = new Object(); - + private final WifiRttManager mNewService; private RttCapabilities mRttCapabilities; - private int mListenerKey = 1; - private AsyncChannel mAsyncChannel; /** * Create a new WifiScanner instance. @@ -1263,170 +1219,20 @@ public class RttManager { */ public RttManager(Context context, IRttManager service, Looper looper) { mContext = context; - mService = service; - Messenger messenger = null; - int[] key = new int[1]; - try { - Log.d(TAG, "Get the messenger from " + mService); - messenger = mService.getMessenger(new Binder(), key); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - - if (messenger == null) { - throw new IllegalStateException("getMessenger() returned null! This is invalid."); - } - - mAsyncChannel = new AsyncChannel(); - - Handler handler = new ServiceHandler(looper); - mAsyncChannel.connectSync(mContext, handler, messenger); - // We cannot use fullyConnectSync because it sends the FULL_CONNECTION message - // synchronously, which causes RttService to receive the wrong replyTo value. - mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION, - new RttClient(context.getPackageName())); - mAsyncChannel.sendMessage(CMD_OP_REG_BINDER, key[0]); + mNewService = (WifiRttManager) mContext.getSystemService(Context.WIFI_RTT_RANGING_SERVICE); + + boolean rttSupported = mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_WIFI_RTT); + + mRttCapabilities = new RttCapabilities(); + mRttCapabilities.oneSidedRttSupported = rttSupported; + mRttCapabilities.twoSided11McRttSupported = rttSupported; + mRttCapabilities.lciSupported = false; + mRttCapabilities.lcrSupported = false; + mRttCapabilities.preambleSupported = PREAMBLE_HT | PREAMBLE_VHT; + mRttCapabilities.bwSupported = RTT_BW_40_SUPPORT | RTT_BW_80_SUPPORT; + mRttCapabilities.responderSupported = false; + mRttCapabilities.secureRttSupported = false; } - - private void validateChannel() { - if (mAsyncChannel == null) throw new IllegalStateException( - "No permission to access and change wifi or a bad initialization"); - } - - private int putListener(Object listener) { - if (listener == null) return INVALID_KEY; - int key; - synchronized (mListenerMapLock) { - do { - key = mListenerKey++; - } while (key == INVALID_KEY); - mListenerMap.put(key, listener); - } - return key; - } - - // Insert a listener if it doesn't exist in mListenerMap. Returns the key of the listener. - private int putListenerIfAbsent(Object listener) { - if (listener == null) return INVALID_KEY; - synchronized (mListenerMapLock) { - int key = getListenerKey(listener); - if (key != INVALID_KEY) { - return key; - } - do { - key = mListenerKey++; - } while (key == INVALID_KEY); - mListenerMap.put(key, listener); - return key; - } - - } - - private Object getListener(int key) { - if (key == INVALID_KEY) return null; - synchronized (mListenerMapLock) { - Object listener = mListenerMap.get(key); - return listener; - } - } - - private int getListenerKey(Object listener) { - if (listener == null) return INVALID_KEY; - synchronized (mListenerMapLock) { - int index = mListenerMap.indexOfValue(listener); - if (index == -1) { - return INVALID_KEY; - } else { - return mListenerMap.keyAt(index); - } - } - } - - private Object removeListener(int key) { - if (key == INVALID_KEY) return null; - synchronized (mListenerMapLock) { - Object listener = mListenerMap.get(key); - mListenerMap.remove(key); - return listener; - } - } - - private int removeListener(Object listener) { - int key = getListenerKey(listener); - if (key == INVALID_KEY) return key; - synchronized (mListenerMapLock) { - mListenerMap.remove(key); - return key; - } - } - - private class ServiceHandler extends Handler { - ServiceHandler(Looper looper) { - super(looper); - } - @Override - public void handleMessage(Message msg) { - Log.i(TAG, "RTT manager get message: " + msg.what); - switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: - return; - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: - Log.e(TAG, "Channel connection lost"); - // This will cause all further async API calls on the WifiManager - // to fail and throw an exception - mAsyncChannel = null; - getLooper().quit(); - return; - } - - Object listener = getListener(msg.arg2); - if (listener == null) { - Log.e(TAG, "invalid listener key = " + msg.arg2 ); - return; - } else { - Log.i(TAG, "listener key = " + msg.arg2); - } - - switch (msg.what) { - /* ActionListeners grouped together */ - case CMD_OP_SUCCEEDED : - reportSuccess(listener, msg); - removeListener(msg.arg2); - break; - case CMD_OP_FAILED : - reportFailure(listener, msg); - removeListener(msg.arg2); - break; - case CMD_OP_ABORTED : - ((RttListener) listener).onAborted(); - removeListener(msg.arg2); - break; - case CMD_OP_ENALBE_RESPONDER_SUCCEEDED: - ResponderConfig config = (ResponderConfig) msg.obj; - ((ResponderCallback) (listener)).onResponderEnabled(config); - break; - case CMD_OP_ENALBE_RESPONDER_FAILED: - ((ResponderCallback) (listener)).onResponderEnableFailure(msg.arg1); - removeListener(msg.arg2); - break; - default: - if (DBG) Log.d(TAG, "Ignoring message " + msg.what); - return; - } - } - - void reportSuccess(Object listener, Message msg) { - RttListener rttListener = (RttListener) listener; - ParcelableRttResults parcelableResults = (ParcelableRttResults) msg.obj; - ((RttListener) listener).onSuccess(parcelableResults.mResults); - } - - void reportFailure(Object listener, Message msg) { - RttListener rttListener = (RttListener) listener; - Bundle bundle = (Bundle) msg.obj; - ((RttListener) listener).onFailure(msg.arg1, bundle.getString(DESCRIPTION_KEY)); - } - } - } |