diff options
224 files changed, 6872 insertions, 1698 deletions
diff --git a/aidl_api/libincremental_aidl/current/android/os/incremental/IncrementalFileSystemControlParcel.aidl b/aidl_api/libincremental_aidl/current/android/os/incremental/IncrementalFileSystemControlParcel.aidl deleted file mode 100644 index d777e34d1240..000000000000 --- a/aidl_api/libincremental_aidl/current/android/os/incremental/IncrementalFileSystemControlParcel.aidl +++ /dev/null @@ -1,24 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // -/////////////////////////////////////////////////////////////////////////////// - -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. -// -// You must not make a backward incompatible changes to the AIDL files built -// with the aidl_interface module type with versions property set. The module -// type is used to build AIDL files in a way that they can be used across -// independently updatable components of the system. If a device is shipped -// with such a backward incompatible change, it has a high risk of breaking -// later when a module using the interface is updated, e.g., Mainline modules. - -package android.os.incremental; -/* @hide */ -parcelable IncrementalFileSystemControlParcel { - ParcelFileDescriptor cmd; - ParcelFileDescriptor pendingReads; - ParcelFileDescriptor log; -} diff --git a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IIncrementalService.aidl b/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IIncrementalService.aidl deleted file mode 100644 index 5e1f013d627a..000000000000 --- a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IIncrementalService.aidl +++ /dev/null @@ -1,44 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // -/////////////////////////////////////////////////////////////////////////////// - -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. -// -// You must not make a backward incompatible changes to the AIDL files built -// with the aidl_interface module type with versions property set. The module -// type is used to build AIDL files in a way that they can be used across -// independently updatable components of the system. If a device is shipped -// with such a backward incompatible change, it has a high risk of breaking -// later when a module using the interface is updated, e.g., Mainline modules. - -package android.os.incremental; -/* @hide */ -interface IIncrementalService { - int openStorage(in @utf8InCpp String path); - int createStorage(in @utf8InCpp String path, in android.content.pm.DataLoaderParamsParcel params, in android.content.pm.IDataLoaderStatusListener listener, int createMode); - int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode); - int makeBindMount(int storageId, in @utf8InCpp String sourcePath, in @utf8InCpp String targetFullPath, int bindType); - int deleteBindMount(int storageId, in @utf8InCpp String targetFullPath); - int makeDirectory(int storageId, in @utf8InCpp String path); - int makeDirectories(int storageId, in @utf8InCpp String path); - int makeFile(int storageId, in @utf8InCpp String path, in android.os.incremental.IncrementalNewFileParams params); - int makeFileFromRange(int storageId, in @utf8InCpp String targetPath, in @utf8InCpp String sourcePath, long start, long end); - int makeLink(int sourceStorageId, in @utf8InCpp String sourcePath, int destStorageId, in @utf8InCpp String destPath); - int unlink(int storageId, in @utf8InCpp String path); - boolean isFileRangeLoaded(int storageId, in @utf8InCpp String path, long start, long end); - byte[] getMetadataByPath(int storageId, in @utf8InCpp String path); - byte[] getMetadataById(int storageId, in byte[] fileId); - boolean startLoading(int storageId); - void deleteStorage(int storageId); - boolean configureNativeBinaries(int storageId, in @utf8InCpp String apkFullPath, in @utf8InCpp String libDirRelativePath, in @utf8InCpp String abi); - const int CREATE_MODE_TEMPORARY_BIND = 1; - const int CREATE_MODE_PERMANENT_BIND = 2; - const int CREATE_MODE_CREATE = 4; - const int CREATE_MODE_OPEN_EXISTING = 8; - const int BIND_TEMPORARY = 0; - const int BIND_PERMANENT = 1; -} diff --git a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IncrementalNewFileParams.aidl b/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IncrementalNewFileParams.aidl deleted file mode 100644 index c73787721113..000000000000 --- a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IncrementalNewFileParams.aidl +++ /dev/null @@ -1,25 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // -/////////////////////////////////////////////////////////////////////////////// - -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. -// -// You must not make a backward incompatible changes to the AIDL files built -// with the aidl_interface module type with versions property set. The module -// type is used to build AIDL files in a way that they can be used across -// independently updatable components of the system. If a device is shipped -// with such a backward incompatible change, it has a high risk of breaking -// later when a module using the interface is updated, e.g., Mainline modules. - -package android.os.incremental; -/* @hide */ -parcelable IncrementalNewFileParams { - long size; - byte[] fileId; - byte[] metadata; - @nullable byte[] signature; -} diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index edf25ae36ec9..e533b7a7d6f3 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -499,20 +499,58 @@ public final class MediaParser { }) public @interface ParserName {} + /** Parser name returned by {@link #getParserName()} when no parser has been selected yet. */ public static final String PARSER_NAME_UNKNOWN = "android.media.mediaparser.UNKNOWN"; + /** + * Parser for the Matroska container format, as defined in the <a + * href="https://matroska.org/technical/specs/">spec</a>. + */ public static final String PARSER_NAME_MATROSKA = "android.media.mediaparser.MatroskaParser"; + /** + * Parser for fragmented files using the MP4 container format, as defined in ISO/IEC 14496-12. + */ public static final String PARSER_NAME_FMP4 = "android.media.mediaparser.FragmentedMp4Parser"; + /** + * Parser for non-fragmented files using the MP4 container format, as defined in ISO/IEC + * 14496-12. + */ public static final String PARSER_NAME_MP4 = "android.media.mediaparser.Mp4Parser"; + /** Parser for the MP3 container format, as defined in ISO/IEC 11172-3. */ public static final String PARSER_NAME_MP3 = "android.media.mediaparser.Mp3Parser"; + /** Parser for the ADTS container format, as defined in ISO/IEC 13818-7. */ public static final String PARSER_NAME_ADTS = "android.media.mediaparser.AdtsParser"; + /** + * Parser for the AC-3 container format, as defined in Digital Audio Compression Standard + * (AC-3). + */ public static final String PARSER_NAME_AC3 = "android.media.mediaparser.Ac3Parser"; + /** Parser for the TS container format, as defined in ISO/IEC 13818-1. */ public static final String PARSER_NAME_TS = "android.media.mediaparser.TsParser"; + /** + * Parser for the FLV container format, as defined in Adobe Flash Video File Format + * Specification. + */ public static final String PARSER_NAME_FLV = "android.media.mediaparser.FlvParser"; + /** Parser for the OGG container format, as defined in RFC 3533. */ public static final String PARSER_NAME_OGG = "android.media.mediaparser.OggParser"; + /** Parser for the PS container format, as defined in ISO/IEC 11172-1. */ public static final String PARSER_NAME_PS = "android.media.mediaparser.PsParser"; + /** + * Parser for the WAV container format, as defined in Multimedia Programming Interface and Data + * Specifications. + */ public static final String PARSER_NAME_WAV = "android.media.mediaparser.WavParser"; + /** Parser for the AMR container format, as defined in RFC 4867. */ public static final String PARSER_NAME_AMR = "android.media.mediaparser.AmrParser"; + /** + * Parser for the AC-4 container format, as defined by Dolby AC-4: Audio delivery for + * Next-Generation Entertainment Services. + */ public static final String PARSER_NAME_AC4 = "android.media.mediaparser.Ac4Parser"; + /** + * Parser for the FLAC container format, as defined in the <a + * href="https://xiph.org/flac/">spec</a>. + */ public static final String PARSER_NAME_FLAC = "android.media.mediaparser.FlacParser"; // MediaParser parameters. diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index 7480ec834596..6f29141f582c 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -146,7 +146,8 @@ java_library { visibility: [ "//frameworks/base", // Framework "//frameworks/base/apex/statsd", // statsd apex - "//frameworks/opt/net/wifi/service" // wifi service + "//frameworks/opt/net/wifi/service", // wifi service + "//packages/providers/MediaProvider", // MediaProvider apk ], } diff --git a/api/current.txt b/api/current.txt index 1c1d1427a4db..ed4d9eba4cf6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -30284,6 +30284,7 @@ package android.net { field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf field public static final int NET_CAPABILITY_RCS = 8; // 0x8 field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 + field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19 field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10 field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6 diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index a3214606c32f..20f9e76b4436 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -337,6 +337,7 @@ cc_test { "tests/external/StatsPullerManager_test.cpp", "tests/FieldValue_test.cpp", "tests/guardrail/StatsdStats_test.cpp", + "tests/HashableDimensionKey_test.cpp", "tests/indexed_priority_queue_test.cpp", "tests/log_event/LogEventQueue_test.cpp", "tests/LogEntryMatcher_test.cpp", diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index 967fd323e5a0..3536e5a5c962 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -16,6 +16,7 @@ #pragma once #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "annotations.h" namespace android { namespace os { @@ -357,6 +358,56 @@ struct Value { Value& operator=(const Value& that); }; +class Annotations { +public: + Annotations() {} + + // This enum stores where particular annotations can be found in the + // bitmask. Note that these pos do not correspond to annotation ids. + enum { + NESTED_POS = 0x0, + PRIMARY_POS = 0x1, + EXCLUSIVE_POS = 0x2 + }; + + inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); } + + inline void setPrimaryField(bool primary) { setBitmaskAtPos(PRIMARY_POS, primary); } + + inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); } + + inline void setResetState(int resetState) { mResetState = resetState; } + + // Default value = false + inline bool isNested() const { return getValueFromBitmask(NESTED_POS); } + + // Default value = false + inline bool isPrimaryField() const { return getValueFromBitmask(PRIMARY_POS); } + + // Default value = false + inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); } + + // If a reset state is not sent in the StatsEvent, returns -1. Note that a + // reset satate is only sent if and only if a reset should be triggered. + inline int getResetState() const { return mResetState; } + +private: + inline void setBitmaskAtPos(int pos, bool value) { + mBooleanBitmask &= ~(1 << pos); // clear + mBooleanBitmask |= (value << pos); // set + } + + inline bool getValueFromBitmask(int pos) const { + return (mBooleanBitmask >> pos) & 0x1; + } + + // This is a bitmask over all annotations stored in boolean form. Because + // there are only 3 booleans, just one byte is required. + uint8_t mBooleanBitmask = 0; + + int mResetState = -1; +}; + /** * Represents a log item, or a dimension item (They are essentially the same). */ @@ -384,6 +435,7 @@ struct FieldValue { Field mField; Value mValue; + Annotations mAnnotations; }; bool HasPositionANY(const FieldMatcher& matcher); diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 23d8f59e94ea..29249f4a6c55 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -230,6 +230,47 @@ void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metr } } +bool containsLinkedStateValues(const HashableDimensionKey& whatKey, + const HashableDimensionKey& primaryKey, + const vector<Metric2State>& stateLinks, const int32_t stateAtomId) { + if (whatKey.getValues().size() < primaryKey.getValues().size()) { + ALOGE("Contains linked values false: whatKey is too small"); + return false; + } + + for (const auto& primaryValue : primaryKey.getValues()) { + bool found = false; + for (const auto& whatValue : whatKey.getValues()) { + if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) && + primaryValue.mValue == whatValue.mValue) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; +} + +bool linked(const vector<Metric2State>& stateLinks, const int32_t stateAtomId, + const Field& stateField, const Field& metricField) { + for (auto stateLink : stateLinks) { + if (stateLink.stateAtomId != stateAtomId) { + continue; + } + + for (size_t i = 0; i < stateLink.stateFields.size(); i++) { + if (stateLink.stateFields[i].mMatcher == stateField && + stateLink.metricFields[i].mMatcher == metricField) { + return true; + } + } + } + return false; +} + bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) { if (s1.size() != s2.size()) { return s1.size() < s2.size(); diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h index a766bbae00d3..33a502497746 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -110,6 +110,10 @@ public: return mStateValuesKey; } + inline HashableDimensionKey* getMutableStateValuesKey() { + return &mStateValuesKey; + } + inline void setStateValuesKey(const HashableDimensionKey& key) { mStateValuesKey = key; } @@ -169,6 +173,32 @@ void getDimensionForCondition(const std::vector<FieldValue>& eventValues, void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link, HashableDimensionKey* statePrimaryKey); +/** + * Returns true if the primaryKey values are a subset of the whatKey values. + * The values from the primaryKey come from the state atom, so we need to + * check that a link exists between the state atom field and what atom field. + * + * Example: + * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}] + * statePrimaryKey = [Atom: 27, {uid: 1005}] + * Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid + * + * Example: + * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}] + * statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}] + * Returns false + */ +bool containsLinkedStateValues(const HashableDimensionKey& whatKey, + const HashableDimensionKey& primaryKey, + const std::vector<Metric2State>& stateLinks, + const int32_t stateAtomId); + +/** + * Returns true if there is a Metric2State link that links the stateField and + * the metricField (they are equal fields from different atoms). + */ +bool linked(const std::vector<Metric2State>& stateLinks, const int32_t stateAtomId, + const Field& stateField, const Field& metricField); } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 14585c3df870..97512ed7595c 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -334,6 +334,11 @@ private: FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); diff --git a/cmds/statsd/src/annotations.h b/cmds/statsd/src/annotations.h new file mode 100644 index 000000000000..1e9390e49ae1 --- /dev/null +++ b/cmds/statsd/src/annotations.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 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 + +namespace android { +namespace os { +namespace statsd { + +const uint8_t ANNOTATION_ID_IS_UID = 1; +const uint8_t ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2; +const uint8_t ANNOTATION_ID_STATE_OPTION = 3; +const uint8_t ANNOTATION_ID_RESET_STATE = 5; +const uint8_t ANNOTATION_ID_STATE_NESTED = 6; + +const int32_t STATE_OPTION_PRIMARY_FIELD = 1; +const int32_t STATE_OPTION_EXCLUSIVE_STATE = 2; +const int32_t STATE_OPTION_PRIMARY_FIELD_FIRST_UID = 3; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 6a4338f7550f..5cd00c35b05c 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -123,7 +123,8 @@ message Atom { BatteryLevelChanged battery_level_changed = 30 [(module) = "framework", (module) = "statsdtest"]; ChargingStateChanged charging_state_changed = 31 [(module) = "framework"]; - PluggedStateChanged plugged_state_changed = 32 [(module) = "framework"]; + PluggedStateChanged plugged_state_changed = 32 + [(module) = "framework", (module) = "statsdtest"]; InteractiveStateChanged interactive_state_changed = 33 [(module) = "framework"]; TouchEventReported touch_event_reported = 34; WakeupAlarmOccurred wakeup_alarm_occurred = 35 [(module) = "framework"]; @@ -5810,7 +5811,7 @@ message NotificationRemoteViews { */ message PackageNotificationPreferences { // Uid under which the package is installed. - optional int32 uid = 1; + optional int32 uid = 1 [(is_uid) = true]; // Notification importance, which specifies when and how a notification is displayed. // Specified under core/java/android/app/NotificationManager.java. optional int32 importance = 2; @@ -5827,7 +5828,7 @@ message PackageNotificationPreferences { */ message PackageNotificationChannelPreferences { // Uid under which the package is installed. - optional int32 uid = 1; + optional int32 uid = 1 [(is_uid) = true]; // Channel's ID. Should always be available. optional string channel_id = 2; // Channel's name. Should always be available. @@ -5850,7 +5851,7 @@ message PackageNotificationChannelPreferences { */ message PackageNotificationChannelGroupPreferences { // Uid under which the package is installed. - optional int32 uid = 1; + optional int32 uid = 1 [(is_uid) = true]; // Channel Group's ID. Should always be available. optional string group_id = 2; // Channel Group's name. Should always be available. diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index a3701a77f27a..79315eb17060 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -41,6 +41,40 @@ namespace android { namespace os { namespace statsd { +// Stores the puller as a wp to avoid holding a reference in case it is unregistered and +// pullAtomCallbackDied is never called. +struct PullAtomCallbackDeathCookie { + PullAtomCallbackDeathCookie(sp<StatsPullerManager> pullerManager, const PullerKey& pullerKey, + const wp<StatsPuller>& puller) + : mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) { + } + + sp<StatsPullerManager> mPullerManager; + PullerKey mPullerKey; + wp<StatsPuller> mPuller; +}; + +void StatsPullerManager::pullAtomCallbackDied(void* cookie) { + PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie); + sp<StatsPullerManager>& thiz = cookie_->mPullerManager; + const PullerKey& pullerKey = cookie_->mPullerKey; + wp<StatsPuller> puller = cookie_->mPuller; + + // Erase the mapping from the puller key to the puller if the mapping still exists. + // Note that we are removing the StatsPuller object, which internally holds the binder + // IPullAtomCallback. However, each new registration creates a new StatsPuller, so this works. + lock_guard<mutex> lock(thiz->mLock); + const auto& it = thiz->kAllPullAtomInfo.find(pullerKey); + if (it != thiz->kAllPullAtomInfo.end() && puller != nullptr && puller == it->second) { + StatsdStats::getInstance().notePullerCallbackRegistrationChanged(pullerKey.atomTag, + /*registered=*/false); + thiz->kAllPullAtomInfo.erase(pullerKey); + } + // The death recipient corresponding to this specific IPullAtomCallback can never + // be triggered again, so free up resources. + delete cookie_; +} + // Values smaller than this may require to update the alarm. const int64_t NO_ALARM_UPDATE = INT64_MAX; @@ -49,7 +83,8 @@ StatsPullerManager::StatsPullerManager() // TrainInfo. {{.atomTag = util::TRAIN_INFO, .uid = -1}, new TrainInfoPuller()}, }), - mNextPullTimeNs(NO_ALARM_UPDATE) { + mNextPullTimeNs(NO_ALARM_UPDATE), + mPullAtomCallbackDeathRecipient(AIBinder_DeathRecipient_new(pullAtomCallbackDied)) { } bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey, @@ -310,19 +345,28 @@ void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t a bool useUid) { std::lock_guard<std::mutex> _l(mLock); VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag); - // TODO(b/146439412): linkToDeath with the callback so that we can remove it - // and delete the puller. + StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true); int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs; int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs; - kAllPullAtomInfo[{.atomTag = atomTag, .uid = useUid ? uid : -1}] = new StatsCallbackPuller( - atomTag, callback, actualCoolDownNs, actualTimeoutNs, additiveFields); + + sp<StatsCallbackPuller> puller = new StatsCallbackPuller(atomTag, callback, actualCoolDownNs, + actualTimeoutNs, additiveFields); + PullerKey key = {.atomTag = atomTag, .uid = useUid ? uid : -1}; + AIBinder_linkToDeath(callback->asBinder().get(), mPullAtomCallbackDeathRecipient.get(), + new PullAtomCallbackDeathCookie(this, key, puller)); + kAllPullAtomInfo[key] = puller; } -void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) { +void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag, + bool useUids) { std::lock_guard<std::mutex> _l(mLock); - StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/false); - kAllPullAtomInfo.erase({.atomTag = atomTag}); + PullerKey key = {.atomTag = atomTag, .uid = useUids ? uid : -1}; + if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) { + StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, + /*registered=*/false); + kAllPullAtomInfo.erase(key); + } } } // namespace statsd diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h index c5824a8de17a..e679837952f7 100644 --- a/cmds/statsd/src/external/StatsPullerManager.h +++ b/cmds/statsd/src/external/StatsPullerManager.h @@ -120,7 +120,7 @@ public: const shared_ptr<IPullAtomCallback>& callback, bool useUid = false); - void UnregisterPullAtomCallback(const int uid, const int32_t atomTag); + void UnregisterPullAtomCallback(const int uid, const int32_t atomTag, bool useUids = false); std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo; @@ -164,6 +164,15 @@ private: int64_t mNextPullTimeNs; + // Death recipient that is triggered when the process holding the IPullAtomCallback has died. + ::ndk::ScopedAIBinder_DeathRecipient mPullAtomCallbackDeathRecipient; + + /** + * Death recipient callback that is called when a pull atom callback dies. + * The cookie is a pointer to a PullAtomCallbackDeathCookie. + */ + static void pullAtomCallbackDied(void* cookie); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents); FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm); FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 3b3d0b64bfb3..a6ae3899e4de 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -17,6 +17,7 @@ #define DEBUG false // STOPSHIP if true #include "logd/LogEvent.h" +#include "annotations.h" #include "stats_log_util.h" #include "statslog_statsd.h" @@ -447,6 +448,7 @@ void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8 void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + int firstUidInChainIndex = mValues.size(); int32_t numNodes = readNextValue<uint8_t>(); for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) { last[1] = (pos[1] == numNodes); @@ -461,26 +463,103 @@ void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); } - parseAnnotations(numAnnotations); + parseAnnotations(numAnnotations, firstUidInChainIndex); pos[1] = pos[2] = 1; last[1] = last[2] = false; } -// TODO(b/151109630): store annotation information within LogEvent -void LogEvent::parseAnnotations(uint8_t numAnnotations) { +void LogEvent::parseIsUidAnnotation(uint8_t annotationType) { + if (mValues.empty() || annotationType != BOOL_TYPE) { + mValid = false; + return; + } + + bool isUid = readNextValue<uint8_t>(); + if (isUid) mUidFieldIndex = mValues.size() - 1; +} + +void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) { + if (!mValues.empty() || annotationType != BOOL_TYPE) { + mValid = false; + return; + } + + mTruncateTimestamp = readNextValue<uint8_t>(); +} + +void LogEvent::parseStateOptionAnnotation(uint8_t annotationType, int firstUidInChainIndex) { + if (mValues.empty() || annotationType != INT32_TYPE) { + mValid = false; + return; + } + + int32_t stateOption = readNextValue<int32_t>(); + switch (stateOption) { + case STATE_OPTION_EXCLUSIVE_STATE: + mValues[mValues.size() - 1].mAnnotations.setExclusiveState(true); + break; + case STATE_OPTION_PRIMARY_FIELD: + mValues[mValues.size() - 1].mAnnotations.setPrimaryField(true); + break; + case STATE_OPTION_PRIMARY_FIELD_FIRST_UID: + if (firstUidInChainIndex == -1) { + mValid = false; + } else { + mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(true); + } + break; + default: + mValid = false; + } +} + +void LogEvent::parseResetStateAnnotation(uint8_t annotationType) { + if (mValues.empty() || annotationType != INT32_TYPE) { + mValid = false; + return; + } + + int32_t resetState = readNextValue<int32_t>(); + mValues[mValues.size() - 1].mAnnotations.setResetState(resetState); +} + +void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) { + if (mValues.empty() || annotationType != BOOL_TYPE) { + mValid = false; + return; + } + + bool nested = readNextValue<uint8_t>(); + mValues[mValues.size() - 1].mAnnotations.setNested(nested); +} + +// firstUidInChainIndex is a default parameter that is only needed when parsing +// annotations for attribution chains. +void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) { for (uint8_t i = 0; i < numAnnotations; i++) { - /*uint8_t annotationId = */ readNextValue<uint8_t>(); + uint8_t annotationId = readNextValue<uint8_t>(); uint8_t annotationType = readNextValue<uint8_t>(); - switch (annotationType) { - case BOOL_TYPE: - /*bool annotationValue = */ readNextValue<uint8_t>(); + + switch (annotationId) { + case ANNOTATION_ID_IS_UID: + parseIsUidAnnotation(annotationType); break; - case INT32_TYPE: - /*int32_t annotationValue =*/ readNextValue<int32_t>(); + case ANNOTATION_ID_TRUNCATE_TIMESTAMP: + parseTruncateTimestampAnnotation(annotationType); + break; + case ANNOTATION_ID_STATE_OPTION: + parseStateOptionAnnotation(annotationType, firstUidInChainIndex); + break; + case ANNOTATION_ID_RESET_STATE: + parseResetStateAnnotation(annotationType); + break; + case ANNOTATION_ID_STATE_NESTED: + parseStateNestedAnnotation(annotationType); break; default: mValid = false; + return; } } } @@ -509,8 +588,8 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { typeInfo = readNextValue<uint8_t>(); if (getTypeId(typeInfo) != INT32_TYPE) mValid = false; mTagId = readNextValue<int32_t>(); - parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations numElements--; + parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) { @@ -544,6 +623,7 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { break; case ATTRIBUTION_CHAIN_TYPE: parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + if (mAttributionChainIndex == -1) mAttributionChainIndex = pos[0]; break; default: mValid = false; diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 6537f13c4089..0a89be4ce335 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -206,6 +206,32 @@ public: return &mValues; } + // Default value = false + inline bool shouldTruncateTimestamp() { + return mTruncateTimestamp; + } + + // Returns the index of the uid field within the FieldValues vector if the + // uid exists. If there is no uid field, returns -1. + // + // If the index within the atom definition is desired, do the following: + // int vectorIndex = LogEvent.getUidFieldIndex(); + // if (vectorIndex != -1) { + // FieldValue& v = LogEvent.getValues()[vectorIndex]; + // int atomIndex = v.mField.getPosAtDepth(0); + // } + // Note that atomIndex is 1-indexed. + inline int getUidFieldIndex() { + return mUidFieldIndex; + } + + // Returns the index of (the first) attribution chain within the atom + // definition. Note that the value is 1-indexed. If there is no attribution + // chain, returns -1. + inline int getAttributionChainIndex() { + return mAttributionChainIndex; + } + inline LogEvent makeCopy() { return LogEvent(*this); } @@ -240,7 +266,13 @@ private: void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); - void parseAnnotations(uint8_t numAnnotations); + + void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1); + void parseIsUidAnnotation(uint8_t annotationType); + void parseTruncateTimestampAnnotation(uint8_t annotationType); + void parseStateOptionAnnotation(uint8_t annotationType, int firstUidInChainIndex); + void parseResetStateAnnotation(uint8_t annotationType); + void parseStateNestedAnnotation(uint8_t annotationType); /** * The below three variables are only valid during the execution of @@ -322,6 +354,11 @@ private: // The pid of the logging client (defaults to -1). int32_t mLogPid = -1; + + // Annotations + bool mTruncateTimestamp = false; + int mUidFieldIndex = -1; + int mAttributionChainIndex = -1; }; void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut); diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index e85b97514242..0de92f3d9f47 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -55,6 +55,7 @@ const int FIELD_ID_DATA = 1; const int FIELD_ID_DIMENSION_IN_WHAT = 1; const int FIELD_ID_BUCKET_INFO = 3; const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; +const int FIELD_ID_SLICE_BY_STATE = 6; // for DurationBucketInfo const int FIELD_ID_DURATION = 3; const int FIELD_ID_BUCKET_NUM = 4; @@ -115,6 +116,14 @@ DurationMetricProducer::DurationMetricProducer( } mUnSlicedPartCondition = ConditionState::kUnknown; + for (const auto& stateLink : metric.state_link()) { + Metric2State ms; + ms.stateAtomId = stateLink.state_atom_id(); + translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); + translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); + mMetric2StateLinks.push_back(ms); + } + mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions); if (mWizard != nullptr && mConditionTrackerIndex >= 0 && mMetric2ConditionLinks.size() == 1) { @@ -150,21 +159,49 @@ sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker( return anomalyTracker; } +void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, + const int32_t oldState, const int32_t newState) { + // Create a FieldValue object to hold the new state. + FieldValue value; + value.mValue.setInt(newState); + // Check if this metric has a StateMap. If so, map the new state value to + // the correct state group id. + mapStateValue(atomId, &value); + + flushIfNeededLocked(eventTimeNs); + + // Each duration tracker is mapped to a different whatKey (a set of values from the + // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the + // state change event are a subset of the tracker's whatKey field values. + // + // Ex. For a duration metric dimensioned on uid and tag: + // DurationTracker1 whatKey = uid: 1001, tag: 1 + // DurationTracker2 whatKey = uid: 1002, tag 1 + // + // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state + // change. + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) { + continue; + } + whatIt.second->onStateChanged(eventTimeNs, atomId, value); + } +} + unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( const MetricDimensionKey& eventKey) const { switch (mAggregationType) { case DurationMetric_AggregationType_SUM: return make_unique<OringDurationTracker>( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, - mTimeBaseNs, mBucketSizeNs, mConditionSliced, - mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, + mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); case DurationMetric_AggregationType_MAX_SPARSE: return make_unique<MaxDurationTracker>( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, - mTimeBaseNs, mBucketSizeNs, mConditionSliced, - mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, + mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); } } @@ -364,6 +401,13 @@ void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); } + // Then fill slice_by_state. + for (auto state : dimensionKey.getStateValuesKey().getValues()) { + uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SLICE_BY_STATE); + writeStateToProto(state, protoOutput); + protoOutput->end(stateToken); + } // Then fill bucket_info (DurationBucketInfo). for (const auto& bucket : pair.second) { uint64_t bucketInfoToken = protoOutput->start( @@ -460,7 +504,6 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey const ConditionKey& conditionKeys, bool condition, const LogEvent& event) { const auto& whatKey = eventKey.getDimensionKeyInWhat(); - auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey); if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { if (hitGuardRailLocked(eventKey)) { @@ -471,19 +514,18 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey auto it = mCurrentSlicedDurationTrackerMap.find(whatKey); if (mUseWhatDimensionAsInternalDimension) { - it->second->noteStart(whatKey, condition, - event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys); return; } if (mInternalDimensions.empty()) { - it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, - event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(), + conditionKeys); } else { HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY; filterValues(mInternalDimensions, event.getValues(), &dimensionKey); - it->second->noteStart( - dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(), + conditionKeys); } } @@ -519,6 +561,41 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat); } + // Stores atom id to primary key pairs for each state atom that the metric is + // sliced by. + std::map<int, HashableDimensionKey> statePrimaryKeys; + + // For states with primary fields, use MetricStateLinks to get the primary + // field values from the log event. These values will form a primary key + // that will be used to query StateTracker for the correct state value. + for (const auto& stateLink : mMetric2StateLinks) { + getDimensionForState(event.getValues(), stateLink, + &statePrimaryKeys[stateLink.stateAtomId]); + } + + // For each sliced state, query StateTracker for the state value using + // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY. + // + // Expected functionality: for any case where the MetricStateLinks are + // initialized incorrectly (ex. # of state links != # of primary fields, no + // links are provided for a state with primary fields, links are provided + // in the wrong order, etc.), StateTracker will simply return kStateUnknown + // when queried using an incorrect key. + HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY; + for (auto atomId : mSlicedStateAtoms) { + FieldValue value; + if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { + // found a primary key for this state, query using the key + queryStateValue(atomId, statePrimaryKeys[atomId], &value); + } else { + // if no MetricStateLinks exist for this state atom, + // query using the default dimension key (empty HashableDimensionKey) + queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); + } + mapStateValue(atomId, &value); + stateValuesKey.addValue(value); + } + // Handles Stop events. if (matcherIndex == mStopIndex) { if (mUseWhatDimensionAsInternalDimension) { @@ -559,8 +636,8 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, condition = condition && mIsActive; - handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey, - condition, event); + handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition, + event); } size_t DurationMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 06da0f64aedb..cc48f99add01 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -54,6 +54,10 @@ public: sp<AnomalyTracker> addAnomalyTracker(const Alert &alert, const sp<AlarmMonitor>& anomalyAlarmMonitor) override; + void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const int32_t oldState, + const int32_t newState) override; + protected: void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override; @@ -137,7 +141,7 @@ private: // Helper function to create a duration tracker given the metric aggregation type. std::unique_ptr<DurationTracker> createDurationTracker( - const MetricDimensionKey& eventKey) const; + const MetricDimensionKey& eventKey) const; // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index be754e29b5bd..2518d85eb6a1 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -120,12 +120,13 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo FieldValue value; if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { // found a primary key for this state, query using the key - getMappedStateValue(atomId, statePrimaryKeys[atomId], &value); + queryStateValue(atomId, statePrimaryKeys[atomId], &value); } else { // if no MetricStateLinks exist for this state atom, // query using the default dimension key (empty HashableDimensionKey) - getMappedStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); + queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); } + mapStateValue(atomId, &value); stateValuesKey.addValue(value); } @@ -264,15 +265,17 @@ void MetricProducer::writeActiveMetricToProtoOutputStream( } } -void MetricProducer::getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, - FieldValue* value) { +void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* value) { if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) { value->mValue = Value(StateTracker::kStateUnknown); value->mField.setTag(atomId); ALOGW("StateTracker not found for state atom %d", atomId); return; } +} +void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) { // check if there is a state map for this atom auto atomIt = mStateGroupMap.find(atomId); if (atomIt == mStateGroupMap.end()) { diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 4c4cd8940b24..4550e65b6438 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -187,7 +187,8 @@ public: }; void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, int oldState, int newState){}; + const HashableDimensionKey& primaryKey, const int32_t oldState, + const int32_t newState){}; // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp. // This method clears all the past buckets. @@ -379,11 +380,15 @@ protected: return (endNs - mTimeBaseNs) / mBucketSizeNs - 1; } - // Query StateManager for original state value. - // If no state map exists for this atom, return the original value. - // Otherwise, return the group_id mapped to the atom and original value. - void getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, - FieldValue* value); + // Query StateManager for original state value using the queryKey. + // The field and value are output. + void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* value); + + // If a state map exists for the given atom, replace the original state + // value with the group id mapped to the value. + // If no state map exists, keep the original state value. + void mapStateValue(const int32_t atomId, FieldValue* value); DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason); @@ -467,6 +472,11 @@ protected: FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 3fb9166bf9bf..1fd6572cc760 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -329,6 +329,11 @@ private: FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index afe93d445e1d..8d59d1362919 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -56,11 +56,19 @@ struct DurationBucket { int64_t mDuration; }; +struct DurationValues { + // Recorded duration for current partial bucket. + int64_t mDuration; + + // Sum of past partial bucket durations in current full bucket. + // Used for anomaly detection. + int64_t mDurationFullBucket; +}; + class DurationTracker { public: DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - bool nesting, + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers) @@ -73,7 +81,6 @@ public: mNested(nesting), mCurrentBucketStartTimeNs(currentBucketStartNs), mDuration(0), - mDurationFullBucket(0), mCurrentBucketNum(currentBucketNum), mStartTimeNs(startTimeNs), mConditionSliced(conditionSliced), @@ -82,8 +89,8 @@ public: virtual ~DurationTracker(){}; - virtual void noteStart(const HashableDimensionKey& key, bool condition, - const int64_t eventTime, const ConditionKey& conditionKey) = 0; + virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, + const ConditionKey& conditionKey) = 0; virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime, const bool stopAll) = 0; virtual void noteStopAll(const int64_t eventTime) = 0; @@ -91,6 +98,9 @@ public: virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0; virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0; + virtual void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) = 0; + // Flush stale buckets if needed, and return true if the tracker has no on-going duration // events, so that the owner can safely remove the tracker. virtual bool flushIfNeeded( @@ -109,9 +119,12 @@ public: // Dump internal states for debugging virtual void dumpStates(FILE* out, bool verbose) const = 0; - void setEventKey(const MetricDimensionKey& eventKey) { - mEventKey = eventKey; - } + virtual int64_t getCurrentStateKeyDuration() const = 0; + + virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0; + + // Replace old value with new value for the given state atom. + virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0; protected: int64_t getCurrentBucketEndTimeNs() const { @@ -140,10 +153,11 @@ protected: } } - void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) { + void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey, + const int64_t& bucketValue, const int64_t& bucketNum) { for (auto& anomalyTracker : mAnomalyTrackers) { if (anomalyTracker != nullptr) { - anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum); + anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum); } } } @@ -164,6 +178,10 @@ protected: return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; } + void setEventKey(const MetricDimensionKey& eventKey) { + mEventKey = eventKey; + } + // A reference to the DurationMetricProducer's config key. const ConfigKey& mConfigKey; @@ -183,7 +201,8 @@ protected: int64_t mDuration; // current recorded duration result (for partial bucket) - int64_t mDurationFullBucket; // Sum of past partial buckets in current full bucket. + // Recorded duration results for each state key in the current partial bucket. + std::unordered_map<HashableDimensionKey, DurationValues> mStateKeyDurationMap; int64_t mCurrentBucketNum; diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 2be5855e90e6..ee4e1672411f 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -26,15 +26,14 @@ namespace statsd { MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - bool nesting, + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, - currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, - conditionSliced, fullLink, anomalyTrackers) { + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, + anomalyTrackers) { } bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { @@ -91,7 +90,6 @@ void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool conditi } } - void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime, bool forceStop) { VLOG("MaxDuration: key %s stop", key.toString().c_str()); @@ -240,6 +238,11 @@ void MaxDurationTracker::onSlicedConditionMayChange(bool overallCondition, } } +void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) { + ALOGE("MaxDurationTracker does not handle sliced state changes."); +} + void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) { for (auto& pair : mInfos) { noteConditionChanged(pair.first, condition, timestamp); @@ -309,6 +312,20 @@ void MaxDurationTracker::dumpStates(FILE* out, bool verbose) const { fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); } +int64_t MaxDurationTracker::getCurrentStateKeyDuration() const { + ALOGE("MaxDurationTracker does not handle sliced state changes."); + return -1; +} + +int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const { + ALOGE("MaxDurationTracker does not handle sliced state changes."); + return -1; +} + +void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { + ALOGE("MaxDurationTracker does not handle sliced state changes."); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index efb8dc70afd1..2891c6e1138a 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -54,10 +54,19 @@ public: void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; void onConditionChanged(bool condition, const int64_t timestamp) override; + void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) override; + int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const int64_t currentTimestamp) const override; void dumpStates(FILE* out, bool verbose) const override; + int64_t getCurrentStateKeyDuration() const override; + + int64_t getCurrentStateKeyFullBucketDuration() const override; + + void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); + private: // Returns true if at least one of the mInfos is started. bool anyStarted(); diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 57f39656fdfe..19b2fe89989d 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -26,13 +26,12 @@ using std::pair; OringDurationTracker::OringDurationTracker( const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, - int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, - const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, - currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, - conditionSliced, fullLink, anomalyTrackers), + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, + int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, + bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, + anomalyTrackers), mStarted(), mPaused() { mLastStartTime = 0; @@ -90,10 +89,14 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const int64 mConditionKeyMap.erase(key); } if (mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); - VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime, - (long long)mDuration); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); + VLOG("record duration %lld, total duration %lld for state key %s", + (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); } } @@ -112,10 +115,14 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const int64 void OringDurationTracker::noteStopAll(const int64_t timestamp) { if (!mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime, - (long long)mDuration); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s", + (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } stopAnomalyAlarm(timestamp); @@ -146,21 +153,36 @@ bool OringDurationTracker::flushCurrentBucket( // Process the current bucket. if (mStarted.size() > 0) { - mDuration += (currentBucketEndTimeNs - mLastStartTime); + // Calculate the duration for the current state key. + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (currentBucketEndTimeNs - mLastStartTime); } - if (mDuration > 0) { - DurationBucket current_info; - current_info.mBucketStartNs = mCurrentBucketStartTimeNs; - current_info.mBucketEndNs = currentBucketEndTimeNs; - current_info.mDuration = mDuration; - (*output)[mEventKey].push_back(current_info); - mDurationFullBucket += mDuration; - VLOG(" duration: %lld", (long long)current_info.mDuration); - } - if (eventTimeNs > fullBucketEnd) { - // End of full bucket, can send to anomaly tracker now. - addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum); - mDurationFullBucket = 0; + // Store DurationBucket info for each whatKey, stateKey pair. + // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the + // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to + // store durations for each stateKey, so we need to flush the bucket by creating a + // DurationBucket for each stateKey. + for (auto& durationIt : mStateKeyDurationMap) { + if (durationIt.second.mDuration > 0) { + DurationBucket current_info; + current_info.mBucketStartNs = mCurrentBucketStartTimeNs; + current_info.mBucketEndNs = currentBucketEndTimeNs; + current_info.mDuration = durationIt.second.mDuration; + (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)] + .push_back(current_info); + + durationIt.second.mDurationFullBucket += durationIt.second.mDuration; + VLOG(" duration: %lld", (long long)current_info.mDuration); + } + + if (eventTimeNs > fullBucketEnd) { + // End of full bucket, can send to anomaly tracker now. + addPastBucketToAnomalyTrackers( + MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first), + getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum); + durationIt.second.mDurationFullBucket = 0; + } + durationIt.second.mDuration = 0; } if (mStarted.size() > 0) { @@ -169,20 +191,19 @@ bool OringDurationTracker::flushCurrentBucket( info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1); info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs; info.mDuration = mBucketSizeNs; + // Full duration buckets are attributed to the current stateKey. (*output)[mEventKey].push_back(info); // Safe to send these buckets to anomaly tracker since they must be full buckets. // If it's a partial bucket, numBucketsForward would be 0. - addPastBucketToAnomalyTrackers(info.mDuration, mCurrentBucketNum + i); + addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i); VLOG(" add filling bucket with duration %lld", (long long)info.mDuration); } } else { if (numBucketsForward >= 2) { - addPastBucketToAnomalyTrackers(0, mCurrentBucketNum + numBucketsForward - 1); + addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1); } } - mDuration = 0; - if (numBucketsForward > 0) { mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs; mCurrentBucketNum += numBucketsForward; @@ -229,10 +250,14 @@ void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, } if (mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime), - (long long)mDuration); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + VLOG("record duration %lld, total duration %lld for state key %s", + (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } } @@ -288,10 +313,13 @@ void OringDurationTracker::onConditionChanged(bool condition, const int64_t time } else { if (!mStarted.empty()) { VLOG("Condition false, all paused"); - mDuration += (timestamp - mLastStartTime); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); mPaused.insert(mStarted.begin(), mStarted.end()); mStarted.clear(); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } } if (mStarted.empty()) { @@ -299,6 +327,20 @@ void OringDurationTracker::onConditionChanged(bool condition, const int64_t time } } +void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) { + // If no keys are being tracked, update the current state key and return. + if (mStarted.empty()) { + updateCurrentStateKey(atomId, newState); + return; + } + // Add the current duration length to the previous state key and then update + // the last start time and current state key. + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime); + mLastStartTime = timestamp; + updateCurrentStateKey(atomId, newState); +} + int64_t OringDurationTracker::predictAnomalyTimestampNs( const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const { @@ -308,12 +350,13 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( // The timestamp of the current bucket end. const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs(); - // The past duration ns for the current bucket. - int64_t currentBucketPastNs = mDuration + mDurationFullBucket; + // The past duration ns for the current bucket of the current stateKey. + int64_t currentStateBucketPastNs = + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration(); // As we move into the future, old buckets get overwritten (so their old data is erased). // Sum of past durations. Will change as we overwrite old buckets. - int64_t pastNs = currentBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey); + int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey); // The refractory period end timestamp for dimension mEventKey. const int64_t refractoryPeriodEndNs = @@ -372,7 +415,7 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx); } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) { - pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs)); + pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs)); } } @@ -382,7 +425,34 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( void OringDurationTracker::dumpStates(FILE* out, bool verbose) const { fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size()); fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size()); - fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); + fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration()); +} + +int64_t OringDurationTracker::getCurrentStateKeyDuration() const { + auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); + if (it == mStateKeyDurationMap.end()) { + return 0; + } else { + return it->second.mDuration; + } +} + +int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const { + auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); + if (it == mStateKeyDurationMap.end()) { + return 0; + } else { + return it->second.mDurationFullBucket; + } +} + +void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { + HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey(); + for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) { + if (stateValuesKey->getValues()[i].mField.getTag() == atomId) { + stateValuesKey->mutableValue(i)->mValue = newState.mValue; + } + } } } // namespace statsd diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index f44e3275b83d..bd8017a7decd 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -28,10 +28,9 @@ class OringDurationTracker : public DurationTracker { public: OringDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, - int conditionIndex, - bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, - int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, - bool fullLink, + int conditionIndex, bool nesting, int64_t currentBucketStartNs, + int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, + bool conditionSliced, bool fullLink, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); OringDurationTracker(const OringDurationTracker& tracker) = default; @@ -45,6 +44,9 @@ public: void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; void onConditionChanged(bool condition, const int64_t timestamp) override; + void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) override; + bool flushCurrentBucket( const int64_t& eventTimeNs, std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override; @@ -56,6 +58,12 @@ public: const int64_t currentTimestamp) const override; void dumpStates(FILE* out, bool verbose) const override; + int64_t getCurrentStateKeyDuration() const override; + + int64_t getCurrentStateKeyFullBucketDuration() const override; + + void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); + private: // We don't need to keep track of individual durations. The information that's needed is: // 1) which keys are started. We record the first start time. diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 3810c486cf88..0d0788e05e0a 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -564,6 +564,33 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } } + std::vector<int> slicedStateAtoms; + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap; + if (metric.slice_by_state_size() > 0) { + if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) { + ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state"); + return false; + } + if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, + allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { + return false; + } + } else { + if (metric.state_link_size() > 0) { + ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state"); + return false; + } + } + + // Check that all metric state links are a subset of dimensions_in_what fields. + std::vector<Matcher> dimensionsInWhat; + translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat); + for (const auto& stateLink : metric.state_link()) { + if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) { + return false; + } + } + unordered_map<int, shared_ptr<Activation>> eventActivationMap; unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap; bool success = handleMetricActivation(config, metric.id(), metricIndex, @@ -575,7 +602,8 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t sp<MetricProducer> durationMetric = new DurationMetricProducer( key, metric, conditionIndex, trackerIndices[0], trackerIndices[1], trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs, - currentTimeNs, eventActivationMap, eventDeactivationMap); + currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms, + stateGroupMap); allMetricProducers.push_back(durationMetric); } diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index c45274e4a3de..ed98f50bcc48 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -103,12 +103,14 @@ message DurationBucketInfo { message DurationMetricData { optional DimensionsValue dimensions_in_what = 1; - optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + repeated StateValue slice_by_state = 6; repeated DurationBucketInfo bucket_info = 3; repeated DimensionsValue dimension_leaf_values_in_what = 4; + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; } diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 83d9484c77ba..c7407bd9af1e 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -227,8 +227,12 @@ message DurationMetric { optional int64 condition = 3; + repeated int64 slice_by_state = 9; + repeated MetricConditionLink links = 4; + repeated MetricStateLink state_link = 10; + enum AggregationType { SUM = 1; @@ -238,9 +242,9 @@ message DurationMetric { optional FieldMatcher dimensions_in_what = 6; - optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; - optional TimeUnit bucket = 7; + + optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; } message GaugeMetric { diff --git a/cmds/statsd/tests/HashableDimensionKey_test.cpp b/cmds/statsd/tests/HashableDimensionKey_test.cpp new file mode 100644 index 000000000000..29adcd08a7b8 --- /dev/null +++ b/cmds/statsd/tests/HashableDimensionKey_test.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 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 "src/HashableDimensionKey.h" + +#include <gtest/gtest.h> + +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "statsd_test_util.h" + +#ifdef __ANDROID__ + +using android::util::ProtoReader; + +namespace android { +namespace os { +namespace statsd { + +/** + * Test that #containsLinkedStateValues returns false when the whatKey is + * smaller than the primaryKey. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_WhatKeyTooSmall) { + std::vector<Metric2State> mMetric2StateLinks; + + int32_t uid1 = 1000; + HashableDimensionKey whatKey = DEFAULT_DIMENSION_KEY; + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, + UID_PROCESS_STATE_ATOM_ID)); +} + +/** + * Test that #containsLinkedStateValues returns false when the linked values + * are not equal. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_UnequalLinkedValues) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + FieldMatcher whatMatcher; + whatMatcher.set_field(util::OVERLAY_STATE_CHANGED); + FieldMatcher* child11 = whatMatcher.add_child(); + child11->set_field(1); + + FieldMatcher stateMatcher; + stateMatcher.set_field(stateAtomId); + FieldMatcher* child21 = stateMatcher.add_child(); + child21->set_field(1); + + std::vector<Metric2State> mMetric2StateLinks; + Metric2State ms; + ms.stateAtomId = stateAtomId; + translateFieldMatcher(whatMatcher, &ms.metricFields); + translateFieldMatcher(stateMatcher, &ms.stateFields); + mMetric2StateLinks.push_back(ms); + + int32_t uid1 = 1000; + int32_t uid2 = 1001; + HashableDimensionKey whatKey; + getOverlayKey(uid2, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +/** + * Test that #containsLinkedStateValues returns false when there is no link + * between the key values. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_MissingMetric2StateLinks) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + std::vector<Metric2State> mMetric2StateLinks; + + int32_t uid1 = 1000; + HashableDimensionKey whatKey; + getOverlayKey(uid1, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +/** + * Test that #containsLinkedStateValues returns true when the key values are + * linked and equal. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_AllConditionsMet) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + FieldMatcher whatMatcher; + whatMatcher.set_field(util::OVERLAY_STATE_CHANGED); + FieldMatcher* child11 = whatMatcher.add_child(); + child11->set_field(1); + + FieldMatcher stateMatcher; + stateMatcher.set_field(stateAtomId); + FieldMatcher* child21 = stateMatcher.add_child(); + child21->set_field(1); + + std::vector<Metric2State> mMetric2StateLinks; + Metric2State ms; + ms.stateAtomId = stateAtomId; + translateFieldMatcher(whatMatcher, &ms.metricFields); + translateFieldMatcher(stateMatcher, &ms.stateFields); + mMetric2StateLinks.push_back(ms); + + int32_t uid1 = 1000; + HashableDimensionKey whatKey; + getOverlayKey(uid1, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_TRUE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index 7458cbf9e9a1..41e21e4afb37 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "src/logd/LogEvent.h" #include <gtest/gtest.h> -#include <log/log_event_list.h> + #include "frameworks/base/cmds/statsd/src/atoms.pb.h" #include "frameworks/base/core/proto/android/stats/launcher/launcher.pb.h" -#include <stats_event.h> +#include "log/log_event_list.h" +#include "src/logd/LogEvent.h" +#include "stats_event.h" #ifdef __ANDROID__ @@ -243,6 +244,117 @@ TEST(LogEventTest, TestAttributionChain) { AStatsEvent_release(event); } +void createIntWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId, + bool annotationValue) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); + AStatsEvent_writeInt32(statsEvent, 10); + AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + EXPECT_TRUE(logEvent->parseBuffer(buf, size)); + + AStatsEvent_release(statsEvent); +} + +TEST(LogEventTest, TestAnnotationIdIsUid) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_IS_UID, true); + + const vector<FieldValue>& values = event.getValues(); + EXPECT_EQ(values.size(), 1); + EXPECT_EQ(event.getUidFieldIndex(), 0); +} + +TEST(LogEventTest, TestAnnotationIdStateNested) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_STATE_NESTED, true); + + const vector<FieldValue>& values = event.getValues(); + EXPECT_EQ(values.size(), 1); + EXPECT_TRUE(values[0].mAnnotations.isNested()); +} + +void createIntWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId, + int annotationValue) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); + AStatsEvent_writeInt32(statsEvent, 10); + AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + EXPECT_TRUE(logEvent->parseBuffer(buf, size)); + + AStatsEvent_release(statsEvent); +} + +TEST(LogEventTest, TestPrimaryFieldAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_STATE_OPTION, + STATE_OPTION_PRIMARY_FIELD); + + const vector<FieldValue>& values = event.getValues(); + EXPECT_EQ(values.size(), 1); + EXPECT_TRUE(values[0].mAnnotations.isPrimaryField()); +} + +TEST(LogEventTest, TestExclusiveStateAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_STATE_OPTION, + STATE_OPTION_EXCLUSIVE_STATE); + + const vector<FieldValue>& values = event.getValues(); + EXPECT_EQ(values.size(), 1); + EXPECT_TRUE(values[0].mAnnotations.isExclusiveState()); +} + +TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) { + // Event has 10 ints and then an attribution chain + int numInts = 10; + int firstUidInChainIndex = numInts; + string tag1 = "tag1"; + string tag2 = "tag2"; + uint32_t uids[] = {1001, 1002}; + const char* tags[] = {tag1.c_str(), tag2.c_str()}; + + // Construct AStatsEvent + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 100); + for (int i = 0; i < numInts; i++) { + AStatsEvent_writeInt32(statsEvent, 10); + } + AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2); + AStatsEvent_addInt32Annotation(statsEvent, ANNOTATION_ID_STATE_OPTION, + STATE_OPTION_PRIMARY_FIELD_FIRST_UID); + AStatsEvent_build(statsEvent); + + // Construct LogEvent + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + LogEvent logEvent(/*uid=*/0, /*pid=*/0); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + AStatsEvent_release(statsEvent); + + // Check annotation + const vector<FieldValue>& values = logEvent.getValues(); + EXPECT_EQ(values.size(), numInts + 4); + EXPECT_TRUE(values[firstUidInChainIndex].mAnnotations.isPrimaryField()); +} + +TEST(LogEventTest, TestResetStateAnnotation) { + int32_t resetState = 10; + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_RESET_STATE, resetState); + + const vector<FieldValue>& values = event.getValues(); + EXPECT_EQ(values.size(), 1); + EXPECT_EQ(values[0].mAnnotations.getResetState(), resetState); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp index ae2a0f50d6ca..2659944684e1 100644 --- a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp @@ -14,12 +14,13 @@ #include <gtest/gtest.h> +#include <vector> + #include "src/StatsLogProcessor.h" +#include "src/state/StateTracker.h" #include "src/stats_log_util.h" #include "tests/statsd_test_util.h" -#include <vector> - namespace android { namespace os { namespace statsd { @@ -101,7 +102,7 @@ TEST(DurationMetricE2eTest, TestOneBucket) { reports.reports(0).metrics(0).duration_metrics(); EXPECT_EQ(1, durationMetrics.data_size()); - auto data = durationMetrics.data(0); + DurationMetricData data = durationMetrics.data(0); EXPECT_EQ(1, data.bucket_info_size()); EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos()); EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -183,7 +184,7 @@ TEST(DurationMetricE2eTest, TestTwoBuckets) { reports.reports(0).metrics(0).duration_metrics(); EXPECT_EQ(1, durationMetrics.data_size()); - auto data = durationMetrics.data(0); + DurationMetricData data = durationMetrics.data(0); EXPECT_EQ(1, data.bucket_info_size()); auto bucketInfo = data.bucket_info(0); @@ -353,7 +354,7 @@ TEST(DurationMetricE2eTest, TestWithActivation) { reports.reports(0).metrics(0).duration_metrics(); EXPECT_EQ(1, durationMetrics.data_size()); - auto data = durationMetrics.data(0); + DurationMetricData data = durationMetrics.data(0); EXPECT_EQ(1, data.bucket_info_size()); auto bucketInfo = data.bucket_info(0); @@ -434,7 +435,7 @@ TEST(DurationMetricE2eTest, TestWithCondition) { EXPECT_EQ(1, reports.reports(0).metrics_size()); EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size()); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate bucket info. EXPECT_EQ(1, data.bucket_info_size()); @@ -533,7 +534,7 @@ TEST(DurationMetricE2eTest, TestWithSlicedCondition) { EXPECT_EQ(1, reports.reports(0).metrics_size()); EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size()); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, appUid); @@ -691,7 +692,7 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { EXPECT_EQ(1, reports.reports(0).metrics_size()); EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size()); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, appUid); @@ -709,6 +710,734 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - duration2StartNs, bucketInfo.duration_nanos()); } +TEST(DurationMetricE2eTest, TestWithSlicedState) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + auto screenState = CreateScreenState(); + *config.add_state() = screenState; + + // Create duration metric that slices by screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreen")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->add_slice_by_state(screenState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + ON OFF ON (BatterySaverMode) + | | | (ScreenIsOnEvent) + | | (ScreenIsOffEvent) + | (ScreenDozeEvent) + */ + // Initialize log events. + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 50 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 80 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 250 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 310 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:20 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 360 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); // 6:10 + EXPECT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size()); + + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(1); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(2); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = deviceUnpluggedPredicate; + + auto screenState = CreateScreenState(); + *config.add_state() = screenState; + + // Create duration metric that has a condition and slices by screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeOnBatterySliceScreen")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->set_condition(deviceUnpluggedPredicate.id()); + durationMetric->add_slice_by_state(screenState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 (minutes) + |---------------------------------------|------------------ + ON OFF ON (BatterySaverMode) + T F T (DeviceUnpluggedPredicate) + | | | (ScreenIsOnEvent) + | | | (ScreenIsOffEvent) + | (ScreenDozeEvent) + */ + // Initialize log events. + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 60 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 1:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 80 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:30 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 2:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 145 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:35 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 170 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 3:00 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 180 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // 3:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 200 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:30 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 230 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 4:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 260 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 4:30 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary. + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 320 * NS_PER_SEC)); // 5:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 380 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 6:30 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 410 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + EXPECT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size()); + + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(2); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(60 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(1); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithSlicedStateMapped) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + auto screenStateWithMap = CreateScreenStateWithOnOffMap(); + *config.add_state() = screenStateWithMap; + + // Create duration metric that slices by mapped screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreenMapped")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->add_slice_by_state(screenStateWithMap.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + ON OFF ON (BatterySaverMode) + ---------------------------------------------------------SCREEN_OFF events + | | (ScreenStateOffEvent = 1) + | (ScreenStateDozeEvent = 3) + | (ScreenStateDozeSuspendEvent = 4) + ---------------------------------------------------------SCREEN_ON events + | | | (ScreenStateOnEvent = 2) + | (ScreenStateVrEvent = 5) + | (ScreenStateOnSuspendEvent = 6) + */ + // Initialize log events. + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 70 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:20 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 100 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:50 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 170 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_VR)); // 3:00 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 250 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary 5:10. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 320 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 390 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 430 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND)); // 7:20 + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 490 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + EXPECT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + EXPECT_EQ(2, reports.reports(0).metrics(0).duration_metrics().data_size()); + + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(StringToId("SCREEN_ON"), data.slice_by_state(0).group_id()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(130 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(1); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(StringToId("SCREEN_OFF"), data.slice_by_state(0).group_id()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(80 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *config.add_predicate() = holdingWakelockPredicate; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create duration metric that slices by uid process state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationHoldingWakelockSliceUidProcessState")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->add_slice_by_state(uidProcessState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // The state has only one primary field (uid). + auto stateLink = durationMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // This config is rejected because the dimension in what fields are not a superset of the sliced + // state primary fields. + EXPECT_EQ(processor->mMetricsManagers.size(), 0); +} + +TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *config.add_predicate() = holdingWakelockPredicate; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create duration metric that slices by uid process state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationPartialWakelockPerTagUidSliceProcessState")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->add_slice_by_state(uidProcessState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // The metric is dimensioning by first uid of attribution node and tag. + *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */}); + // The state has only one primary field (uid). + auto stateLink = durationMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID); + EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Initialize log events. + int appUid1 = 1001; + int appUid2 = 1002; + std::vector<int> attributionUids1 = {appUid1}; + std::vector<string> attributionTags1 = {"App1"}; + + std::vector<int> attributionUids2 = {appUid2}; + std::vector<string> attributionTags2 = {"App2"}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, appUid1, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:20 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock1")); // 0:30 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock2")); // 0:35 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock1")); // 0:40 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock2")); // 0:45 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 50 * NS_PER_SEC, appUid2, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:00 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 60 * NS_PER_SEC, appUid1, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:10 + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 100 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock1")); // 1:50 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, appUid2, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 2:10 + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 200 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock2")); // 3:30 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 320 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + EXPECT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + EXPECT_EQ(9, reports.reports(0).metrics(0).duration_metrics().data_size()); + + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock2"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(1); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock2"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(2); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock1"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(3); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock1"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(240 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(4); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock1"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(5); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE, + data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(180 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(6); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(7); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock1"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(8); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp index 100220b730d7..d2f0f57e0f54 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -62,9 +62,8 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey()); // Event starts again. This would not change anything as it already starts. @@ -97,9 +96,8 @@ TEST(MaxDurationTrackerTest, TestStopAll) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey()); @@ -132,9 +130,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); // The event starts. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); @@ -172,9 +169,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); // 2 starts tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); @@ -218,9 +214,8 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true, - false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + 0, bucketStartTimeNs, bucketSizeNs, true, false, {}); EXPECT_TRUE(tracker.mAnomalyTrackers.empty()); tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1); @@ -267,9 +262,9 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - true, false, {anomalyTracker}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1); sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second; @@ -326,9 +321,9 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - true, false, {anomalyTracker}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1); tracker.noteConditionChanged(key1, true, conditionStarts1); @@ -408,9 +403,9 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - true, false, {anomalyTracker}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1); tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2); diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp index 1cd7bdbf7bb0..39d3919dffd0 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -61,9 +61,9 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -92,9 +92,8 @@ TEST(OringDurationTrackerTest, TestDurationNested) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -124,9 +123,8 @@ TEST(OringDurationTrackerTest, TestStopAll) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -154,9 +152,8 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -198,9 +195,9 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + true, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -237,9 +234,9 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + true, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); // condition to false; record duration 5n @@ -275,9 +272,8 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1); @@ -316,9 +312,9 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); // Nothing in the past bucket. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); @@ -422,9 +418,8 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, - wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, + 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {anomalyTracker}); int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC; @@ -481,15 +476,15 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, false, {anomalyTracker}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); EXPECT_TRUE(tracker.mStarted.empty()); - EXPECT_EQ(10LL, tracker.mDuration); // 10ns + EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration); // 10ns EXPECT_EQ(0u, tracker.mStarted.size()); @@ -530,11 +525,11 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs, - bucketSizeNs, false, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, + bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false, + false, {anomalyTracker}); - tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1 + tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1 EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); @@ -544,13 +539,13 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { EXPECT_EQ(0u, anomalyTracker->mAlarms.size()); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again + tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2 + tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2 EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp index a0e00954531f..a5b8e1c50c33 100644 --- a/cmds/statsd/tests/state/StateTracker_test.cpp +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -105,63 +105,6 @@ std::unique_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::stri } // END: build event functions. -// START: get primary key functions -void getUidProcessKey(int uid, HashableDimensionKey* key) { - int pos1[] = {1, 0, 0}; - Field field1(27 /* atom id */, pos1, 0 /* depth */); - Value value1((int32_t)uid); - - key->addValue(FieldValue(field1, value1)); -} - -void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) { - int pos1[] = {1, 0, 0}; - int pos2[] = {2, 0, 0}; - - Field field1(59 /* atom id */, pos1, 0 /* depth */); - Field field2(59 /* atom id */, pos2, 0 /* depth */); - - Value value1((int32_t)uid); - Value value2(packageName); - - key->addValue(FieldValue(field1, value1)); - key->addValue(FieldValue(field2, value2)); -} - -void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) { - int pos1[] = {1, 1, 1}; - int pos3[] = {2, 0, 0}; - int pos4[] = {3, 0, 0}; - - Field field1(10 /* atom id */, pos1, 2 /* depth */); - - Field field3(10 /* atom id */, pos3, 0 /* depth */); - Field field4(10 /* atom id */, pos4, 0 /* depth */); - - Value value1((int32_t)uid); - Value value3((int32_t)1 /*partial*/); - Value value4(tag); - - key->addValue(FieldValue(field1, value1)); - key->addValue(FieldValue(field3, value3)); - key->addValue(FieldValue(field4, value4)); -} - -void getPartialWakelockKey(int uid, HashableDimensionKey* key) { - int pos1[] = {1, 1, 1}; - int pos3[] = {2, 0, 0}; - - Field field1(10 /* atom id */, pos1, 2 /* depth */); - Field field3(10 /* atom id */, pos3, 0 /* depth */); - - Value value1((int32_t)uid); - Value value3((int32_t)1 /*partial*/); - - key->addValue(FieldValue(field1, value1)); - key->addValue(FieldValue(field3, value3)); -} -// END: get primary key functions - TEST(StateListenerTest, TestStateListenerWeakPointer) { sp<TestStateListener> listener = new TestStateListener(); wp<TestStateListener> wListener = listener; diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 8c8836b94f56..2f81c2ded8b0 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -135,6 +135,27 @@ AtomMatcher CreateBatterySaverModeStopAtomMatcher() { "BatterySaverModeStop", BatterySaverModeStateChanged::OFF); } +AtomMatcher CreateBatteryStateChangedAtomMatcher(const string& name, + BatteryPluggedStateEnum 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(util::PLUGGED_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 CreateBatteryStateNoneMatcher() { + return CreateBatteryStateChangedAtomMatcher("BatteryPluggedNone", + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); +} + +AtomMatcher CreateBatteryStateUsbMatcher() { + return CreateBatteryStateChangedAtomMatcher("BatteryPluggedUsb", + BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); +} AtomMatcher CreateScreenStateChangedAtomMatcher( const string& name, android::view::DisplayStateEnum state) { @@ -234,6 +255,14 @@ Predicate CreateBatterySaverModePredicate() { return predicate; } +Predicate CreateDeviceUnpluggedPredicate() { + Predicate predicate; + predicate.set_id(StringToId("DeviceUnplugged")); + predicate.mutable_simple_predicate()->set_start(StringToId("BatteryPluggedNone")); + predicate.mutable_simple_predicate()->set_stop(StringToId("BatteryPluggedUsb")); + return predicate; +} + Predicate CreateScreenIsOnPredicate() { Predicate predicate; predicate.set_id(StringToId("ScreenIsOn")); @@ -410,6 +439,74 @@ FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) return dimensions; } +FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, + const std::vector<Position>& positions, + const std::vector<int>& fields) { + FieldMatcher dimensions = CreateAttributionUidDimensions(atomId, positions); + + for (const int field : fields) { + dimensions.add_child()->set_field(field); + } + return dimensions; +} + +// START: get primary key functions +void getUidProcessKey(int uid, HashableDimensionKey* key) { + int pos1[] = {1, 0, 0}; + Field field1(27 /* atom id */, pos1, 0 /* depth */); + Value value1((int32_t)uid); + + key->addValue(FieldValue(field1, value1)); +} + +void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) { + int pos1[] = {1, 0, 0}; + int pos2[] = {2, 0, 0}; + + Field field1(59 /* atom id */, pos1, 0 /* depth */); + Field field2(59 /* atom id */, pos2, 0 /* depth */); + + Value value1((int32_t)uid); + Value value2(packageName); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field2, value2)); +} + +void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + int pos4[] = {3, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + + Field field3(10 /* atom id */, pos3, 0 /* depth */); + Field field4(10 /* atom id */, pos4, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + Value value4(tag); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); + key->addValue(FieldValue(field4, value4)); +} + +void getPartialWakelockKey(int uid, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + Field field3(10 /* atom id */, pos3, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); +} +// END: get primary key functions + shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, int32_t value2) { AStatsEvent* statsEvent = AStatsEvent_obtain(); @@ -595,6 +692,23 @@ std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs) { return logEvent; } +std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + logEvent->parseBuffer(buf, size); + AStatsEvent_release(statsEvent); + return logEvent; +} + std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED); @@ -964,6 +1078,22 @@ int64_t StringToId(const string& str) { return static_cast<int64_t>(std::hash<std::string>()(str)); } +void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId, + const int uid, const string& tag) { + EXPECT_EQ(value.field(), atomId); + EXPECT_EQ(value.value_tuple().dimensions_value_size(), 2); + // Attribution field. + EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); + // Uid field. + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(), + uid); + // Tag field. + EXPECT_EQ(value.value_tuple().dimensions_value(1).field(), 3); + EXPECT_EQ(value.value_tuple().dimensions_value(1).value_str(), tag); +} + void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) { EXPECT_EQ(value.field(), atomId); EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1); diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index 7c017554d511..715ba2b73169 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -68,6 +68,12 @@ AtomMatcher CreateBatterySaverModeStartAtomMatcher(); // Create AtomMatcher proto for stopping battery save mode. AtomMatcher CreateBatterySaverModeStopAtomMatcher(); +// Create AtomMatcher proto for battery state none mode. +AtomMatcher CreateBatteryStateNoneMatcher(); + +// Create AtomMatcher proto for battery state usb mode. +AtomMatcher CreateBatteryStateUsbMatcher(); + // Create AtomMatcher proto for process state changed. AtomMatcher CreateUidProcessStateChangedAtomMatcher(); @@ -110,6 +116,9 @@ Predicate CreateScheduledJobPredicate(); // Create Predicate proto for battery saver mode. Predicate CreateBatterySaverModePredicate(); +// Create Predicate proto for device unplogged mode. +Predicate CreateDeviceUnpluggedPredicate(); + // Create Predicate proto for holding wakelock. Predicate CreateHoldingWakelockPredicate(); @@ -164,6 +173,22 @@ FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, FieldMatcher CreateAttributionUidDimensions(const int atomId, const std::vector<Position>& positions); +FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, + const std::vector<Position>& positions, + const std::vector<int>& fields); + +// START: get primary key functions +// These functions take in atom field information and create FieldValues which are stored in the +// given HashableDimensionKey. +void getUidProcessKey(int uid, HashableDimensionKey* key); + +void getOverlayKey(int uid, string packageName, HashableDimensionKey* key); + +void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key); + +void getPartialWakelockKey(int uid, HashableDimensionKey* key); +// END: get primary key functions + shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, int32_t value2); @@ -213,6 +238,9 @@ 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 when battery state changes. +std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state); + // Create log event for app moving to background. std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid); @@ -277,6 +305,8 @@ void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events); int64_t StringToId(const string& str); +void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId, + const int uid, const string& tag); void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid); void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid); void ValidateAttributionUidAndTagDimension( diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 2531c899ee78..b6d519ae5d2b 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -73,8 +73,8 @@ import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Singleton; import android.util.Size; -import android.window.WindowContainerToken; import android.view.Surface; +import android.window.WindowContainerToken; import com.android.internal.app.LocalePicker; import com.android.internal.app.procstats.ProcessStats; @@ -3632,7 +3632,8 @@ public class ActivityManager { * Set custom state data for this process. It will be included in the record of * {@link ApplicationExitInfo} on the death of the current calling process; the new process * of the app can retrieve this state data by calling - * {@link ApplicationExitInfo#getProcessStateSummary} on the record returned by + * {@link android.app.ApplicationExitInfo#getProcessStateSummary() + * ApplicationExitInfo.getProcessStateSummary()} on the record returned by * {@link #getHistoricalProcessExitReasons}. * * <p> This would be useful for the calling app to save its stateful data: if it's @@ -3657,7 +3658,7 @@ public class ActivityManager { } } - /* + /** * @return Whether or not the low memory kill will be reported in * {@link #getHistoricalProcessExitReasons}. * diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index 0ecc003a33bd..cfe0aff05d4a 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -90,7 +90,8 @@ public final class ApplicationExitInfo implements Parcelable { * {@link #REASON_SIGNALED} and {@link #getStatus} will return * the value {@link android.system.OsConstants#SIGKILL}. * - * Application should use {@link ActivityManager#isLowMemoryKillReportSupported} to check + * Application should use {@link android.app.ActivityManager#isLowMemoryKillReportSupported() + * ActivityManager.isLowMemoryKillReportSupported()} to check * if the device supports reporting {@link #REASON_LOW_MEMORY} or not. * </p> */ @@ -523,7 +524,7 @@ public final class ApplicationExitInfo implements Parcelable { return mReason; } - /* + /** * The exit status argument of exit() if the application calls it, or the signal * number if the application is signaled. */ @@ -538,7 +539,7 @@ public final class ApplicationExitInfo implements Parcelable { return mImportance; } - /* + /** * Last proportional set size of the memory that the process had used in kB. * * <p class="note">Note: This is the value from last sampling on the process, @@ -562,7 +563,7 @@ public final class ApplicationExitInfo implements Parcelable { /** * The timestamp of the process's death, in milliseconds since the epoch, - * as returned by {@link System#currentTimeMillis System.currentTimeMillis()}. + * as returned by {@link java.lang.System#currentTimeMillis() System.currentTimeMillis()}. */ public @CurrentTimeMillisLong long getTimestamp() { return mTimestamp; @@ -586,8 +587,9 @@ public final class ApplicationExitInfo implements Parcelable { } /** - * Return the state data set by calling {@link ActivityManager#setProcessStateSummary} - * from the process before its death. + * Return the state data set by calling + * {@link android.app.ActivityManager#setProcessStateSummary(byte[]) + * ActivityManager.setProcessStateSummary(byte[])} from the process before its death. * * @return The process-customized data * @see ActivityManager#setProcessStateSummary(byte[]) diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index a1ec27b3e9f7..f883b60b534f 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1610,7 +1610,10 @@ public class ApplicationPackageManager extends PackageManager { @Override public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) { - Drawable badge = getProfileIconForDensity(user, + if (!hasUserBadge(user.getIdentifier())) { + return null; + } + Drawable badge = getDrawableForDensity( getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density); if (badge != null) { badge.setTint(getUserBadgeColor(user)); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 054e5e0945f3..91a857225324 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -186,6 +186,7 @@ import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyRegistryManager; import android.util.ArrayMap; import android.util.Log; +import android.util.Slog; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.WindowManager; @@ -222,6 +223,9 @@ import java.util.Objects; public final class SystemServiceRegistry { private static final String TAG = "SystemServiceRegistry"; + /** @hide */ + public static boolean sEnableServiceNotFoundWtf = false; + // Service registry information. // This information is never changed once static initialization has completed. private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES = @@ -1364,8 +1368,30 @@ public final class SystemServiceRegistry { * @hide */ public static Object getSystemService(ContextImpl ctx, String name) { - ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); - return fetcher != null ? fetcher.getService(ctx) : null; + if (name == null) { + return null; + } + final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); + if (fetcher == null) { + if (sEnableServiceNotFoundWtf) { + Slog.wtf(TAG, "Unknown manager requested: " + name); + } + return null; + } + + final Object ret = fetcher.getService(ctx); + if (sEnableServiceNotFoundWtf && ret == null) { + // Some services do return null in certain situations, so don't do WTF for them. + switch (name) { + case Context.CONTENT_CAPTURE_MANAGER_SERVICE: + case Context.APP_PREDICTION_SERVICE: + case Context.INCREMENTAL_SERVICE: + return null; + } + Slog.wtf(TAG, "Manager wrapper not available: " + name); + return null; + } + return ret; } /** @@ -1373,7 +1399,15 @@ public final class SystemServiceRegistry { * @hide */ public static String getSystemServiceName(Class<?> serviceClass) { - return SYSTEM_SERVICE_NAMES.get(serviceClass); + if (serviceClass == null) { + return null; + } + final String serviceName = SYSTEM_SERVICE_NAMES.get(serviceClass); + if (sEnableServiceNotFoundWtf && serviceName == null) { + // This should be a caller bug. + Slog.wtf(TAG, "Unknown manager requested: " + serviceClass.getCanonicalName()); + } + return serviceName; } /** @@ -1683,7 +1717,9 @@ public final class SystemServiceRegistry { try { cache.wait(); } catch (InterruptedException e) { - Log.w(TAG, "getService() interrupted"); + // This shouldn't normally happen, but if someone interrupts the + // thread, it will. + Slog.wtf(TAG, "getService() interrupted"); Thread.currentThread().interrupt(); return null; } diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 7b45b725f5b6..ab868604dfde 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -44,7 +44,7 @@ "name": "CtsWindowManagerDeviceTestCases", "options": [ { - "include-filter": "android.server.wm.ToastTest" + "include-filter": "android.server.wm.ToastWindowTest" } ], "file_patterns": ["INotificationManager\\.aidl"] diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 5bad055810cc..8bebafff37f0 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -306,6 +306,35 @@ interface IPackageManager { void setHomeActivity(in ComponentName className, int userId); /** + * Overrides the label and icon of the component specified by the component name. The component + * must belong to the calling app. + * + * These changes will be reset on the next boot and whenever the package is updated. + * + * Only the app defined as com.android.internal.R.config_overrideComponentUiPackage is allowed + * to call this. + * + * @param componentName The component name to override the label/icon of. + * @param nonLocalizedLabel The label to be displayed. + * @param icon The icon to be displayed. + * @param userId The user id. + */ + void overrideLabelAndIcon(in ComponentName componentName, String nonLocalizedLabel, + int icon, int userId); + + /** + * Restores the label and icon of the activity specified by the component name if either has + * been overridden. The component must belong to the calling app. + * + * Only the app defined as com.android.internal.R.config_overrideComponentUiPackage is allowed + * to call this. + * + * @param componentName The component name. + * @param userId The user id. + */ + void restoreLabelAndIcon(in ComponentName componentName, int userId); + + /** * As per {@link android.content.pm.PackageManager#setComponentEnabledSetting}. */ @UnsupportedAppUsage diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index 61b1553e28a8..327d1b8beeb1 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -27,18 +27,24 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.pm.parsing.component.ParsedMainComponent; import android.os.BaseBundle; import android.os.Debug; import android.os.PersistableBundle; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; +import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.CollectionUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -84,6 +90,9 @@ public class PackageUserState { private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths private String[] cachedOverlayPaths; + @Nullable + private ArrayMap<ComponentName, Pair<String, Integer>> componentLabelIconOverrideMap; + @UnsupportedAppUsage public PackageUserState() { installed = true; @@ -123,6 +132,9 @@ public class PackageUserState { sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths); } harmfulAppWarning = o.harmfulAppWarning; + if (o.componentLabelIconOverrideMap != null) { + this.componentLabelIconOverrideMap = new ArrayMap<>(o.componentLabelIconOverrideMap); + } } public String[] getOverlayPaths() { @@ -147,6 +159,65 @@ public class PackageUserState { } /** + * Overrides the non-localized label and icon of a component. + * + * @return true if the label or icon was changed. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean overrideLabelAndIcon(@NonNull ComponentName component, + @Nullable String nonLocalizedLabel, @Nullable Integer icon) { + String existingLabel = null; + Integer existingIcon = null; + + if (componentLabelIconOverrideMap != null) { + Pair<String, Integer> pair = componentLabelIconOverrideMap.get(component); + if (pair != null) { + existingLabel = pair.first; + existingIcon = pair.second; + } + } + + boolean changed = !TextUtils.equals(existingLabel, nonLocalizedLabel) + || !Objects.equals(existingIcon, icon); + + if (changed) { + if (nonLocalizedLabel == null && icon == null) { + componentLabelIconOverrideMap.remove(component); + if (componentLabelIconOverrideMap.isEmpty()) { + componentLabelIconOverrideMap = null; + } + } else { + if (componentLabelIconOverrideMap == null) { + componentLabelIconOverrideMap = new ArrayMap<>(1); + } + + componentLabelIconOverrideMap.put(component, Pair.create(nonLocalizedLabel, icon)); + } + } + + return changed; + } + + /** + * Clears all values previously set by {@link #overrideLabelAndIcon(ComponentName, + * String, Integer)}. + * + * This is done when the package is updated as the components and resource IDs may have changed. + */ + public void resetOverrideComponentLabelIcon() { + componentLabelIconOverrideMap = null; + } + + @Nullable + public Pair<String, Integer> getOverrideLabelIconForComponent(ComponentName componentName) { + if (ArrayUtils.isEmpty(componentLabelIconOverrideMap)) { + return null; + } + + return componentLabelIconOverrideMap.get(componentName); + } + + /** * Test if this package is installed. */ public boolean isAvailable(int flags) { diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index 435c70ae999b..eee91ce173dc 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -109,4 +109,8 @@ public abstract class ShortcutServiceInternal { */ public abstract String getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage, @NonNull String packageName, @NonNull String shortcutId, int userId); + + public abstract boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage, + @NonNull String packageName, @NonNull String shortcutId, int userId, + @NonNull IntentFilter filter); } diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index 894ad5584922..be1817d09155 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -322,7 +322,12 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { private String className; private int compatibleWidthLimitDp; private int descriptionRes; - private boolean enabled; + + // Usually there's code to set this to true during parsing, but it's possible to install an APK + // targeting <R that doesn't contain an <application> tag. That code would be skipped and never + // assign this, so initialize this to true for those cases. + private boolean enabled = true; + private boolean crossProfile; private int fullBackupContent; private int iconRes; diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index b52b437b4557..a298c856a0fb 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -18,6 +18,7 @@ package android.inputmethodservice; import android.annotation.BinderThread; import android.annotation.MainThread; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; @@ -37,6 +38,7 @@ import android.view.inputmethod.InputMethodSession; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; +import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; import com.android.internal.view.IInlineSuggestionsRequestCallback; @@ -52,7 +54,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; /** * Implements the internal IInputMethod interface to convert incoming calls @@ -90,12 +91,13 @@ class IInputMethodWrapper extends IInputMethod.Stub * * <p>This field must be set and cleared only from the binder thread(s), where the system * guarantees that {@link #bindInput(InputBinding)}, - * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and + * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean, boolean)}, and * {@link #unbindInput()} are called with the same order as the original calls * in {@link com.android.server.inputmethod.InputMethodManagerService}. * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p> */ - AtomicBoolean mIsUnbindIssued = null; + @Nullable + CancellationGroup mCancellationGroup = null; // NOTE: we should have a cache of these. static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback { @@ -187,11 +189,11 @@ class IInputMethodWrapper extends IInputMethod.Stub final IBinder startInputToken = (IBinder) args.arg1; final IInputContext inputContext = (IInputContext) args.arg2; final EditorInfo info = (EditorInfo) args.arg3; - final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4; + final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; SomeArgs moreArgs = (SomeArgs) args.arg5; final InputConnection ic = inputContext != null ? new InputConnectionWrapper( - mTarget, inputContext, moreArgs.argi3, isUnbindIssued) + mTarget, inputContext, moreArgs.argi3, cancellationGroup) : null; info.makeCompatible(mTargetSdkVersion); inputMethod.dispatchStartInputWithToken( @@ -295,15 +297,15 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void bindInput(InputBinding binding) { - if (mIsUnbindIssued != null) { + if (mCancellationGroup != null) { Log.e(TAG, "bindInput must be paired with unbindInput."); } - mIsUnbindIssued = new AtomicBoolean(); + mCancellationGroup = new CancellationGroup(); // This IInputContext is guaranteed to implement all the methods. final int missingMethodFlags = 0; InputConnection ic = new InputConnectionWrapper(mTarget, IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags, - mIsUnbindIssued); + mCancellationGroup); InputBinding nu = new InputBinding(ic, binding); mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu)); } @@ -311,10 +313,10 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void unbindInput() { - if (mIsUnbindIssued != null) { + if (mCancellationGroup != null) { // Signal the flag then forget it. - mIsUnbindIssued.set(true); - mIsUnbindIssued = null; + mCancellationGroup.cancelAll(); + mCancellationGroup = null; } else { Log.e(TAG, "unbindInput must be paired with bindInput."); } @@ -326,16 +328,16 @@ class IInputMethodWrapper extends IInputMethod.Stub public void startInput(IBinder startInputToken, IInputContext inputContext, @InputConnectionInspector.MissingMethodFlags final int missingMethods, EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) { - if (mIsUnbindIssued == null) { + if (mCancellationGroup == null) { Log.e(TAG, "startInput must be called after bindInput."); - mIsUnbindIssued = new AtomicBoolean(); + mCancellationGroup = new CancellationGroup(); } SomeArgs args = SomeArgs.obtain(); args.argi1 = restarting ? 1 : 0; args.argi2 = shouldPreRenderIme ? 1 : 0; args.argi3 = missingMethods; - mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO( - DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args)); + mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(DO_START_INPUT, startInputToken, + inputContext, attribute, mCancellationGroup, args)); } @BinderThread diff --git a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java index ef138a0c2217..dbb669be1402 100644 --- a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java +++ b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java @@ -39,6 +39,7 @@ import android.view.inputmethod.ExtractedText; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IMultiClientInputMethodSession; +import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.os.SomeArgs; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.view.IInputContext; @@ -46,7 +47,6 @@ import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputConnectionWrapper; import java.lang.ref.WeakReference; -import java.util.concurrent.atomic.AtomicBoolean; /** * Re-dispatches all the incoming per-client events to the specified {@link Looper} thread. @@ -80,19 +80,19 @@ final class MultiClientInputMethodClientCallbackAdaptor { @Nullable InputEventReceiver mInputEventReceiver; - private final AtomicBoolean mFinished = new AtomicBoolean(false); + private final CancellationGroup mCancellationGroup = new CancellationGroup(); IInputMethodSession.Stub createIInputMethodSession() { synchronized (mSessionLock) { return new InputMethodSessionImpl( - mSessionLock, mCallbackImpl, mHandler, mFinished); + mSessionLock, mCallbackImpl, mHandler, mCancellationGroup); } } IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() { synchronized (mSessionLock) { return new MultiClientInputMethodSessionImpl( - mSessionLock, mCallbackImpl, mHandler, mFinished); + mSessionLock, mCallbackImpl, mHandler, mCancellationGroup); } } @@ -105,7 +105,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { mHandler = new Handler(looper, null, true); mReadChannel = readChannel; mInputEventReceiver = new ImeInputEventReceiver(mReadChannel, mHandler.getLooper(), - mFinished, mDispatcherState, mCallbackImpl.mOriginalCallback); + mCancellationGroup, mDispatcherState, mCallbackImpl.mOriginalCallback); } } @@ -139,16 +139,17 @@ final class MultiClientInputMethodClientCallbackAdaptor { } private static final class ImeInputEventReceiver extends InputEventReceiver { - private final AtomicBoolean mFinished; + private final CancellationGroup mCancellationGroupOnFinishSession; private final KeyEvent.DispatcherState mDispatcherState; private final MultiClientInputMethodServiceDelegate.ClientCallback mClientCallback; private final KeyEventCallbackAdaptor mKeyEventCallbackAdaptor; - ImeInputEventReceiver(InputChannel readChannel, Looper looper, AtomicBoolean finished, + ImeInputEventReceiver(InputChannel readChannel, Looper looper, + CancellationGroup cancellationGroupOnFinishSession, KeyEvent.DispatcherState dispatcherState, MultiClientInputMethodServiceDelegate.ClientCallback callback) { super(readChannel, looper); - mFinished = finished; + mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; mDispatcherState = dispatcherState; mClientCallback = callback; mKeyEventCallbackAdaptor = new KeyEventCallbackAdaptor(callback); @@ -156,7 +157,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { @Override public void onInputEvent(InputEvent event) { - if (mFinished.get()) { + if (mCancellationGroupOnFinishSession.isCanceled()) { // The session has been finished. finishInputEvent(event, false); return; @@ -187,14 +188,14 @@ final class MultiClientInputMethodClientCallbackAdaptor { private CallbackImpl mCallbackImpl; @GuardedBy("mSessionLock") private Handler mHandler; - private final AtomicBoolean mSessionFinished; + private final CancellationGroup mCancellationGroupOnFinishSession; InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler, - AtomicBoolean sessionFinished) { + CancellationGroup cancellationGroupOnFinishSession) { mSessionLock = lock; mCallbackImpl = callback; mHandler = handler; - mSessionFinished = sessionFinished; + mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; } @Override @@ -272,7 +273,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { if (mCallbackImpl == null || mHandler == null) { return; } - mSessionFinished.set(true); + mCancellationGroupOnFinishSession.cancelAll(); mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::finishSession, mCallbackImpl)); mCallbackImpl = null; @@ -311,14 +312,14 @@ final class MultiClientInputMethodClientCallbackAdaptor { private CallbackImpl mCallbackImpl; @GuardedBy("mSessionLock") private Handler mHandler; - private final AtomicBoolean mSessionFinished; + private final CancellationGroup mCancellationGroupOnFinishSession; MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback, - Handler handler, AtomicBoolean sessionFinished) { + Handler handler, CancellationGroup cancellationGroupOnFinishSession) { mSessionLock = lock; mCallbackImpl = callback; mHandler = handler; - mSessionFinished = sessionFinished; + mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; } @Override @@ -335,7 +336,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { new WeakReference<>(null); args.arg1 = (inputContext == null) ? null : new InputConnectionWrapper(fakeIMS, inputContext, missingMethods, - mSessionFinished); + mCancellationGroupOnFinishSession); args.arg2 = editorInfo; args.argi1 = controlFlags; args.argi2 = softInputMode; diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 9ff7ebee6da4..73c6b3daf2ec 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -169,6 +169,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_OEM_PAID, NET_CAPABILITY_MCX, NET_CAPABILITY_PARTIAL_CONNECTIVITY, + NET_CAPABILITY_TEMPORARILY_NOT_METERED, }) public @interface NetCapability { } @@ -336,8 +337,16 @@ public final class NetworkCapabilities implements Parcelable { @SystemApi public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; + /** + * This capability will be set for networks that are generally metered, but are currently + * unmetered, e.g., because the user is in a particular area. This capability can be changed at + * any time. When it is removed, applications are responsible for stopping any data transfer + * that should not occur on a metered network. + */ + public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PARTIAL_CONNECTIVITY; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_TEMPORARILY_NOT_METERED; /** * Network capabilities that are expected to be mutable, i.e., can change while a particular @@ -353,7 +362,8 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_FOREGROUND) | (1 << NET_CAPABILITY_NOT_CONGESTED) | (1 << NET_CAPABILITY_NOT_SUSPENDED) - | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY); + | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY + | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)); /** * Network capabilities that are not allowed in NetworkRequests. This exists because the @@ -424,6 +434,7 @@ public final class NetworkCapabilities implements Parcelable { */ private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES = (1 << NET_CAPABILITY_NOT_METERED) + | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED) | (1 << NET_CAPABILITY_NOT_RESTRICTED) | (1 << NET_CAPABILITY_NOT_VPN) | (1 << NET_CAPABILITY_NOT_ROAMING) @@ -1864,6 +1875,7 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_OEM_PAID: return "OEM_PAID"; case NET_CAPABILITY_MCX: return "MCX"; case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY"; + case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED"; default: return Integer.toString(capability); } } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index cdc00195c169..b8e1aa88c3a3 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -32,6 +32,7 @@ import android.app.PropertyInvalidatedCache; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.service.dreams.Sandman; +import android.sysprop.InitProperties; import android.util.ArrayMap; import android.util.Log; import android.util.proto.ProtoOutputStream; @@ -1487,7 +1488,7 @@ public final class PowerManager { */ // TODO(b/138605180): add link to documentation once it's ready. public boolean isRebootingUserspaceSupported() { - return SystemProperties.getBoolean("ro.init.userspace_reboot.is_supported", false); + return InitProperties.is_userspace_reboot_supported().orElse(false); } /** diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl index 2dbaea860e2a..d8308c7c3362 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -38,6 +38,13 @@ interface IIncrementalService { int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode); /** + * Changes storage params. Returns 0 on success, and -errno on failure. + * Use enableReadLogs to switch pages read logs reporting on and off. + * Returns 0 on success, and - errno on failure: permission check or remount. + */ + int setStorageParams(int storageId, boolean enableReadLogs); + + /** * Bind-mounts a path under a storage to a full path. Can be permanent or temporary. */ const int BIND_TEMPORARY = 0; diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 35518db32829..5f01408944e8 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -19,11 +19,13 @@ package android.os.incremental; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; import android.content.pm.DataLoaderParams; import android.content.pm.IDataLoaderStatusListener; import android.os.RemoteException; +import android.system.ErrnoException; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -319,6 +321,23 @@ public final class IncrementalManager { return nativeUnsafeGetFileSignature(path); } + /** + * Sets storage parameters. + * + * @param enableReadLogs - enables or disables read logs. Caller has to have a permission. + */ + @RequiresPermission(android.Manifest.permission.LOADER_USAGE_STATS) + public void setStorageParams(int storageId, boolean enableReadLogs) throws ErrnoException { + try { + int res = mService.setStorageParams(storageId, enableReadLogs); + if (res < 0) { + throw new ErrnoException("setStorageParams", -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + /* Native methods */ private static native boolean nativeIsEnabled(); private static native boolean nativeIsIncrementalPath(@NonNull String path); diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 327bca268a7b..2e00c0c9d2a4 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -1274,6 +1274,8 @@ public abstract class DocumentsProvider extends ContentProvider { out.putParcelable(DocumentsContract.EXTRA_RESULT, path); } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) { + enforceReadPermissionInner(documentUri, getCallingPackage(), + getCallingAttributionTag(), null); return getDocumentMetadata(documentId); } else { throw new UnsupportedOperationException("Method not supported " + method); diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java index c047dc0d07c7..05877a59368a 100644 --- a/core/java/android/service/dataloader/DataLoaderService.java +++ b/core/java/android/service/dataloader/DataLoaderService.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.content.pm.DataLoaderParams; import android.content.pm.DataLoaderParamsParcel; @@ -31,6 +32,8 @@ import android.content.pm.InstallationFile; import android.content.pm.InstallationFileParcel; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.os.incremental.IncrementalManager; +import android.system.ErrnoException; import android.util.ExceptionUtils; import android.util.Slog; @@ -208,6 +211,25 @@ public abstract class DataLoaderService extends Service { private final long mNativeInstance; } + /* Used by native FileSystemConnector. */ + private boolean setStorageParams(int storageId, boolean enableReadLogs) { + IncrementalManager incrementalManager = (IncrementalManager) getSystemService( + Context.INCREMENTAL_SERVICE); + if (incrementalManager == null) { + Slog.e(TAG, "Failed to obtain incrementalManager: " + storageId); + return false; + } + try { + // This has to be done directly in incrementalManager as the storage + // might be missing still. + incrementalManager.setStorageParams(storageId, enableReadLogs); + } catch (ErrnoException e) { + Slog.e(TAG, "Failed to set params for storage: " + storageId, e); + return false; + } + return true; + } + /* Native methods */ private native boolean nativeCreateDataLoader(int storageId, @NonNull FileSystemControlParcel control, diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 4b3afbaada64..e8d345997022 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -335,7 +335,17 @@ public class TimeUtils { /** @hide Just for debugging; not internationalized. */ public static String formatUptime(long time) { - final long diff = time - SystemClock.uptimeMillis(); + return formatTime(time, SystemClock.uptimeMillis()); + } + + /** @hide Just for debugging; not internationalized. */ + public static String formatRealtime(long time) { + return formatTime(time, SystemClock.elapsedRealtime()); + } + + /** @hide Just for debugging; not internationalized. */ + public static String formatTime(long time, long referenceTime) { + long diff = time - referenceTime; if (diff > 0) { return time + " (in " + diff + " ms)"; } diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 35286ba007c4..2461e96c5b49 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -16,7 +16,6 @@ package android.view; -import static android.view.InsetsController.ANIMATION_TYPE_USER; import static android.view.InsetsController.AnimationType; import static android.view.InsetsState.ITYPE_IME; @@ -104,13 +103,9 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { void hide(boolean animationFinished, @AnimationType int animationType) { super.hide(); - if (!animationFinished) { - if (animationType == ANIMATION_TYPE_USER) { - // if controlWindowInsetsAnimation is hiding keyboard. - notifyHidden(); - } - } else { + if (animationFinished) { // remove IME surface as IME has finished hide animation. + notifyHidden(); removeSurface(); } } diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 4227f78564a7..74c186948b2f 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -40,8 +40,10 @@ public interface InsetsAnimationControlCallbacks { /** * Schedule the apply by posting the animation callback. + * + * @param runner The runner that requested applying insets */ - void scheduleApplyChangeInsets(); + void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner); /** * Finish the final steps after the animation. diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index ef6a9b212e92..05abc6032116 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -156,7 +156,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mPendingFraction = sanitize(fraction); mPendingInsets = sanitize(insets); mPendingAlpha = sanitize(alpha); - mController.scheduleApplyChangeInsets(); + mController.scheduleApplyChangeInsets(this); } @VisibleForTesting diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 40ffa7ef48bf..9dfdd0604e6b 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -54,7 +54,7 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro } @Override - public void scheduleApplyChangeInsets() { + public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) { mControl.applyChangeInsets(mState); } @@ -91,7 +91,7 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro @AnimationType int animationType, Handler mainThreadHandler) { mMainThreadHandler = mainThreadHandler; mOuterCallbacks = controller; - mControl = new InsetsAnimationControlImpl(copyControls(controls), frame, state, listener, + mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types, mCallbacks, durationMs, interpolator, animationType); InsetsAnimationThread.getHandler().post(() -> listener.onReady(mControl, types)); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 8eb9b5f6ef23..72ddaca27a76 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1076,8 +1076,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting @Override - public void scheduleApplyChangeInsets() { - if (mStartingAnimation) { + public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) { + if (mStartingAnimation || runner.getAnimationType() == ANIMATION_TYPE_USER) { mAnimCallback.run(); mAnimCallbackScheduled = false; return; diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 83ff8fa42f49..f74221dc3e3a 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -117,7 +117,7 @@ public class InsetsSourceConsumer { } } if (lastControl != null) { - lastControl.release(mController::releaseSurfaceControlFromRt); + lastControl.release(SurfaceControl::release); } } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index c5154662928e..9896aa4fe214 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -172,6 +172,10 @@ public class InsetsState implements Parcelable { for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { InsetsSource source = mSources.get(type); if (source == null) { + int index = indexOf(toPublicType(type)); + if (typeInsetsMap[index] == null) { + typeInsetsMap[index] = Insets.NONE; + } continue; } diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 0359f3b4fde7..a9f3e04036b5 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -60,11 +60,7 @@ public class NotificationHeaderView extends ViewGroup { private NotificationExpandButton mExpandButton; private CachingIconView mIcon; private View mProfileBadge; - private View mOverlayIcon; - private View mCameraIcon; - private View mMicIcon; private View mAppOps; - private View mAudiblyAlertedIcon; private boolean mExpanded; private boolean mShowExpandButtonAtEnd; private boolean mShowWorkBadgeAtEnd; @@ -121,11 +117,7 @@ public class NotificationHeaderView extends ViewGroup { mExpandButton = findViewById(com.android.internal.R.id.expand_button); mIcon = findViewById(com.android.internal.R.id.icon); mProfileBadge = findViewById(com.android.internal.R.id.profile_badge); - mCameraIcon = findViewById(com.android.internal.R.id.camera); - mMicIcon = findViewById(com.android.internal.R.id.mic); - mOverlayIcon = findViewById(com.android.internal.R.id.overlay); mAppOps = findViewById(com.android.internal.R.id.app_ops); - mAudiblyAlertedIcon = findViewById(com.android.internal.R.id.alerted_icon); } @Override @@ -300,10 +292,6 @@ public class NotificationHeaderView extends ViewGroup { */ public void setAppOpsOnClickListener(OnClickListener l) { mAppOpsListener = l; - mAppOps.setOnClickListener(mAppOpsListener); - mCameraIcon.setOnClickListener(mAppOpsListener); - mMicIcon.setOnClickListener(mAppOpsListener); - mOverlayIcon.setOnClickListener(mAppOpsListener); updateTouchListener(); } @@ -328,27 +316,6 @@ public class NotificationHeaderView extends ViewGroup { updateExpandButton(); } - /** - * Shows or hides 'app op in use' icons based on app usage. - */ - public void showAppOpsIcons(ArraySet<Integer> appOps) { - if (mOverlayIcon == null || mCameraIcon == null || mMicIcon == null || appOps == null) { - return; - } - - mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) - ? View.VISIBLE : View.GONE); - mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA) - ? View.VISIBLE : View.GONE); - mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO) - ? View.VISIBLE : View.GONE); - } - - /** Updates icon visibility based on the noisiness of the notification. */ - public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) { - mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE); - } - private void updateExpandButton() { int drawableId; int contentDescriptionId; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 51304dcfe8cb..35f955f7e78b 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -130,6 +130,7 @@ import android.view.View.MeasureSpec; import android.view.Window.OnContentApplyWindowInsetsListener; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; +import android.view.WindowManager.LayoutParams; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -1412,6 +1413,10 @@ public final class ViewRootImpl implements ViewParent, | (oldSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST); } + if ((changes & LayoutParams.SOFT_INPUT_MODE_CHANGED) != 0) { + requestFitSystemWindows(); + } + mWindowAttributesChanged = true; scheduleTraversals(); } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 9b2a6cbce48f..ca3dd04fd756 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -818,12 +818,13 @@ public final class WindowInsets { * @return A modified copy of this WindowInsets * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED} - * instead to stop dispatching insets. + * instead to stop dispatching insets. On {@link android.os.Build.VERSION_CODES#R R}, this + * method has no effect. */ @Deprecated @NonNull public WindowInsets consumeStableInsets() { - return consumeSystemWindowInsets(); + return this; } /** @@ -835,12 +836,24 @@ public final class WindowInsets { @Override public String toString() { - return "WindowInsets{systemWindowInsets=" + getSystemWindowInsets() - + " stableInsets=" + getStableInsets() - + " sysGestureInsets=" + getSystemGestureInsets() - + (mDisplayCutout != null ? " cutout=" + mDisplayCutout : "") - + (isRound() ? " round" : "") - + "}"; + StringBuilder result = new StringBuilder("WindowInsets{\n "); + for (int i = 0; i < SIZE; i++) { + Insets insets = mTypeInsetsMap[i]; + Insets maxInsets = mTypeMaxInsetsMap[i]; + boolean visible = mTypeVisibilityMap[i]; + if (!Insets.NONE.equals(insets) || !Insets.NONE.equals(maxInsets) || visible) { + result.append(Type.toString(1 << i)).append("=").append(insets) + .append(" max=").append(maxInsets) + .append(" vis=").append(visible) + .append("\n "); + } + } + + result.append(mDisplayCutout != null ? "cutout=" + mDisplayCutout : ""); + result.append("\n "); + result.append(isRound() ? "round" : ""); + result.append("}"); + return result.toString(); } /** @@ -1309,6 +1322,32 @@ public final class WindowInsets { } } + static String toString(@InsetsType int type) { + switch (type) { + case STATUS_BARS: + return "statusBars"; + case NAVIGATION_BARS: + return "navigationBars"; + case CAPTION_BAR: + return "captionBar"; + case IME: + return "ime"; + case SYSTEM_GESTURES: + return "systemGestures"; + case MANDATORY_SYSTEM_GESTURES: + return "mandatorySystemGestures"; + case TAPPABLE_ELEMENT: + return "tappableElement"; + case DISPLAY_CUTOUT: + return "displayCutout"; + case WINDOW_DECOR: + return "windowDecor"; + default: + throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST," + + " type=" + type); + } + } + private Type() { } diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java index 6aa288ddc638..af896fca932a 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -30,6 +30,7 @@ import android.widget.inline.InlinePresentationSpec; import com.android.internal.util.DataClass; import com.android.internal.util.Preconditions; +import com.android.internal.widget.InlinePresentationStyleUtils; import java.util.ArrayList; import java.util.List; @@ -113,6 +114,10 @@ public final class InlineSuggestionsRequest implements Parcelable { mHostInputToken = hostInputToken; } + private boolean extrasEquals(@NonNull Bundle extras) { + return InlinePresentationStyleUtils.bundleEquals(mExtras, extras); + } + // TODO(b/149609075): remove once IBinder parcelling is natively supported private void parcelHostInputToken(@NonNull Parcel parcel, int flags) { parcel.writeStrongBinder(mHostInputToken); @@ -331,7 +336,7 @@ public final class InlineSuggestionsRequest implements Parcelable { && java.util.Objects.equals(mInlinePresentationSpecs, that.mInlinePresentationSpecs) && java.util.Objects.equals(mHostPackageName, that.mHostPackageName) && java.util.Objects.equals(mSupportedLocales, that.mSupportedLocales) - && java.util.Objects.equals(mExtras, that.mExtras) + && extrasEquals(that.mExtras) && java.util.Objects.equals(mHostInputToken, that.mHostInputToken) && mHostDisplayId == that.mHostDisplayId; } @@ -603,10 +608,10 @@ public final class InlineSuggestionsRequest implements Parcelable { } @DataClass.Generated( - time = 1585691147541L, + time = 1585768018462L, codegenVersion = "1.0.15", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java", - inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs()\npublic void setHostInputToken(android.os.IBinder)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsRequest.Builder addPresentationSpecs(android.view.inline.InlinePresentationSpec)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs()\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsRequest.Builder addPresentationSpecs(android.view.inline.InlinePresentationSpec)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING index d0b8dbcf8339..f089f48368a0 100644 --- a/core/java/android/widget/TEST_MAPPING +++ b/core/java/android/widget/TEST_MAPPING @@ -13,7 +13,7 @@ "name": "CtsWindowManagerDeviceTestCases", "options": [ { - "include-filter": "android.server.wm.ToastTest" + "include-filter": "android.server.wm.ToastWindowTest" } ], "file_patterns": ["Toast\\.java"] diff --git a/core/java/android/widget/inline/InlinePresentationSpec.java b/core/java/android/widget/inline/InlinePresentationSpec.java index 00eb3ce271a1..5635857154de 100644 --- a/core/java/android/widget/inline/InlinePresentationSpec.java +++ b/core/java/android/widget/inline/InlinePresentationSpec.java @@ -23,6 +23,7 @@ import android.os.Parcelable; import android.util.Size; import com.android.internal.util.DataClass; +import com.android.internal.widget.InlinePresentationStyleUtils; /** * This class represents the presentation specification by which an inline suggestion @@ -52,6 +53,10 @@ public final class InlinePresentationSpec implements Parcelable { return Bundle.EMPTY; } + private boolean styleEquals(@NonNull Bundle style) { + return InlinePresentationStyleUtils.bundleEquals(mStyle, style); + } + /** @hide */ @DataClass.Suppress({"setMaxSize", "setMinSize"}) abstract static class BaseBuilder { @@ -143,7 +148,7 @@ public final class InlinePresentationSpec implements Parcelable { return true && java.util.Objects.equals(mMinSize, that.mMinSize) && java.util.Objects.equals(mMaxSize, that.mMaxSize) - && java.util.Objects.equals(mStyle, that.mStyle); + && styleEquals(that.mStyle); } @Override @@ -280,10 +285,10 @@ public final class InlinePresentationSpec implements Parcelable { } @DataClass.Generated( - time = 1585605466300L, + time = 1585768046898L, codegenVersion = "1.0.15", sourceFile = "frameworks/base/core/java/android/widget/inline/InlinePresentationSpec.java", - inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.NonNull android.os.Bundle mStyle\nprivate static @android.annotation.NonNull android.os.Bundle defaultStyle()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.NonNull android.os.Bundle mStyle\nprivate static @android.annotation.NonNull android.os.Bundle defaultStyle()\nprivate boolean styleEquals(android.os.Bundle)\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/window/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java index 0b2dcd8b3a92..1afbfeb96fc3 100644 --- a/core/java/android/window/VirtualDisplayTaskEmbedder.java +++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java @@ -248,7 +248,6 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { options = super.prepareActivityOptions(options); options.setLaunchDisplayId(getDisplayId()); options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - options.setTaskAlwaysOnTop(true); return options; } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index dca682e4ee29..10c742817d0a 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -124,6 +124,7 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.content.PackageMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.GridLayoutManager; import com.android.internal.widget.RecyclerView; import com.android.internal.widget.ResolverDrawerLayout; @@ -178,7 +179,7 @@ public class ChooserActivity extends ResolverActivity implements private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true; // TODO(b/123088566) Share these in a better way. private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share"; - public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share"; + public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share"; private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20; public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter"; @@ -194,6 +195,14 @@ public class ChooserActivity extends ResolverActivity implements public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2; public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3; + public static final int SELECTION_TYPE_SERVICE = 1; + public static final int SELECTION_TYPE_APP = 2; + public static final int SELECTION_TYPE_STANDARD = 3; + public static final int SELECTION_TYPE_COPY = 4; + + // statsd logger wrapper + protected ChooserActivityLogger mChooserActivityLogger; + private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true; @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = { @@ -270,9 +279,9 @@ public class ChooserActivity extends ResolverActivity implements // Starting at 1 since 0 is considered "undefined" for some of the database transformations // of tron logs. - private static final int CONTENT_PREVIEW_IMAGE = 1; - private static final int CONTENT_PREVIEW_FILE = 2; - private static final int CONTENT_PREVIEW_TEXT = 3; + protected static final int CONTENT_PREVIEW_IMAGE = 1; + protected static final int CONTENT_PREVIEW_FILE = 2; + protected static final int CONTENT_PREVIEW_TEXT = 3; protected MetricsLogger mMetricsLogger; private ContentPreviewCoordinator mPreviewCoord; @@ -500,6 +509,9 @@ public class ChooserActivity extends ResolverActivity implements case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT: mMinTimeoutPassed = true; + if (!mServiceConnections.isEmpty()) { + getChooserActivityLogger().logSharesheetDirectLoadTimeout(); + } unbindRemainingServices(); maybeStopServiceRequestTimer(); break; @@ -533,6 +545,7 @@ public class ChooserActivity extends ResolverActivity implements logDirectShareTargetReceived( MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER); sendVoiceChoicesIfNeeded(); + getChooserActivityLogger().logSharesheetDirectLoadComplete(); break; default: @@ -544,6 +557,7 @@ public class ChooserActivity extends ResolverActivity implements @Override protected void onCreate(Bundle savedInstanceState) { final long intentReceivedTime = System.currentTimeMillis(); + getChooserActivityLogger().logSharesheetTriggered(); // This is the only place this value is being set. Effectively final. mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable(); @@ -707,6 +721,8 @@ public class ChooserActivity extends ResolverActivity implements incrementNumSheetExpansions(); mWrittenOnce = true; } + getChooserActivityLogger() + .logSharesheetExpansionChanged(isCollapsed); } }); } @@ -715,6 +731,16 @@ public class ChooserActivity extends ResolverActivity implements Log.d(TAG, "System Time Cost is " + systemCost); } + getChooserActivityLogger().logShareStarted( + FrameworkStatsLog.SHARESHEET_STARTED, + getReferrerPackageName(), + target.getType(), + initialIntents == null ? 0 : initialIntents.length, + mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length, + isWorkProfile(), + findPreferredContentPreview(getTargetIntent(), getContentResolver()), + target.getAction() + ); mDirectShareShortcutInfoCache = new HashMap<>(); } @@ -969,6 +995,10 @@ public class ChooserActivity extends ResolverActivity implements LogMaker targetLogMaker = new LogMaker( MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1); getMetricsLogger().write(targetLogMaker); + getChooserActivityLogger().logShareTargetSelected( + SELECTION_TYPE_COPY, + "", + -1); finish(); } @@ -1644,18 +1674,33 @@ public class ChooserActivity extends ResolverActivity implements if (mCallerChooserTargets != null) { numCallerProvided = mCallerChooserTargets.length; } + getChooserActivityLogger().logShareTargetSelected( + SELECTION_TYPE_SERVICE, + targetInfo.getResolveInfo().activityInfo.processName, + value + ); break; case ChooserListAdapter.TARGET_CALLER: case ChooserListAdapter.TARGET_STANDARD: cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET; value -= currentListAdapter.getSelectableServiceTargetCount(); numCallerProvided = currentListAdapter.getCallerTargetCount(); + getChooserActivityLogger().logShareTargetSelected( + SELECTION_TYPE_APP, + targetInfo.getResolveInfo().activityInfo.processName, + value + ); break; case ChooserListAdapter.TARGET_STANDARD_AZ: // A-Z targets are unranked standard targets; we use -1 to mark that they // are from the alphabetical pool. value = -1; cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET; + getChooserActivityLogger().logShareTargetSelected( + SELECTION_TYPE_STANDARD, + targetInfo.getResolveInfo().activityInfo.processName, + value + ); break; } @@ -2131,7 +2176,7 @@ public class ChooserActivity extends ResolverActivity implements if (appTarget != null) { directShareAppPredictor.notifyAppTargetEvent( new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH) - .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE) + .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE) .build()); } } @@ -2290,6 +2335,13 @@ public class ChooserActivity extends ResolverActivity implements return mMetricsLogger; } + protected ChooserActivityLogger getChooserActivityLogger() { + if (mChooserActivityLogger == null) { + mChooserActivityLogger = new ChooserActivityLoggerImpl(); + } + return mChooserActivityLogger; + } + public class ChooserListController extends ResolverListController { public ChooserListController(Context context, PackageManager pm, @@ -2601,6 +2653,7 @@ public class ChooserActivity extends ResolverActivity implements // don't support direct share on low ram devices if (ActivityManager.isLowRamDeviceStatic()) { + getChooserActivityLogger().logSharesheetAppLoadComplete(); return; } @@ -2619,6 +2672,8 @@ public class ChooserActivity extends ResolverActivity implements queryTargetServices(chooserListAdapter); } + + getChooserActivityLogger().logSharesheetAppLoadComplete(); } private void setupScrollListener() { @@ -3777,4 +3832,9 @@ public class ChooserActivity extends ResolverActivity implements canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint); } } + + @Override + protected void maybeLogProfileChange() { + getChooserActivityLogger().logShareheetProfileChanged(); + } } diff --git a/core/java/com/android/internal/app/ChooserActivityLogger.java b/core/java/com/android/internal/app/ChooserActivityLogger.java new file mode 100644 index 000000000000..dc482443040a --- /dev/null +++ b/core/java/com/android/internal/app/ChooserActivityLogger.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2020 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.internal.app; + +import android.content.Intent; +import android.provider.MediaStore; + +import com.android.internal.logging.InstanceId; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.util.FrameworkStatsLog; + +/** + * Interface for writing Sharesheet atoms to statsd log. + * @hide + */ +public interface ChooserActivityLogger { + /** Logs a UiEventReported event for the system sharesheet completing initial start-up. */ + void logShareStarted(int eventId, String packageName, String mimeType, int appProvidedDirect, + int appProvidedApp, boolean isWorkprofile, int previewType, String intent); + + /** Logs a UiEventReported event for the system sharesheet when the user selects a target. */ + void logShareTargetSelected(int targetType, String packageName, int positionPicked); + + /** Logs a UiEventReported event for the system sharesheet being triggered by the user. */ + default void logSharesheetTriggered() { + log(SharesheetStandardEvent.SHARESHEET_TRIGGERED, getInstanceId()); + } + + /** Logs a UiEventReported event for the system sharesheet completing loading app targets. */ + default void logSharesheetAppLoadComplete() { + log(SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE, getInstanceId()); + } + + /** + * Logs a UiEventReported event for the system sharesheet completing loading service targets. + */ + default void logSharesheetDirectLoadComplete() { + log(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE, getInstanceId()); + } + + /** + * Logs a UiEventReported event for the system sharesheet timing out loading service targets. + */ + default void logSharesheetDirectLoadTimeout() { + log(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_TIMEOUT, getInstanceId()); + } + + /** + * Logs a UiEventReported event for the system sharesheet switching + * between work and main profile. + */ + default void logShareheetProfileChanged() { + log(SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED, getInstanceId()); + } + + /** Logs a UiEventReported event for the system sharesheet getting expanded or collapsed. */ + default void logSharesheetExpansionChanged(boolean isCollapsed) { + log(isCollapsed ? SharesheetStandardEvent.SHARESHEET_COLLAPSED : + SharesheetStandardEvent.SHARESHEET_EXPANDED, getInstanceId()); + } + + /** + * Logs a UiEventReported event for a given share activity + * @param event + * @param instanceId + */ + void log(UiEventLogger.UiEventEnum event, InstanceId instanceId); + + /** + * + * @return + */ + InstanceId getInstanceId(); + + /** + * The UiEvent enums that this class can log. + */ + enum SharesheetStartedEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "Basic system Sharesheet has started and is visible.") + SHARE_STARTED(228); + + private final int mId; + SharesheetStartedEvent(int id) { + mId = id; + } + @Override + public int getId() { + return mId; + } + } + + /** + * The UiEvent enums that this class can log. + */ + enum SharesheetTargetSelectedEvent implements UiEventLogger.UiEventEnum { + INVALID(0), + @UiEvent(doc = "User selected a service target.") + SHARESHEET_SERVICE_TARGET_SELECTED(232), + @UiEvent(doc = "User selected an app target.") + SHARESHEET_APP_TARGET_SELECTED(233), + @UiEvent(doc = "User selected a standard target.") + SHARESHEET_STANDARD_TARGET_SELECTED(234), + @UiEvent(doc = "User selected the copy target.") + SHARESHEET_COPY_TARGET_SELECTED(235); + + private final int mId; + SharesheetTargetSelectedEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } + + public static SharesheetTargetSelectedEvent fromTargetType(int targetType) { + switch(targetType) { + case ChooserActivity.SELECTION_TYPE_SERVICE: + return SHARESHEET_SERVICE_TARGET_SELECTED; + case ChooserActivity.SELECTION_TYPE_APP: + return SHARESHEET_APP_TARGET_SELECTED; + case ChooserActivity.SELECTION_TYPE_STANDARD: + return SHARESHEET_STANDARD_TARGET_SELECTED; + case ChooserActivity.SELECTION_TYPE_COPY: + return SHARESHEET_COPY_TARGET_SELECTED; + default: + return INVALID; + } + } + } + + /** + * The UiEvent enums that this class can log. + */ + enum SharesheetStandardEvent implements UiEventLogger.UiEventEnum { + INVALID(0), + @UiEvent(doc = "User clicked share.") + SHARESHEET_TRIGGERED(227), + @UiEvent(doc = "User changed from work to personal profile or vice versa.") + SHARESHEET_PROFILE_CHANGED(229), + @UiEvent(doc = "User expanded target list.") + SHARESHEET_EXPANDED(230), + @UiEvent(doc = "User collapsed target list.") + SHARESHEET_COLLAPSED(231), + @UiEvent(doc = "Sharesheet app targets is fully populated.") + SHARESHEET_APP_LOAD_COMPLETE(322), + @UiEvent(doc = "Sharesheet direct targets is fully populated.") + SHARESHEET_DIRECT_LOAD_COMPLETE(323), + @UiEvent(doc = "Sharesheet direct targets timed out.") + SHARESHEET_DIRECT_LOAD_TIMEOUT(324); + + private final int mId; + SharesheetStandardEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } + } + + /** + * Returns the enum used in sharesheet started atom to indicate what preview type was used. + */ + default int typeFromPreviewInt(int previewType) { + switch(previewType) { + case ChooserActivity.CONTENT_PREVIEW_IMAGE: + return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_IMAGE; + case ChooserActivity.CONTENT_PREVIEW_FILE: + return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_FILE; + case ChooserActivity.CONTENT_PREVIEW_TEXT: + default: + return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_TEXT; + } + } + + /** + * Returns the enum used in sharesheet started atom to indicate what intent triggers the + * ChooserActivity. + */ + default int typeFromIntentString(String intent) { + switch (intent) { + case Intent.ACTION_VIEW: + return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_VIEW; + case Intent.ACTION_EDIT: + return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_EDIT; + case Intent.ACTION_SEND: + return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND; + case Intent.ACTION_SENDTO: + return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SENDTO; + case Intent.ACTION_SEND_MULTIPLE: + return FrameworkStatsLog + .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND_MULTIPLE; + case MediaStore.ACTION_IMAGE_CAPTURE: + return FrameworkStatsLog + .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_IMAGE_CAPTURE; + case Intent.ACTION_MAIN: + return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_MAIN; + default: + return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_DEFAULT; + } + } +} diff --git a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java new file mode 100644 index 000000000000..48bdba3f5dae --- /dev/null +++ b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 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.internal.app; + +import com.android.internal.logging.InstanceId; +import com.android.internal.logging.InstanceIdSequence; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; +import com.android.internal.util.FrameworkStatsLog; + +/** + * Standard implementation of ChooserActivityLogger interface. + * @hide + */ +public class ChooserActivityLoggerImpl implements ChooserActivityLogger { + private static final int SHARESHEET_INSTANCE_ID_MAX = (1 << 13); + + private UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + // A small per-notification ID, used for statsd logging. + private InstanceId mInstanceId; + private static InstanceIdSequence sInstanceIdSequence; + + @Override + public void logShareStarted(int eventId, String packageName, String mimeType, + int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType, + String intent) { + FrameworkStatsLog.write(FrameworkStatsLog.SHARESHEET_STARTED, + /* event_id = 1 */ SharesheetStartedEvent.SHARE_STARTED.getId(), + /* package_name = 2 */ packageName, + /* instance_id = 3 */ getInstanceId().getId(), + /* mime_type = 4 */ mimeType, + /* num_app_provided_direct_targets = 5 */ appProvidedDirect, + /* num_app_provided_app_targets = 6 */ appProvidedApp, + /* is_workprofile = 7 */ isWorkprofile, + /* previewType = 8 */ typeFromPreviewInt(previewType), + /* intentType = 9 */ typeFromIntentString(intent)); + } + + @Override + public void logShareTargetSelected(int targetType, String packageName, int positionPicked) { + FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED, + /* event_id = 1 */ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), + /* package_name = 2 */ packageName, + /* instance_id = 3 */ getInstanceId().getId(), + /* position_picked = 4 */ positionPicked); + } + + @Override + public void log(UiEventLogger.UiEventEnum event, InstanceId instanceId) { + mUiEventLogger.logWithInstanceId( + event, + 0, + null, + instanceId); + } + + @Override + public InstanceId getInstanceId() { + if (mInstanceId == null) { + if (sInstanceIdSequence == null) { + sInstanceIdSequence = new InstanceIdSequence(SHARESHEET_INSTANCE_ID_MAX); + } + mInstanceId = sInstanceIdSequence.newInstanceId(); + } + return mInstanceId; + } + +} diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index f088ab38658c..35253b68aac7 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1578,6 +1578,7 @@ public class ResolverActivity extends Activity implements viewPager.setCurrentItem(1); } setupViewVisibilities(); + maybeLogProfileChange(); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS) .setInt(viewPager.getCurrentItem()) @@ -1998,4 +1999,6 @@ public class ResolverActivity extends Activity implements } } } + + protected void maybeLogProfileChange() {} } diff --git a/core/java/com/android/internal/inputmethod/CancellationGroup.java b/core/java/com/android/internal/inputmethod/CancellationGroup.java new file mode 100644 index 000000000000..09c9d128553b --- /dev/null +++ b/core/java/com/android/internal/inputmethod/CancellationGroup.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2020 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.internal.inputmethod; + +import android.annotation.AnyThread; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * A utility class, which works as both a factory class of completable objects and a cancellation + * signal to cancel all the completable objects created by this object. + */ +public final class CancellationGroup { + private final Object mLock = new Object(); + + /** + * List of {@link CountDownLatch}, which can be used to propagate {@link #cancelAll()} to + * completable objects. + * + * <p>This will be lazily instantiated to avoid unnecessary object allocations.</p> + */ + @Nullable + @GuardedBy("mLock") + private ArrayList<CountDownLatch> mLatchList = null; + + @GuardedBy("mLock") + private boolean mCanceled = false; + + /** + * An inner class to consolidate completable object types supported by + * {@link CancellationGroup}. + */ + public static final class Completable { + + /** + * Not intended to be instantiated. + */ + private Completable() { + } + + /** + * Base class of all the completable types supported by {@link CancellationGroup}. + */ + protected static class ValueBase { + /** + * {@link CountDownLatch} to be signaled to unblock {@link #await(int, TimeUnit)}. + */ + private final CountDownLatch mLatch = new CountDownLatch(1); + + /** + * {@link CancellationGroup} to which this completable object belongs. + */ + @NonNull + private final CancellationGroup mParentGroup; + + /** + * Lock {@link Object} to guard complete operations within this class. + */ + protected final Object mValueLock = new Object(); + + /** + * {@code true} after {@link #onComplete()} gets called. + */ + @GuardedBy("mValueLock") + protected boolean mHasValue = false; + + /** + * Base constructor. + * + * @param parentGroup {@link CancellationGroup} to which this completable object + * belongs. + */ + protected ValueBase(@NonNull CancellationGroup parentGroup) { + mParentGroup = parentGroup; + } + + /** + * @return {@link true} if {@link #onComplete()} gets called already. + */ + @AnyThread + public boolean hasValue() { + synchronized (mValueLock) { + return mHasValue; + } + } + + /** + * Called by subclasses to signale {@link #mLatch}. + */ + @AnyThread + protected void onComplete() { + mLatch.countDown(); + } + + /** + * Blocks the calling thread until at least one of the following conditions is met. + * + * <p> + * <ol> + * <li>This object becomes ready to return the value.</li> + * <li>{@link CancellationGroup#cancelAll()} gets called.</li> + * <li>The given timeout period has passed.</li> + * </ol> + * </p> + * + * <p>The caller can distinguish the case 1 and case 2 by calling {@link #hasValue()}. + * Note that the return value of {@link #hasValue()} can change from {@code false} to + * {@code true} at any time, even after this methods finishes with returning + * {@code true}.</p> + * + * @param timeout length of the timeout. + * @param timeUnit unit of {@code timeout}. + * @return {@code false} if and only if the given timeout period has passed. Otherwise + * {@code true}. + */ + @AnyThread + public boolean await(int timeout, @NonNull TimeUnit timeUnit) { + if (!mParentGroup.registerLatch(mLatch)) { + // Already canceled when this method gets called. + return false; + } + try { + return mLatch.await(timeout, timeUnit); + } catch (InterruptedException e) { + return true; + } finally { + mParentGroup.unregisterLatch(mLatch); + } + } + } + + /** + * Completable object of integer primitive. + */ + public static final class Int extends ValueBase { + @GuardedBy("mValueLock") + private int mValue = 0; + + private Int(@NonNull CancellationGroup factory) { + super(factory); + } + + /** + * Notify when a value is set to this completable object. + * + * @param value value to be set. + */ + @AnyThread + void onComplete(int value) { + synchronized (mValueLock) { + if (mHasValue) { + throw new UnsupportedOperationException( + "onComplete() cannot be called multiple times"); + } + mValue = value; + mHasValue = true; + } + onComplete(); + } + + /** + * @return value associated with this object. + * @throws UnsupportedOperationException when called while {@link #hasValue()} returns + * {@code false}. + */ + @AnyThread + public int getValue() { + synchronized (mValueLock) { + if (!mHasValue) { + throw new UnsupportedOperationException( + "getValue() is allowed only if hasValue() returns true"); + } + return mValue; + } + } + } + + /** + * Base class of completable object types. + * + * @param <T> type associated with this completable object. + */ + public static class Values<T> extends ValueBase { + @GuardedBy("mValueLock") + @Nullable + private T mValue = null; + + protected Values(@NonNull CancellationGroup factory) { + super(factory); + } + + /** + * Notify when a value is set to this completable value object. + * + * @param value value to be set. + */ + @AnyThread + void onComplete(@Nullable T value) { + synchronized (mValueLock) { + if (mHasValue) { + throw new UnsupportedOperationException( + "onComplete() cannot be called multiple times"); + } + mValue = value; + mHasValue = true; + } + onComplete(); + } + + /** + * @return value associated with this object. + * @throws UnsupportedOperationException when called while {@link #hasValue()} returns + * {@code false}. + */ + @AnyThread + @Nullable + public T getValue() { + synchronized (mValueLock) { + if (!mHasValue) { + throw new UnsupportedOperationException( + "getValue() is allowed only if hasValue() returns true"); + } + return mValue; + } + } + } + + /** + * Completable object of {@link java.lang.CharSequence}. + */ + public static final class CharSequence extends Values<java.lang.CharSequence> { + private CharSequence(@NonNull CancellationGroup factory) { + super(factory); + } + } + + /** + * Completable object of {@link android.view.inputmethod.ExtractedText}. + */ + public static final class ExtractedText + extends Values<android.view.inputmethod.ExtractedText> { + private ExtractedText(@NonNull CancellationGroup factory) { + super(factory); + } + } + } + + /** + * @return an instance of {@link Completable.Int} that is associated with this + * {@link CancellationGroup}. + */ + @AnyThread + public Completable.Int createCompletableInt() { + return new Completable.Int(this); + } + + /** + * @return an instance of {@link Completable.CharSequence} that is associated with this + * {@link CancellationGroup}. + */ + @AnyThread + public Completable.CharSequence createCompletableCharSequence() { + return new Completable.CharSequence(this); + } + + /** + * @return an instance of {@link Completable.ExtractedText} that is associated with this + * {@link CancellationGroup}. + */ + @AnyThread + public Completable.ExtractedText createCompletableExtractedText() { + return new Completable.ExtractedText(this); + } + + @AnyThread + private boolean registerLatch(@NonNull CountDownLatch latch) { + synchronized (mLock) { + if (mCanceled) { + return false; + } + if (mLatchList == null) { + // Set the initial capacity to 1 with an assumption that usually there is up to 1 + // on-going operation. + mLatchList = new ArrayList<>(1); + } + mLatchList.add(latch); + return true; + } + } + + @AnyThread + private void unregisterLatch(@NonNull CountDownLatch latch) { + synchronized (mLock) { + if (mLatchList != null) { + mLatchList.remove(latch); + } + } + } + + /** + * Cancel all the completable objects created from this {@link CancellationGroup}. + * + * <p>Secondary calls will be silently ignored.</p> + */ + @AnyThread + public void cancelAll() { + synchronized (mLock) { + if (!mCanceled) { + mCanceled = true; + if (mLatchList != null) { + mLatchList.forEach(CountDownLatch::countDown); + mLatchList.clear(); + mLatchList = null; + } + } + } + } + + /** + * @return {@code true} if {@link #cancelAll()} is already called. {@code false} otherwise. + */ + @AnyThread + public boolean isCanceled() { + synchronized (mLock) { + return mCanceled; + } + } +} diff --git a/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl b/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl new file mode 100644 index 000000000000..da56fd045e57 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2020 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.internal.inputmethod; + +oneway interface ICharSequenceResultCallback { + void onResult(in CharSequence result); +} diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl index 0f40a83d7ee4..b603f6adc2d2 100644 --- a/core/java/com/android/internal/view/IInputContextCallback.aidl +++ b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2020 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. @@ -14,19 +14,10 @@ * limitations under the License. */ -package com.android.internal.view; +package com.android.internal.inputmethod; import android.view.inputmethod.ExtractedText; -/** - * {@hide} - */ -oneway interface IInputContextCallback { - void setTextBeforeCursor(CharSequence textBeforeCursor, int seq); - void setTextAfterCursor(CharSequence textAfterCursor, int seq); - void setCursorCapsMode(int capsMode, int seq); - void setExtractedText(in ExtractedText extractedText, int seq); - void setSelectedText(CharSequence selectedText, int seq); - void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq); - void setCommitContentResult(boolean result, int seq); +oneway interface IExtractedTextResultCallback { + void onResult(in ExtractedText result); } diff --git a/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl b/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl new file mode 100644 index 000000000000..bc5ed0d38633 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2020 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.internal.inputmethod; + +oneway interface IIntResultCallback { + void onResult(int result); +} diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java new file mode 100644 index 000000000000..44a8a83b519f --- /dev/null +++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2020 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.internal.inputmethod; + +import android.annotation.AnyThread; +import android.annotation.BinderThread; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Defines a set of factory methods to create {@link android.os.IBinder}-based callbacks that are + * associated with completable objects defined in {@link CancellationGroup.Completable}. + */ +public final class ResultCallbacks { + + /** + * Not intended to be instantiated. + */ + private ResultCallbacks() { + } + + @AnyThread + @Nullable + private static <T> T unwrap(@NonNull AtomicReference<WeakReference<T>> atomicRef) { + final WeakReference<T> ref = atomicRef.getAndSet(null); + if (ref == null) { + // Double-call is guaranteed to be ignored here. + return null; + } + final T value = ref.get(); + ref.clear(); + return value; + } + + /** + * Creates {@link IIntResultCallback.Stub} that is to set + * {@link CancellationGroup.Completable.Int} when receiving the result. + * + * @param value {@link CancellationGroup.Completable.Int} to be set when receiving the result. + * @return {@link IIntResultCallback.Stub} that can be passed as a binder IPC parameter. + */ + @AnyThread + public static IIntResultCallback.Stub of(@NonNull CancellationGroup.Completable.Int value) { + final AtomicReference<WeakReference<CancellationGroup.Completable.Int>> + atomicRef = new AtomicReference<>(new WeakReference<>(value)); + + return new IIntResultCallback.Stub() { + @BinderThread + @Override + public void onResult(int result) { + final CancellationGroup.Completable.Int value = unwrap(atomicRef); + if (value == null) { + return; + } + value.onComplete(result); + } + }; + } + + /** + * Creates {@link ICharSequenceResultCallback.Stub} that is to set + * {@link CancellationGroup.Completable.CharSequence} when receiving the result. + * + * @param value {@link CancellationGroup.Completable.CharSequence} to be set when receiving the + * result. + * @return {@link ICharSequenceResultCallback.Stub} that can be passed as a binder IPC + * parameter. + */ + @AnyThread + public static ICharSequenceResultCallback.Stub of( + @NonNull CancellationGroup.Completable.CharSequence value) { + final AtomicReference<WeakReference<CancellationGroup.Completable.CharSequence>> atomicRef = + new AtomicReference<>(new WeakReference<>(value)); + + return new ICharSequenceResultCallback.Stub() { + @BinderThread + @Override + public void onResult(CharSequence result) { + final CancellationGroup.Completable.CharSequence value = unwrap(atomicRef); + if (value == null) { + return; + } + value.onComplete(result); + } + }; + } + + /** + * Creates {@link IExtractedTextResultCallback.Stub} that is to set + * {@link CancellationGroup.Completable.ExtractedText} when receiving the result. + * + * @param value {@link CancellationGroup.Completable.ExtractedText} to be set when receiving the + * result. + * @return {@link IExtractedTextResultCallback.Stub} that can be passed as a binder IPC + * parameter. + */ + @AnyThread + public static IExtractedTextResultCallback.Stub of( + @NonNull CancellationGroup.Completable.ExtractedText value) { + final AtomicReference<WeakReference<CancellationGroup.Completable.ExtractedText>> + atomicRef = new AtomicReference<>(new WeakReference<>(value)); + + return new IExtractedTextResultCallback.Stub() { + @BinderThread + @Override + public void onResult(android.view.inputmethod.ExtractedText result) { + final CancellationGroup.Completable.ExtractedText value = unwrap(atomicRef); + if (value == null) { + return; + } + value.onComplete(result); + } + }; + } +} diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java index 91ba0dfbcc54..180ab0810f5b 100644 --- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java +++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java @@ -85,7 +85,7 @@ public class UiEventLoggerFake implements UiEventLogger { } @Override - public void logWithInstanceId(UiEventLogger.UiEventEnum event, int uid, String packageName, + public void logWithInstanceId(UiEventEnum event, int uid, String packageName, InstanceId instance) { final int eventId = event.getId(); if (eventId > 0) { diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index 6278d4a35329..9257c6d19148 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -29,6 +29,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionInspector; @@ -36,6 +37,9 @@ import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags; import android.view.inputmethod.InputContentInfo; import com.android.internal.annotations.GuardedBy; +import com.android.internal.inputmethod.ICharSequenceResultCallback; +import com.android.internal.inputmethod.IExtractedTextResultCallback; +import com.android.internal.inputmethod.IIntResultCallback; import com.android.internal.os.SomeArgs; public abstract class IInputConnectionWrapper extends IInputContext.Stub { @@ -111,28 +115,31 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { abstract protected boolean isActive(); - public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) { - dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback)); + public void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback) { + dispatchMessage(mH.obtainMessage(DO_GET_TEXT_AFTER_CURSOR, length, flags, callback)); } - - public void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) { - dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback)); + + public void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback) { + dispatchMessage(mH.obtainMessage(DO_GET_TEXT_BEFORE_CURSOR, length, flags, callback)); } - public void getSelectedText(int flags, int seq, IInputContextCallback callback) { - dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback)); + public void getSelectedText(int flags, ICharSequenceResultCallback callback) { + dispatchMessage(mH.obtainMessage(DO_GET_SELECTED_TEXT, flags, 0 /* unused */, callback)); } - public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) { - dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback)); + public void getCursorCapsMode(int reqModes, IIntResultCallback callback) { + dispatchMessage( + mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback)); } - public void getExtractedText(ExtractedTextRequest request, - int flags, int seq, IInputContextCallback callback) { - dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags, - request, seq, callback)); + public void getExtractedText(ExtractedTextRequest request, int flags, + IExtractedTextResultCallback callback) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = request; + args.arg2 = callback; + dispatchMessage(mH.obtainMessage(DO_GET_EXTRACTED_TEXT, flags, 0 /* unused */, args)); } - + public void commitText(CharSequence text, int newCursorPosition) { dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text)); } @@ -199,10 +206,9 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data)); } - public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq, - IInputContextCallback callback) { - dispatchMessage(obtainMessageISC(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode, - seq, callback)); + public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, IIntResultCallback callback) { + dispatchMessage(mH.obtainMessage(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode, + 0 /* unused */, callback)); } public void closeConnection() { @@ -210,9 +216,12 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } public void commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts, - int seq, IInputContextCallback callback) { - dispatchMessage(obtainMessageIOOSC(DO_COMMIT_CONTENT, flags, inputContentInfo, opts, seq, - callback)); + IIntResultCallback callback) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = inputContentInfo; + args.arg2 = opts; + args.arg3 = callback; + dispatchMessage(mH.obtainMessage(DO_COMMIT_CONTENT, flags, 0 /* unused */, args)); } void dispatchMessage(Message msg) { @@ -231,100 +240,97 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { void executeMessage(Message msg) { switch (msg.what) { case DO_GET_TEXT_AFTER_CURSOR: { - SomeArgs args = (SomeArgs)msg.obj; + final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj; + final InputConnection ic = getInputConnection(); + final CharSequence result; + if (ic == null || !isActive()) { + Log.w(TAG, "getTextAfterCursor on inactive InputConnection"); + result = null; + } else { + result = ic.getTextAfterCursor(msg.arg1, msg.arg2); + } try { - final IInputContextCallback callback = (IInputContextCallback) args.arg6; - final int callbackSeq = args.argi6; - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "getTextAfterCursor on inactive InputConnection"); - callback.setTextAfterCursor(null, callbackSeq); - return; - } - callback.setTextAfterCursor(ic.getTextAfterCursor( - msg.arg1, msg.arg2), callbackSeq); + callback.onResult(result); } catch (RemoteException e) { - Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e); - } finally { - args.recycle(); + Log.w(TAG, "Failed to return the result to getTextAfterCursor()." + + " result=" + result, e); } return; } case DO_GET_TEXT_BEFORE_CURSOR: { - SomeArgs args = (SomeArgs)msg.obj; + final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj; + final InputConnection ic = getInputConnection(); + final CharSequence result; + if (ic == null || !isActive()) { + Log.w(TAG, "getTextBeforeCursor on inactive InputConnection"); + result = null; + } else { + result = ic.getTextBeforeCursor(msg.arg1, msg.arg2); + } try { - final IInputContextCallback callback = (IInputContextCallback) args.arg6; - final int callbackSeq = args.argi6; - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "getTextBeforeCursor on inactive InputConnection"); - callback.setTextBeforeCursor(null, callbackSeq); - return; - } - callback.setTextBeforeCursor(ic.getTextBeforeCursor( - msg.arg1, msg.arg2), callbackSeq); + callback.onResult(result); } catch (RemoteException e) { - Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e); - } finally { - args.recycle(); + Log.w(TAG, "Failed to return the result to getTextBeforeCursor()." + + " result=" + result, e); } return; } case DO_GET_SELECTED_TEXT: { - SomeArgs args = (SomeArgs)msg.obj; + final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj; + final InputConnection ic = getInputConnection(); + final CharSequence result; + if (ic == null || !isActive()) { + Log.w(TAG, "getSelectedText on inactive InputConnection"); + result = null; + } else { + result = ic.getSelectedText(msg.arg1); + } try { - final IInputContextCallback callback = (IInputContextCallback) args.arg6; - final int callbackSeq = args.argi6; - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "getSelectedText on inactive InputConnection"); - callback.setSelectedText(null, callbackSeq); - return; - } - callback.setSelectedText(ic.getSelectedText( - msg.arg1), callbackSeq); + callback.onResult(result); } catch (RemoteException e) { - Log.w(TAG, "Got RemoteException calling setSelectedText", e); - } finally { - args.recycle(); + Log.w(TAG, "Failed to return the result to getSelectedText()." + + " result=" + result, e); } return; } case DO_GET_CURSOR_CAPS_MODE: { - SomeArgs args = (SomeArgs)msg.obj; + final IIntResultCallback callback = (IIntResultCallback) msg.obj; + final InputConnection ic = getInputConnection(); + final int result; + if (ic == null || !isActive()) { + Log.w(TAG, "getCursorCapsMode on inactive InputConnection"); + result = 0; + } else { + result = ic.getCursorCapsMode(msg.arg1); + } try { - final IInputContextCallback callback = (IInputContextCallback) args.arg6; - final int callbackSeq = args.argi6; - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "getCursorCapsMode on inactive InputConnection"); - callback.setCursorCapsMode(0, callbackSeq); - return; - } - callback.setCursorCapsMode(ic.getCursorCapsMode(msg.arg1), - callbackSeq); + callback.onResult(result); } catch (RemoteException e) { - Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e); - } finally { - args.recycle(); + Log.w(TAG, "Failed to return the result to getCursorCapsMode()." + + " result=" + result, e); } return; } case DO_GET_EXTRACTED_TEXT: { - SomeArgs args = (SomeArgs)msg.obj; + final SomeArgs args = (SomeArgs) msg.obj; try { - final IInputContextCallback callback = (IInputContextCallback) args.arg6; - final int callbackSeq = args.argi6; - InputConnection ic = getInputConnection(); + final ExtractedTextRequest request = (ExtractedTextRequest) args.arg1; + final IExtractedTextResultCallback callback = + (IExtractedTextResultCallback) args.arg2; + final InputConnection ic = getInputConnection(); + final ExtractedText result; if (ic == null || !isActive()) { Log.w(TAG, "getExtractedText on inactive InputConnection"); - callback.setExtractedText(null, callbackSeq); - return; + result = null; + } else { + result = ic.getExtractedText(request, msg.arg1); + } + try { + callback.onResult(result); + } catch (RemoteException e) { + Log.w(TAG, "Failed to return the result to getExtractedText()." + + " result=" + result, e); } - callback.setExtractedText(ic.getExtractedText( - (ExtractedTextRequest)args.arg1, msg.arg1), callbackSeq); - } catch (RemoteException e) { - Log.w(TAG, "Got RemoteException calling setExtractedText", e); } finally { args.recycle(); } @@ -494,22 +500,20 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { return; } case DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO: { - SomeArgs args = (SomeArgs)msg.obj; + final IIntResultCallback callback = (IIntResultCallback) msg.obj; + final InputConnection ic = getInputConnection(); + final boolean result; + if (ic == null || !isActive()) { + Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection"); + result = false; + } else { + result = ic.requestCursorUpdates(msg.arg1); + } try { - final IInputContextCallback callback = (IInputContextCallback) args.arg6; - final int callbackSeq = args.argi6; - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection"); - callback.setRequestUpdateCursorAnchorInfoResult(false, callbackSeq); - return; - } - callback.setRequestUpdateCursorAnchorInfoResult( - ic.requestCursorUpdates(msg.arg1), callbackSeq); + callback.onResult(result ? 1 : 0); } catch (RemoteException e) { - Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e); - } finally { - args.recycle(); + Log.w(TAG, "Failed to return the result to requestCursorUpdates()." + + " result=" + result, e); } return; } @@ -547,26 +551,28 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { final int flags = msg.arg1; SomeArgs args = (SomeArgs) msg.obj; try { - final IInputContextCallback callback = (IInputContextCallback) args.arg6; - final int callbackSeq = args.argi6; - InputConnection ic = getInputConnection(); + final IIntResultCallback callback = (IIntResultCallback) args.arg3; + final InputConnection ic = getInputConnection(); + final boolean result; if (ic == null || !isActive()) { Log.w(TAG, "commitContent on inactive InputConnection"); - callback.setCommitContentResult(false, callbackSeq); - return; + result = false; + } else { + final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1; + if (inputContentInfo == null || !inputContentInfo.validate()) { + Log.w(TAG, "commitContent with invalid inputContentInfo=" + + inputContentInfo); + result = false; + } else { + result = ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2); + } } - final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1; - if (inputContentInfo == null || !inputContentInfo.validate()) { - Log.w(TAG, "commitContent with invalid inputContentInfo=" - + inputContentInfo); - callback.setCommitContentResult(false, callbackSeq); - return; + try { + callback.onResult(result ? 1 : 0); + } catch (RemoteException e) { + Log.w(TAG, "Failed to return the result to commitContent()." + + " result=" + result, e); } - final boolean result = - ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2); - callback.setCommitContentResult(result, callbackSeq); - } catch (RemoteException e) { - Log.w(TAG, "Got RemoteException calling commitContent", e); } finally { args.recycle(); } @@ -588,40 +594,6 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { return mH.obtainMessage(what, 0, 0, arg1); } - Message obtainMessageISC(int what, int arg1, int callbackSeq, IInputContextCallback callback) { - final SomeArgs args = SomeArgs.obtain(); - args.arg6 = callback; - args.argi6 = callbackSeq; - return mH.obtainMessage(what, arg1, 0, args); - } - - Message obtainMessageIISC(int what, int arg1, int arg2, int callbackSeq, - IInputContextCallback callback) { - final SomeArgs args = SomeArgs.obtain(); - args.arg6 = callback; - args.argi6 = callbackSeq; - return mH.obtainMessage(what, arg1, arg2, args); - } - - Message obtainMessageIOOSC(int what, int arg1, Object objArg1, Object objArg2, int callbackSeq, - IInputContextCallback callback) { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = objArg1; - args.arg2 = objArg2; - args.arg6 = callback; - args.argi6 = callbackSeq; - return mH.obtainMessage(what, arg1, 0, args); - } - - Message obtainMessageIOSC(int what, int arg1, Object arg2, int callbackSeq, - IInputContextCallback callback) { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = arg2; - args.arg6 = callback; - args.argi6 = callbackSeq; - return mH.obtainMessage(what, arg1, 0, args); - } - Message obtainMessageIO(int what, int arg1, Object arg2) { return mH.obtainMessage(what, arg1, 0, arg2); } diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index c22799179b72..86f1293c014f 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -23,7 +23,9 @@ import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputContentInfo; -import com.android.internal.view.IInputContextCallback; +import com.android.internal.inputmethod.ICharSequenceResultCallback; +import com.android.internal.inputmethod.IExtractedTextResultCallback; +import com.android.internal.inputmethod.IIntResultCallback; /** * Interface from an input method to the application, allowing it to perform @@ -31,14 +33,14 @@ import com.android.internal.view.IInputContextCallback; * {@hide} */ oneway interface IInputContext { - void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback); + void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback); - void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback); - - void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback); - - void getExtractedText(in ExtractedTextRequest request, int flags, int seq, - IInputContextCallback callback); + void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback); + + void getCursorCapsMode(int reqModes, IIntResultCallback callback); + + void getExtractedText(in ExtractedTextRequest request, int flags, + IExtractedTextResultCallback callback); void deleteSurroundingText(int beforeLength, int afterLength); void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); @@ -71,11 +73,10 @@ import com.android.internal.view.IInputContextCallback; void setComposingRegion(int start, int end); - void getSelectedText(int flags, int seq, IInputContextCallback callback); + void getSelectedText(int flags, ICharSequenceResultCallback callback); - void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq, - IInputContextCallback callback); + void requestUpdateCursorAnchorInfo(int cursorUpdateMode, IIntResultCallback callback); - void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, int sec, - IInputContextCallback callback); + void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, + IIntResultCallback callback); } diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index a41048c0f426..0bf52345bc7e 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -17,14 +17,12 @@ package com.android.internal.view; import android.annotation.AnyThread; -import android.annotation.BinderThread; import android.annotation.NonNull; -import android.compat.annotation.UnsupportedAppUsage; +import android.annotation.Nullable; import android.inputmethodservice.AbstractInputMethodService; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; -import android.os.SystemClock; import android.util.Log; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; @@ -36,10 +34,15 @@ import android.view.inputmethod.InputConnectionInspector; import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags; import android.view.inputmethod.InputContentInfo; +import com.android.internal.inputmethod.CancellationGroup; +import com.android.internal.inputmethod.ResultCallbacks; + import java.lang.ref.WeakReference; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.TimeUnit; public class InputConnectionWrapper implements InputConnection { + private static final String TAG = "InputConnectionWrapper"; + private static final int MAX_WAIT_TIME_MILLIS = 2000; private final IInputContext mIInputContext; @NonNull @@ -49,257 +52,94 @@ public class InputConnectionWrapper implements InputConnection { private final int mMissingMethods; /** - * {@code true} if the system already decided to take away IME focus from the target app. This - * can be signaled even when the corresponding signal is in the task queue and - * {@link InputMethodService#onUnbindInput()} is not yet called back on the UI thread. + * Signaled when the system decided to take away IME focus from the target app. + * + * <p>This is expected to be signaled immediately when the IME process receives + * {@link IInputMethod#unbindInput()}.</p> */ @NonNull - private final AtomicBoolean mIsUnbindIssued; - - static class InputContextCallback extends IInputContextCallback.Stub { - private static final String TAG = "InputConnectionWrapper.ICC"; - public int mSeq; - public boolean mHaveValue; - public CharSequence mTextBeforeCursor; - public CharSequence mTextAfterCursor; - public CharSequence mSelectedText; - public ExtractedText mExtractedText; - public int mCursorCapsMode; - public boolean mRequestUpdateCursorAnchorInfoResult; - public boolean mCommitContentResult; - - // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain - // exclusive access to this object. - private static InputContextCallback sInstance = new InputContextCallback(); - private static int sSequenceNumber = 1; - - /** - * Returns an InputContextCallback object that is guaranteed not to be in use by - * any other thread. The returned object's 'have value' flag is cleared and its expected - * sequence number is set to a new integer. We use a sequence number so that replies that - * occur after a timeout has expired are not interpreted as replies to a later request. - */ - @UnsupportedAppUsage - @AnyThread - private static InputContextCallback getInstance() { - synchronized (InputContextCallback.class) { - // Return sInstance if it's non-null, otherwise construct a new callback - InputContextCallback callback; - if (sInstance != null) { - callback = sInstance; - sInstance = null; - - // Reset the callback - callback.mHaveValue = false; - } else { - callback = new InputContextCallback(); - } - - // Set the sequence number - callback.mSeq = sSequenceNumber++; - return callback; - } - } - - /** - * Makes the given InputContextCallback available for use in the future. - */ - @UnsupportedAppUsage - @AnyThread - private void dispose() { - synchronized (InputContextCallback.class) { - // If sInstance is non-null, just let this object be garbage-collected - if (sInstance == null) { - // Allow any objects being held to be gc'ed - mTextAfterCursor = null; - mTextBeforeCursor = null; - mExtractedText = null; - sInstance = this; - } - } - } - - @BinderThread - public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) { - synchronized (this) { - if (seq == mSeq) { - mTextBeforeCursor = textBeforeCursor; - mHaveValue = true; - notifyAll(); - } else { - Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq - + ") in setTextBeforeCursor, ignoring."); - } - } - } - - @BinderThread - public void setTextAfterCursor(CharSequence textAfterCursor, int seq) { - synchronized (this) { - if (seq == mSeq) { - mTextAfterCursor = textAfterCursor; - mHaveValue = true; - notifyAll(); - } else { - Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq - + ") in setTextAfterCursor, ignoring."); - } - } - } - - @BinderThread - public void setSelectedText(CharSequence selectedText, int seq) { - synchronized (this) { - if (seq == mSeq) { - mSelectedText = selectedText; - mHaveValue = true; - notifyAll(); - } else { - Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq - + ") in setSelectedText, ignoring."); - } - } - } - - @BinderThread - public void setCursorCapsMode(int capsMode, int seq) { - synchronized (this) { - if (seq == mSeq) { - mCursorCapsMode = capsMode; - mHaveValue = true; - notifyAll(); - } else { - Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq - + ") in setCursorCapsMode, ignoring."); - } - } - } - - @BinderThread - public void setExtractedText(ExtractedText extractedText, int seq) { - synchronized (this) { - if (seq == mSeq) { - mExtractedText = extractedText; - mHaveValue = true; - notifyAll(); - } else { - Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq - + ") in setExtractedText, ignoring."); - } - } - } - - @BinderThread - public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) { - synchronized (this) { - if (seq == mSeq) { - mRequestUpdateCursorAnchorInfoResult = result; - mHaveValue = true; - notifyAll(); - } else { - Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq - + ") in setCursorAnchorInfoRequestResult, ignoring."); - } - } - } - - @BinderThread - public void setCommitContentResult(boolean result, int seq) { - synchronized (this) { - if (seq == mSeq) { - mCommitContentResult = result; - mHaveValue = true; - notifyAll(); - } else { - Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq - + ") in setCommitContentResult, ignoring."); - } - } - } - - /** - * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds. - * - * <p>The caller must be synchronized on this callback object. - */ - @AnyThread - void waitForResultLocked() { - long startTime = SystemClock.uptimeMillis(); - long endTime = startTime + MAX_WAIT_TIME_MILLIS; - - while (!mHaveValue) { - long remainingTime = endTime - SystemClock.uptimeMillis(); - if (remainingTime <= 0) { - Log.w(TAG, "Timed out waiting on IInputContextCallback"); - return; - } - try { - wait(remainingTime); - } catch (InterruptedException e) { - } - } - } - } + private final CancellationGroup mCancellationGroup; public InputConnectionWrapper( @NonNull WeakReference<AbstractInputMethodService> inputMethodService, - IInputContext inputContext, @MissingMethodFlags final int missingMethods, - @NonNull AtomicBoolean isUnbindIssued) { + IInputContext inputContext, @MissingMethodFlags int missingMethods, + @NonNull CancellationGroup cancellationGroup) { mInputMethodService = inputMethodService; mIInputContext = inputContext; mMissingMethods = missingMethods; - mIsUnbindIssued = isUnbindIssued; + mCancellationGroup = cancellationGroup; + } + + @AnyThread + private static void logInternal(@Nullable String methodName, boolean timedOut, + @Nullable Object defaultValue) { + if (timedOut) { + Log.w(TAG, methodName + " didn't respond in " + MAX_WAIT_TIME_MILLIS + " msec." + + " Returning default: " + defaultValue); + } else { + Log.w(TAG, methodName + " was canceled before complete. Returning default: " + + defaultValue); + } + } + + @AnyThread + private static int getResultOrZero(@NonNull CancellationGroup.Completable.Int value, + @NonNull String methodName) { + final boolean timedOut = value.await(MAX_WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS); + if (value.hasValue()) { + return value.getValue(); + } + logInternal(methodName, timedOut, 0); + return 0; + } + + @AnyThread + @Nullable + private static <T> T getResultOrNull(@NonNull CancellationGroup.Completable.Values<T> value, + @NonNull String methodName) { + final boolean timedOut = value.await(MAX_WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS); + if (value.hasValue()) { + return value.getValue(); + } + logInternal(methodName, timedOut, null); + return null; } @AnyThread public CharSequence getTextAfterCursor(int length, int flags) { - if (mIsUnbindIssued.get()) { + if (mCancellationGroup.isCanceled()) { return null; } - CharSequence value = null; + final CancellationGroup.Completable.CharSequence value = + mCancellationGroup.createCompletableCharSequence(); try { - InputContextCallback callback = InputContextCallback.getInstance(); - mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback); - synchronized (callback) { - callback.waitForResultLocked(); - if (callback.mHaveValue) { - value = callback.mTextAfterCursor; - } - } - callback.dispose(); + mIInputContext.getTextAfterCursor(length, flags, ResultCallbacks.of(value)); } catch (RemoteException e) { return null; } - return value; + return getResultOrNull(value, "getTextAfterCursor()"); } @AnyThread public CharSequence getTextBeforeCursor(int length, int flags) { - if (mIsUnbindIssued.get()) { + if (mCancellationGroup.isCanceled()) { return null; } - CharSequence value = null; + final CancellationGroup.Completable.CharSequence value = + mCancellationGroup.createCompletableCharSequence(); try { - InputContextCallback callback = InputContextCallback.getInstance(); - mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback); - synchronized (callback) { - callback.waitForResultLocked(); - if (callback.mHaveValue) { - value = callback.mTextBeforeCursor; - } - } - callback.dispose(); + mIInputContext.getTextBeforeCursor(length, flags, ResultCallbacks.of(value)); } catch (RemoteException e) { return null; } - return value; + return getResultOrNull(value, "getTextBeforeCursor()"); } @AnyThread public CharSequence getSelectedText(int flags) { - if (mIsUnbindIssued.get()) { + if (mCancellationGroup.isCanceled()) { return null; } @@ -307,67 +147,46 @@ public class InputConnectionWrapper implements InputConnection { // This method is not implemented. return null; } - CharSequence value = null; + final CancellationGroup.Completable.CharSequence value = + mCancellationGroup.createCompletableCharSequence(); try { - InputContextCallback callback = InputContextCallback.getInstance(); - mIInputContext.getSelectedText(flags, callback.mSeq, callback); - synchronized (callback) { - callback.waitForResultLocked(); - if (callback.mHaveValue) { - value = callback.mSelectedText; - } - } - callback.dispose(); + mIInputContext.getSelectedText(flags, ResultCallbacks.of(value)); } catch (RemoteException e) { return null; } - return value; + return getResultOrNull(value, "getSelectedText()"); } @AnyThread public int getCursorCapsMode(int reqModes) { - if (mIsUnbindIssued.get()) { + if (mCancellationGroup.isCanceled()) { return 0; } - int value = 0; + final CancellationGroup.Completable.Int value = + mCancellationGroup.createCompletableInt(); try { - InputContextCallback callback = InputContextCallback.getInstance(); - mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback); - synchronized (callback) { - callback.waitForResultLocked(); - if (callback.mHaveValue) { - value = callback.mCursorCapsMode; - } - } - callback.dispose(); + mIInputContext.getCursorCapsMode(reqModes, ResultCallbacks.of(value)); } catch (RemoteException e) { return 0; } - return value; + return getResultOrZero(value, "getCursorCapsMode()"); } @AnyThread public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { - if (mIsUnbindIssued.get()) { + if (mCancellationGroup.isCanceled()) { return null; } - ExtractedText value = null; + final CancellationGroup.Completable.ExtractedText value = + mCancellationGroup.createCompletableExtractedText(); try { - InputContextCallback callback = InputContextCallback.getInstance(); - mIInputContext.getExtractedText(request, flags, callback.mSeq, callback); - synchronized (callback) { - callback.waitForResultLocked(); - if (callback.mHaveValue) { - value = callback.mExtractedText; - } - } - callback.dispose(); + mIInputContext.getExtractedText(request, flags, ResultCallbacks.of(value)); } catch (RemoteException e) { return null; } - return value; + return getResultOrNull(value, "getExtractedText()"); } @AnyThread @@ -563,29 +382,22 @@ public class InputConnectionWrapper implements InputConnection { @AnyThread public boolean requestCursorUpdates(int cursorUpdateMode) { - if (mIsUnbindIssued.get()) { + if (mCancellationGroup.isCanceled()) { return false; } - boolean result = false; if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) { // This method is not implemented. return false; } + final CancellationGroup.Completable.Int value = mCancellationGroup.createCompletableInt(); try { - InputContextCallback callback = InputContextCallback.getInstance(); - mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback); - synchronized (callback) { - callback.waitForResultLocked(); - if (callback.mHaveValue) { - result = callback.mRequestUpdateCursorAnchorInfoResult; - } - } - callback.dispose(); + mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, + ResultCallbacks.of(value)); } catch (RemoteException e) { return false; } - return result; + return getResultOrZero(value, "requestUpdateCursorAnchorInfo()") != 0; } @AnyThread @@ -601,38 +413,31 @@ public class InputConnectionWrapper implements InputConnection { @AnyThread public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { - if (mIsUnbindIssued.get()) { + if (mCancellationGroup.isCanceled()) { return false; } - boolean result = false; if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) { // This method is not implemented. return false; } - try { - if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { - final AbstractInputMethodService inputMethodService = mInputMethodService.get(); - if (inputMethodService == null) { - // This basically should not happen, because it's the the caller of this method. - return false; - } - inputMethodService.exposeContent(inputContentInfo, this); - } - InputContextCallback callback = InputContextCallback.getInstance(); - mIInputContext.commitContent(inputContentInfo, flags, opts, callback.mSeq, callback); - synchronized (callback) { - callback.waitForResultLocked(); - if (callback.mHaveValue) { - result = callback.mCommitContentResult; - } + if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { + final AbstractInputMethodService inputMethodService = mInputMethodService.get(); + if (inputMethodService == null) { + // This basically should not happen, because it's the caller of this method. + return false; } - callback.dispose(); + inputMethodService.exposeContent(inputContentInfo, this); + } + + final CancellationGroup.Completable.Int value = mCancellationGroup.createCompletableInt(); + try { + mIInputContext.commitContent(inputContentInfo, flags, opts, ResultCallbacks.of(value)); } catch (RemoteException e) { return false; } - return result; + return getResultOrZero(value, "commitContent()") != 0; } @AnyThread diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 5248ca944c3d..ab68c440483e 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -45,6 +45,7 @@ import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.RemotableViewMethod; +import android.view.TouchDelegate; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -153,6 +154,9 @@ public class ConversationLayout extends FrameLayout private int mFacePileProtectionWidthExpanded; private boolean mImportantConversation; private TextView mUnreadBadge; + private ViewGroup mAppOps; + private Rect mAppOpsTouchRect = new Rect(); + private float mMinTouchSize; public ConversationLayout(@NonNull Context context) { super(context); @@ -191,6 +195,8 @@ public class ConversationLayout extends FrameLayout mConversationIcon = findViewById(R.id.conversation_icon); mConversationIconContainer = findViewById(R.id.conversation_icon_container); mIcon = findViewById(R.id.icon); + mAppOps = findViewById(com.android.internal.R.id.app_ops); + mMinTouchSize = 48 * getResources().getDisplayMetrics().density; mImportanceRingView = findViewById(R.id.conversation_icon_badge_ring); mConversationIconBadge = findViewById(R.id.conversation_icon_badge); mConversationIconBadgeBg = findViewById(R.id.conversation_icon_badge_bg); @@ -871,6 +877,7 @@ public class ConversationLayout extends FrameLayout @RemotableViewMethod public void setSenderTextColor(int color) { mSenderTextColor = color; + mConversationText.setTextColor(color); } /** @@ -1071,6 +1078,47 @@ public class ConversationLayout extends FrameLayout } }); } + if (mAppOps.getWidth() > 0) { + + // Let's increase the touch size of the app ops view if it's here + mAppOpsTouchRect.set( + mAppOps.getLeft(), + mAppOps.getTop(), + mAppOps.getRight(), + mAppOps.getBottom()); + for (int i = 0; i < mAppOps.getChildCount(); i++) { + View child = mAppOps.getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + // Make sure each child has at least a minTouchSize touch target around it + float childTouchLeft = child.getLeft() + child.getWidth() / 2.0f + - mMinTouchSize / 2.0f; + float childTouchRight = childTouchLeft + mMinTouchSize; + mAppOpsTouchRect.left = (int) Math.min(mAppOpsTouchRect.left, + mAppOps.getLeft() + childTouchLeft); + mAppOpsTouchRect.right = (int) Math.max(mAppOpsTouchRect.right, + mAppOps.getLeft() + childTouchRight); + } + + // Increase the height + int heightIncrease = 0; + if (mAppOpsTouchRect.height() < mMinTouchSize) { + heightIncrease = (int) Math.ceil((mMinTouchSize - mAppOpsTouchRect.height()) + / 2.0f); + } + mAppOpsTouchRect.inset(0, -heightIncrease); + + // Let's adjust the hitrect since app ops isn't a direct child + ViewGroup viewGroup = (ViewGroup) mAppOps.getParent(); + while (viewGroup != this) { + mAppOpsTouchRect.offset(viewGroup.getLeft(), viewGroup.getTop()); + viewGroup = (ViewGroup) viewGroup.getParent(); + } + // + // Extend the size of the app opps to be at least 48dp + setTouchDelegate(new TouchDelegate(mAppOpsTouchRect, mAppOps)); + } } public MessagingLinearLayout getMessagingLinearLayout() { diff --git a/core/java/com/android/internal/widget/InlinePresentationStyleUtils.java b/core/java/com/android/internal/widget/InlinePresentationStyleUtils.java new file mode 100644 index 000000000000..264c8bd2303a --- /dev/null +++ b/core/java/com/android/internal/widget/InlinePresentationStyleUtils.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 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.internal.widget; + +import android.annotation.NonNull; +import android.os.Bundle; + +import java.util.Objects; +import java.util.Set; + +/** + * Utility methods relating to inline presentation UI. + */ +public final class InlinePresentationStyleUtils { + + /** + * Returns true if the two bundles are deeply equal. + * + * Each input bundle may represent a UI style in the + * {@link android.widget.inline.InlinePresentationSpec} or the extra + * request info in the {@link android.view.inputmethod.InlineSuggestionsRequest} + * + * Note: this method should not be called in the framework process for security reasons. + */ + public static boolean bundleEquals(@NonNull Bundle bundle1, @NonNull Bundle bundle2) { + if (bundle1 == bundle2) { + return true; + } + if (bundle1 == null || bundle2 == null) { + return false; + } + if (bundle1.size() != bundle2.size()) { + return false; + } + Set<String> keys = bundle1.keySet(); + for (String key : keys) { + Object value1 = bundle1.get(key); + Object value2 = bundle2.get(key); + if (value1 instanceof Bundle && value2 instanceof Bundle + && !bundleEquals((Bundle) value1, (Bundle) value2)) { + return false; + } else if (!Objects.equals(value1, value2)) { + return false; + } + } + return true; + } + + /** + * Private ctor to avoid constructing the class. + */ + private InlinePresentationStyleUtils() { + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index eae614546dfb..451363f6bd3d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3788,10 +3788,9 @@ android:protectionLevel="signature|installer" /> <!-- @SystemApi Allows an application to manage the holders of a role. - @hide - STOPSHIP b/145526313: Remove wellbeing protection flag from MANAGE_ROLE_HOLDERS. --> + @hide --> <permission android:name="android.permission.MANAGE_ROLE_HOLDERS" - android:protectionLevel="signature|installer|wellbeing" /> + android:protectionLevel="signature|installer" /> <!-- @SystemApi Allows an application to observe role holder changes. @hide --> @@ -4713,12 +4712,6 @@ <permission android:name="android.permission.MANAGE_SOUND_TRIGGER" android:protectionLevel="signature|privileged" /> - <!-- Allows preempting sound trigger recognitions for the sake of capturing audio on - implementations which do not support running both concurrently. - @hide --> - <permission android:name="android.permission.PREEMPT_SOUND_TRIGGER" - android:protectionLevel="signature|privileged" /> - <!-- Must be required by system/priv apps implementing sound trigger detection services @hide @SystemApi --> diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml index 581c53fa44ee..b9ca29276cf0 100644 --- a/core/res/res/layout/notification_template_material_conversation.xml +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -136,6 +136,7 @@ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" android:textSize="16sp" android:singleLine="true" + android:layout_weight="1" /> <TextView @@ -166,6 +167,18 @@ /> <ImageView + android:id="@+id/alerted_icon" + android:layout_width="@dimen/notification_alerted_size" + android:layout_height="@dimen/notification_alerted_size" + android:layout_gravity="center" + android:layout_marginStart="4dp" + android:paddingTop="2dp" + android:scaleType="fitCenter" + android:visibility="gone" + android:contentDescription="@string/notification_alerted_content_description" + android:src="@drawable/ic_notifications_alerted"/> + + <ImageView android:id="@+id/profile_badge" android:layout_width="@dimen/notification_badge_size" android:layout_height="@dimen/notification_badge_size" @@ -176,6 +189,44 @@ android:visibility="gone" android:contentDescription="@string/notification_work_profile_content_description" /> + <LinearLayout + android:id="@+id/app_ops" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:paddingTop="3dp" + android:layout_marginStart="2dp" + android:orientation="horizontal" > + <ImageButton + android:layout_marginStart="4dp" + android:id="@+id/camera" + android:layout_width="?attr/notificationHeaderIconSize" + android:layout_height="?attr/notificationHeaderIconSize" + android:src="@drawable/ic_camera" + android:background="?android:selectableItemBackgroundBorderless" + android:visibility="gone" + android:contentDescription="@string/notification_appops_camera_active" + /> + <ImageButton + android:id="@+id/mic" + android:layout_width="?attr/notificationHeaderIconSize" + android:layout_height="?attr/notificationHeaderIconSize" + android:src="@drawable/ic_mic" + android:background="?android:selectableItemBackgroundBorderless" + android:layout_marginStart="4dp" + android:visibility="gone" + android:contentDescription="@string/notification_appops_microphone_active" + /> + <ImageButton + android:id="@+id/overlay" + android:layout_width="?attr/notificationHeaderIconSize" + android:layout_height="?attr/notificationHeaderIconSize" + android:src="@drawable/ic_alert_window_layer" + android:background="?android:selectableItemBackgroundBorderless" + android:layout_marginStart="4dp" + android:visibility="gone" + android:contentDescription="@string/notification_appops_overlay_active" + /> + </LinearLayout> </LinearLayout> <!-- App Name --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 4c4b7e6202f9..b1bba53bd7ab 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1910,6 +1910,9 @@ <!-- The name of the package that will hold the system gallery role. --> <string name="config_systemGallery" translatable="false">com.android.gallery</string> + <!-- The name of the package that will be allowed to change its components' label/icon. --> + <string name="config_overrideComponentUiPackage" translatable="false"></string> + <!-- Enable/disable default bluetooth profiles: HSP_AG, ObexObjectPush, Audio, NAP --> <bool name="config_bluetooth_default_profiles">true</bool> @@ -4418,7 +4421,7 @@ <string name="config_customSessionPolicyProvider"></string> <!-- The max scale for the wallpaper when it's zoomed in --> - <item name="config_wallpaperMaxScale" format="float" type="dimen">1.15</item> + <item name="config_wallpaperMaxScale" format="float" type="dimen">1.10</item> <!-- Package name that will receive an explicit manifest broadcast for android.os.action.POWER_SAVE_MODE_CHANGED. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 42718cba0fc9..ec8058235912 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3961,4 +3961,6 @@ <!-- Set to true to make assistant show in front of the dream/screensaver. --> <java-symbol type="bool" name="config_assistantOnTopOfDream"/> + + <java-symbol type="string" name="config_overrideComponentUiPackage" /> </resources> diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 9d251dac7c78..5f12bf04d931 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -229,7 +229,7 @@ public class InsetsAnimationControlImplTest { doAnswer(invocation -> { mController.applyChangeInsets(mInsetsState); return null; - }).when(mMockController).scheduleApplyChangeInsets(); + }).when(mMockController).scheduleApplyChangeInsets(any()); mController.finish(true /* shown */); assertEquals(Insets.of(0, 100, 100, 0), mController.getCurrentInsets()); verify(mMockController).notifyFinished(eq(mController), eq(true /* shown */)); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java new file mode 100644 index 000000000000..374edb837057 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 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.internal.app; + +import com.android.internal.logging.InstanceId; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.util.FrameworkStatsLog; + +import java.util.ArrayList; +import java.util.List; + +public class ChooserActivityLoggerFake implements ChooserActivityLogger { + static class CallRecord { + // shared fields between all logs + public int atomId; + public String packageName; + public InstanceId instanceId; + + // generic log field + public UiEventLogger.UiEventEnum event; + + // share started fields + public String mimeType; + public int appProvidedDirect; + public int appProvidedApp; + public boolean isWorkprofile; + public int previewType; + public String intent; + + // share completed fields + public int targetType; + public int positionPicked; + + CallRecord(int atomId, UiEventLogger.UiEventEnum eventId, + String packageName, InstanceId instanceId) { + this.atomId = atomId; + this.packageName = packageName; + this.instanceId = instanceId; + this.event = eventId; + } + + CallRecord(int atomId, String packageName, InstanceId instanceId, String mimeType, + int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType, + String intent) { + this.atomId = atomId; + this.packageName = packageName; + this.instanceId = instanceId; + this.mimeType = mimeType; + this.appProvidedDirect = appProvidedDirect; + this.appProvidedApp = appProvidedApp; + this.isWorkprofile = isWorkprofile; + this.previewType = previewType; + this.intent = intent; + } + + CallRecord(int atomId, String packageName, InstanceId instanceId, int targetType, + int positionPicked) { + this.atomId = atomId; + this.packageName = packageName; + this.instanceId = instanceId; + this.targetType = targetType; + this.positionPicked = positionPicked; + } + + } + private List<CallRecord> mCalls = new ArrayList<>(); + + public int numCalls() { + return mCalls.size(); + } + + List<CallRecord> getCalls() { + return mCalls; + } + + CallRecord get(int index) { + return mCalls.get(index); + } + + UiEventLogger.UiEventEnum event(int index) { + return mCalls.get(index).event; + } + + @Override + public void logShareStarted(int eventId, String packageName, String mimeType, + int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType, + String intent) { + mCalls.add(new CallRecord(FrameworkStatsLog.SHARESHEET_STARTED, packageName, + getInstanceId(), mimeType, appProvidedDirect, appProvidedApp, isWorkprofile, + previewType, intent)); + } + + @Override + public void logShareTargetSelected(int targetType, String packageName, int positionPicked) { + mCalls.add(new CallRecord(FrameworkStatsLog.RANKING_SELECTED, packageName, getInstanceId(), + SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), positionPicked)); + } + + @Override + public void log(UiEventLogger.UiEventEnum event, InstanceId instanceId) { + mCalls.add(new CallRecord(FrameworkStatsLog.UI_EVENT_REPORTED, + event, "", instanceId)); + } + + @Override + public InstanceId getInstanceId() { + return InstanceId.fakeInstanceId(-1); + } +} diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index 812e2a6c63fd..be129c7a2f26 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -77,6 +77,7 @@ import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import com.android.internal.app.chooser.DisplayResolveInfo; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.FrameworkStatsLog; import org.junit.Before; import org.junit.Ignore; @@ -1430,6 +1431,251 @@ public class ChooserActivityTest { .check(matches(isDisplayed())); } + @Test + public void testAppTargetLogging() throws InterruptedException { + Intent sendIntent = createSendTextIntent(); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + + final ChooserWrapperActivity activity = mActivityRule + .launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + + assertThat(activity.getAdapter().getCount(), is(2)); + onView(withId(R.id.profile_button)).check(doesNotExist()); + + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + + ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); + onView(withText(toChoose.activityInfo.name)) + .perform(click()); + waitForIdle(); + + ChooserActivityLoggerFake logger = + (ChooserActivityLoggerFake) activity.getChooserActivityLogger(); + assertThat(logger.numCalls(), is(6)); + // first one should be SHARESHEET_TRIGGERED uievent + assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(0).event.getId(), + is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId())); + // second one should be SHARESHEET_STARTED event + assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); + assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); + assertThat(logger.get(1).mimeType, is("text/plain")); + assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).appProvidedApp, is(0)); + assertThat(logger.get(1).appProvidedDirect, is(0)); + assertThat(logger.get(1).isWorkprofile, is(false)); + assertThat(logger.get(1).previewType, is(3)); + // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent + assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(2).event.getId(), + is(ChooserActivityLogger + .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId())); + // fourth and fifth are just artifacts of test set-up + // sixth one should be ranking atom with SHARESHEET_APP_TARGET_SELECTED event + assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED)); + assertThat(logger.get(5).targetType, + is(ChooserActivityLogger + .SharesheetTargetSelectedEvent.SHARESHEET_APP_TARGET_SELECTED.getId())); + } + + @Test + public void testDirectTargetLogging() throws InterruptedException { + Intent sendIntent = createSendTextIntent(); + // We need app targets for direct targets to get displayed + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + + // Create direct share target + List<ChooserTarget> serviceTargets = createDirectShareTargets(1, + resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); + ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0); + + // Start activity + final ChooserWrapperActivity activity = mActivityRule + .launchActivity(Intent.createChooser(sendIntent, null)); + + // Insert the direct share target + Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>(); + directShareToShortcutInfos.put(serviceTargets.get(0), null); + InstrumentationRegistry.getInstrumentation().runOnMainSync( + () -> activity.getAdapter().addServiceResults( + activity.createTestDisplayResolveInfo(sendIntent, + ri, + "testLabel", + "testInfo", + sendIntent, + /* resolveInfoPresentationGetter */ null), + serviceTargets, + TARGET_TYPE_CHOOSER_TARGET, + directShareToShortcutInfos, + null) + ); + // Thread.sleep shouldn't be a thing in an integration test but it's + // necessary here because of the way the code is structured + // TODO: restructure the tests b/129870719 + Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS); + + assertThat("Chooser should have 3 targets (2 apps, 1 direct)", + activity.getAdapter().getCount(), is(3)); + assertThat("Chooser should have exactly one selectable direct target", + activity.getAdapter().getSelectableServiceTargetCount(), is(1)); + assertThat("The resolver info must match the resolver info used to create the target", + activity.getAdapter().getItem(0).getResolveInfo(), is(ri)); + + // Click on the direct target + String name = serviceTargets.get(0).getTitle().toString(); + onView(withText(name)) + .perform(click()); + waitForIdle(); + + ChooserActivityLoggerFake logger = + (ChooserActivityLoggerFake) activity.getChooserActivityLogger(); + assertThat(logger.numCalls(), is(6)); + // first one should be SHARESHEET_TRIGGERED uievent + assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(0).event.getId(), + is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId())); + // second one should be SHARESHEET_STARTED event + assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); + assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); + assertThat(logger.get(1).mimeType, is("text/plain")); + assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).appProvidedApp, is(0)); + assertThat(logger.get(1).appProvidedDirect, is(0)); + assertThat(logger.get(1).isWorkprofile, is(false)); + assertThat(logger.get(1).previewType, is(3)); + // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent + assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(2).event.getId(), + is(ChooserActivityLogger + .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId())); + // fourth and fifth are just artifacts of test set-up + // sixth one should be ranking atom with SHARESHEET_COPY_TARGET_SELECTED event + assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED)); + assertThat(logger.get(5).targetType, + is(ChooserActivityLogger + .SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId())); + } + + @Test + public void testCopyTextToClipboardLogging() throws Exception { + Intent sendIntent = createSendTextIntent(); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent( + Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + + final ChooserWrapperActivity activity = mActivityRule + .launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + + onView(withId(R.id.chooser_copy_button)).check(matches(isDisplayed())); + onView(withId(R.id.chooser_copy_button)).perform(click()); + + ChooserActivityLoggerFake logger = + (ChooserActivityLoggerFake) activity.getChooserActivityLogger(); + assertThat(logger.numCalls(), is(6)); + // first one should be SHARESHEET_TRIGGERED uievent + assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(0).event.getId(), + is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId())); + // second one should be SHARESHEET_STARTED event + assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); + assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); + assertThat(logger.get(1).mimeType, is("text/plain")); + assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).appProvidedApp, is(0)); + assertThat(logger.get(1).appProvidedDirect, is(0)); + assertThat(logger.get(1).isWorkprofile, is(false)); + assertThat(logger.get(1).previewType, is(3)); + // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent + assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(2).event.getId(), + is(ChooserActivityLogger + .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId())); + // fourth and fifth are just artifacts of test set-up + // sixth one should be ranking atom with SHARESHEET_COPY_TARGET_SELECTED event + assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED)); + assertThat(logger.get(5).targetType, + is(ChooserActivityLogger + .SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId())); + } + + @Test + public void testSwitchProfileLogging() throws InterruptedException { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + int workProfileTargets = 4; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(workProfileTargets); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + + final ChooserWrapperActivity activity = + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + onView(withText(R.string.resolver_personal_tab)).perform(click()); + waitForIdle(); + + ChooserActivityLoggerFake logger = + (ChooserActivityLoggerFake) activity.getChooserActivityLogger(); + assertThat(logger.numCalls(), is(8)); + // first one should be SHARESHEET_TRIGGERED uievent + assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(0).event.getId(), + is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId())); + // second one should be SHARESHEET_STARTED event + assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); + assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); + assertThat(logger.get(1).mimeType, is("TestType")); + assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).appProvidedApp, is(0)); + assertThat(logger.get(1).appProvidedDirect, is(0)); + assertThat(logger.get(1).isWorkprofile, is(false)); + assertThat(logger.get(1).previewType, is(3)); + // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent + assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(2).event.getId(), + is(ChooserActivityLogger + .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId())); + // fourth one is artifact of test setup + // fifth one is switch to work profile + assertThat(logger.get(4).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(4).event.getId(), + is(ChooserActivityLogger + .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId())); + // sixth one should be SHARESHEET_APP_LOAD_COMPLETE uievent + assertThat(logger.get(5).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(5).event.getId(), + is(ChooserActivityLogger + .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId())); + // seventh one is artifact of test setup + // eigth one is switch to work profile + assertThat(logger.get(7).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED)); + assertThat(logger.get(7).event.getId(), + is(ChooserActivityLogger + .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId())); + } + private Intent createSendTextIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index 363551bc92fc..5b83f952b745 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -146,6 +146,11 @@ public class ChooserWrapperActivity extends ChooserActivity { } @Override + protected ChooserActivityLogger getChooserActivityLogger() { + return sOverrides.chooserActivityLogger; + } + + @Override public Cursor queryResolver(ContentResolver resolver, Uri uri) { if (sOverrides.resolverCursor != null) { return sOverrides.resolverCursor; @@ -205,6 +210,7 @@ public class ChooserWrapperActivity extends ChooserActivity { public boolean resolverForceException; public Bitmap previewThumbnail; public MetricsLogger metricsLogger; + public ChooserActivityLogger chooserActivityLogger; public int alternateProfileSetting; public Resources resources; public UserHandle workProfileUserHandle; @@ -223,6 +229,7 @@ public class ChooserWrapperActivity extends ChooserActivity { resolverListController = mock(ResolverListController.class); workResolverListController = mock(ResolverListController.class); metricsLogger = mock(MetricsLogger.class); + chooserActivityLogger = new ChooserActivityLoggerFake(); alternateProfileSetting = 0; resources = null; workProfileUserHandle = null; diff --git a/data/etc/platform.xml b/data/etc/platform.xml index f63ec6bd04c3..6af887d401f6 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -162,7 +162,6 @@ <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" /> <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" /> <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" /> - <assign-permission name="android.permission.PREEMPT_SOUND_TRIGGER" uid="audioserver" /> <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" /> diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index f3e4d81285bd..4dd1a29d8595 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -71,8 +71,7 @@ import com.android.internal.util.Preconditions; * heavy-weight work after receiving an update - such as using the network. * * <p>Activities should strongly consider removing all location - * request when entering the background - * (for example at {@link android.app.Activity#onPause}), or + * request when entering the background, or * at least swap the request to a larger interval and lower quality. * Future version of the location manager may automatically perform background * throttling on behalf of applications. @@ -146,38 +145,32 @@ public final class LocationRequest implements Parcelable { */ public static final int POWER_HIGH = 203; - /** - * By default, mFastestInterval = FASTEST_INTERVAL_MULTIPLE * mInterval - */ + private static final long DEFAULT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour private static final double FASTEST_INTERVAL_FACTOR = 6.0; // 6x + @UnsupportedAppUsage + private String mProvider; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private int mQuality = POWER_LOW; + private int mQuality; @UnsupportedAppUsage - private long mInterval = 60 * 60 * 1000; // 60 minutes + private long mInterval; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private long mFastestInterval = (long) (mInterval / FASTEST_INTERVAL_FACTOR); // 10 minutes + private long mFastestInterval; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private boolean mExplicitFastestInterval = false; + private boolean mExplicitFastestInterval; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private long mExpireAt = Long.MAX_VALUE; // no expiry - private long mExpireIn = Long.MAX_VALUE; // no expiry + private long mExpireAt; + private long mExpireIn; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private int mNumUpdates = Integer.MAX_VALUE; // no expiry + private int mNumUpdates; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private float mSmallestDisplacement = 0.0f; // meters + private float mSmallestDisplacement; @UnsupportedAppUsage - private WorkSource mWorkSource = null; + private boolean mHideFromAppOps; + private boolean mLocationSettingsIgnored; + private boolean mLowPowerMode; @UnsupportedAppUsage - private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps - private boolean mLocationSettingsIgnored = false; - - @UnsupportedAppUsage - private String mProvider = LocationManager.FUSED_PROVIDER; - // for deprecated APIs that explicitly request a provider - - /** If true, GNSS chipset will make strong tradeoffs to substantially restrict power use */ - private boolean mLowPowerMode = false; + private @Nullable WorkSource mWorkSource; /** * Create a location request with default parameters. @@ -260,23 +253,71 @@ public final class LocationRequest implements Parcelable { /** @hide */ public LocationRequest() { + this( + /* provider= */ LocationManager.FUSED_PROVIDER, + /* quality= */ POWER_LOW, + /* interval= */ DEFAULT_INTERVAL_MS, + /* fastestInterval= */ (long) (DEFAULT_INTERVAL_MS / FASTEST_INTERVAL_FACTOR), + /* explicitFastestInterval= */ false, + /* expireAt= */ Long.MAX_VALUE, + /* expireIn= */ Long.MAX_VALUE, + /* numUpdates= */ Integer.MAX_VALUE, + /* smallestDisplacement= */ 0, + /* hideFromAppOps= */ false, + /* lowPowerMode= */ false, + /* locationSettingsIgnored= */ false, + /* workSource= */ null); } /** @hide */ public LocationRequest(LocationRequest src) { - mQuality = src.mQuality; - mInterval = src.mInterval; - mFastestInterval = src.mFastestInterval; - mExplicitFastestInterval = src.mExplicitFastestInterval; - mExpireAt = src.mExpireAt; - mExpireIn = src.mExpireIn; - mNumUpdates = src.mNumUpdates; - mSmallestDisplacement = src.mSmallestDisplacement; - mProvider = src.mProvider; - mWorkSource = src.mWorkSource; - mHideFromAppOps = src.mHideFromAppOps; - mLowPowerMode = src.mLowPowerMode; - mLocationSettingsIgnored = src.mLocationSettingsIgnored; + this( + src.mProvider, + src.mQuality, + src.mInterval, + src.mFastestInterval, + src.mExplicitFastestInterval, + src.mExpireAt, + src.mExpireIn, + src.mNumUpdates, + src.mSmallestDisplacement, + src.mHideFromAppOps, + src.mLowPowerMode, + src.mLocationSettingsIgnored, + src.mWorkSource); + } + + private LocationRequest( + @NonNull String provider, + int quality, + long intervalMs, + long fastestIntervalMs, + boolean explicitFastestInterval, + long expireAt, + long expireInMs, + int numUpdates, + float smallestDisplacementM, + boolean hideFromAppOps, + boolean locationSettingsIgnored, + boolean lowPowerMode, + WorkSource workSource) { + Preconditions.checkArgument(provider != null, "invalid provider: null"); + checkQuality(quality); + + mProvider = provider; + mQuality = quality; + mInterval = intervalMs; + mFastestInterval = fastestIntervalMs; + mExplicitFastestInterval = explicitFastestInterval; + mExpireAt = expireAt; + mExpireIn = expireInMs; + mNumUpdates = numUpdates; + mSmallestDisplacement = Preconditions.checkArgumentInRange(smallestDisplacementM, 0, + Float.MAX_VALUE, "smallestDisplacementM"); + mHideFromAppOps = hideFromAppOps; + mLowPowerMode = lowPowerMode; + mLocationSettingsIgnored = locationSettingsIgnored; + mWorkSource = workSource; } /** @@ -567,7 +608,7 @@ public final class LocationRequest implements Parcelable { /** Sets the provider to use for this location request. */ public @NonNull LocationRequest setProvider(@NonNull String provider) { - checkProvider(provider); + Preconditions.checkArgument(provider != null, "invalid provider: null"); mProvider = provider; return this; } @@ -580,9 +621,9 @@ public final class LocationRequest implements Parcelable { /** @hide */ @SystemApi - public @NonNull LocationRequest setSmallestDisplacement(float meters) { - checkDisplacement(meters); - mSmallestDisplacement = meters; + public @NonNull LocationRequest setSmallestDisplacement(float smallestDisplacementM) { + mSmallestDisplacement = Preconditions.checkArgumentInRange(smallestDisplacementM, 0, + Float.MAX_VALUE, "smallestDisplacementM"); return this; } @@ -653,40 +694,24 @@ public final class LocationRequest implements Parcelable { } } - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private static void checkDisplacement(float meters) { - if (meters < 0.0f) { - throw new IllegalArgumentException("invalid displacement: " + meters); - } - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private static void checkProvider(String name) { - if (name == null) { - throw new IllegalArgumentException("invalid provider: null"); - } - } - - public static final @android.annotation.NonNull Parcelable.Creator<LocationRequest> CREATOR = + public static final @NonNull Parcelable.Creator<LocationRequest> CREATOR = new Parcelable.Creator<LocationRequest>() { @Override public LocationRequest createFromParcel(Parcel in) { - LocationRequest request = new LocationRequest(); - request.setQuality(in.readInt()); - request.setFastestInterval(in.readLong()); - request.setInterval(in.readLong()); - request.setExpireAt(in.readLong()); - request.setExpireIn(in.readLong()); - request.setNumUpdates(in.readInt()); - request.setSmallestDisplacement(in.readFloat()); - request.setHideFromAppOps(in.readInt() != 0); - request.setLowPowerMode(in.readInt() != 0); - request.setLocationSettingsIgnored(in.readInt() != 0); - String provider = in.readString(); - if (provider != null) request.setProvider(provider); - WorkSource workSource = in.readParcelable(null); - if (workSource != null) request.setWorkSource(workSource); - return request; + return new LocationRequest( + /* provider= */ in.readString(), + /* quality= */ in.readInt(), + /* interval= */ in.readLong(), + /* fastestInterval= */ in.readLong(), + /* explicitFastestInterval= */ in.readBoolean(), + /* expireAt= */ in.readLong(), + /* expireIn= */ in.readLong(), + /* numUpdates= */ in.readInt(), + /* smallestDisplacement= */ in.readFloat(), + /* hideFromAppOps= */ in.readBoolean(), + /* locationSettingsIgnored= */ in.readBoolean(), + /* lowPowerMode= */ in.readBoolean(), + /* workSource= */ in.readTypedObject(WorkSource.CREATOR)); } @Override @@ -702,18 +727,19 @@ public final class LocationRequest implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mProvider); parcel.writeInt(mQuality); - parcel.writeLong(mFastestInterval); parcel.writeLong(mInterval); + parcel.writeLong(mFastestInterval); + parcel.writeBoolean(mExplicitFastestInterval); parcel.writeLong(mExpireAt); parcel.writeLong(mExpireIn); parcel.writeInt(mNumUpdates); parcel.writeFloat(mSmallestDisplacement); - parcel.writeInt(mHideFromAppOps ? 1 : 0); - parcel.writeInt(mLowPowerMode ? 1 : 0); - parcel.writeInt(mLocationSettingsIgnored ? 1 : 0); - parcel.writeString(mProvider); - parcel.writeParcelable(mWorkSource, 0); + parcel.writeBoolean(mHideFromAppOps); + parcel.writeBoolean(mLocationSettingsIgnored); + parcel.writeBoolean(mLowPowerMode); + parcel.writeTypedObject(mWorkSource, 0); } /** @hide */ @@ -740,16 +766,19 @@ public final class LocationRequest implements Parcelable { @Override public String toString() { StringBuilder s = new StringBuilder(); - s.append("Request[").append(qualityToString(mQuality)); - if (mProvider != null) s.append(' ').append(mProvider); + s.append("Request["); + s.append(qualityToString(mQuality)); + s.append(" ").append(mProvider); if (mQuality != POWER_NONE) { - s.append(" requested="); + s.append(" interval="); TimeUtils.formatDuration(mInterval, s); + if (mExplicitFastestInterval) { + s.append(" fastestInterval="); + TimeUtils.formatDuration(mFastestInterval, s); + } } - s.append(" fastest="); - TimeUtils.formatDuration(mFastestInterval, s); if (mExpireAt != Long.MAX_VALUE) { - s.append(" expireAt=").append(TimeUtils.formatUptime(mExpireAt)); + s.append(" expireAt=").append(TimeUtils.formatRealtime(mExpireAt)); } if (mExpireIn != Long.MAX_VALUE) { s.append(" expireIn="); diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java index 572fbc373730..a81ddfed8194 100644 --- a/location/java/com/android/internal/location/ProviderRequest.java +++ b/location/java/com/android/internal/location/ProviderRequest.java @@ -23,7 +23,6 @@ import android.os.Parcelable; import android.os.WorkSource; import android.util.TimeUtils; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -83,18 +82,14 @@ public final class ProviderRequest implements Parcelable { new Parcelable.Creator<ProviderRequest>() { @Override public ProviderRequest createFromParcel(Parcel in) { - boolean reportLocation = in.readInt() == 1; - long interval = in.readLong(); - boolean lowPowerMode = in.readBoolean(); - boolean locationSettingsIgnored = in.readBoolean(); - int count = in.readInt(); - ArrayList<LocationRequest> locationRequests = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - locationRequests.add(LocationRequest.CREATOR.createFromParcel(in)); - } - WorkSource workSource = in.readParcelable(null); - return new ProviderRequest(reportLocation, interval, lowPowerMode, - locationSettingsIgnored, locationRequests, workSource); + return new ProviderRequest( + /* reportLocation= */ in.readBoolean(), + /* interval= */ in.readLong(), + /* lowPowerMode= */ in.readBoolean(), + /* locationSettingsIgnored= */ in.readBoolean(), + /* locationRequests= */ + in.createTypedArrayList(LocationRequest.CREATOR), + /* workSource= */ in.readTypedObject(WorkSource.CREATOR)); } @Override @@ -110,15 +105,12 @@ public final class ProviderRequest implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeInt(reportLocation ? 1 : 0); + parcel.writeBoolean(reportLocation); parcel.writeLong(interval); parcel.writeBoolean(lowPowerMode); parcel.writeBoolean(locationSettingsIgnored); - parcel.writeInt(locationRequests.size()); - for (LocationRequest request : locationRequests) { - request.writeToParcel(parcel, flags); - } - parcel.writeParcelable(workSource, flags); + parcel.writeTypedList(locationRequests); + parcel.writeTypedObject(workSource, flags); } @Override diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl index a2f9ee906c03..5925d380115c 100644 --- a/media/java/android/media/IMediaRouter2Manager.aidl +++ b/media/java/android/media/IMediaRouter2Manager.aidl @@ -24,8 +24,8 @@ import android.media.RoutingSessionInfo; * {@hide} */ oneway interface IMediaRouter2Manager { - void notifySessionCreated(in RoutingSessionInfo sessionInfo); - void notifySessionsUpdated(); + void notifySessionCreated(int requestId, in RoutingSessionInfo sessionInfo); + void notifySessionUpdated(in RoutingSessionInfo sessionInfo); void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures); void notifyRoutesAdded(in List<MediaRoute2Info> routes); void notifyRoutesRemoved(in List<MediaRoute2Info> routes); diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 88bcd6aaad95..b694fd059bfa 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -75,6 +75,8 @@ public final class MediaRouter2Manager { final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>(); private final AtomicInteger mNextRequestId = new AtomicInteger(1); + private final CopyOnWriteArrayList<TransferRequest> mTransferRequests = + new CopyOnWriteArrayList<>(); /** * Gets an instance of media router manager that controls media route of other applications. @@ -328,6 +330,9 @@ public final class MediaRouter2Manager { if (client != null) { try { int requestId = mNextRequestId.getAndIncrement(); + //TODO: Ensure that every request is eventually removed. + mTransferRequests.add(new TransferRequest(requestId, sessionInfo, route)); + mMediaRouterService.requestCreateSessionWithManager( client, requestId, sessionInfo.getClientPackageName(), route); } catch (RemoteException ex) { @@ -446,6 +451,77 @@ public final class MediaRouter2Manager { } } + void createSessionOnHandler(int requestId, RoutingSessionInfo sessionInfo) { + TransferRequest matchingRequest = null; + for (TransferRequest request : mTransferRequests) { + if (request.mRequestId == requestId) { + matchingRequest = request; + break; + } + } + + if (matchingRequest == null) { + return; + } + + mTransferRequests.remove(matchingRequest); + + MediaRoute2Info requestedRoute = matchingRequest.mTargetRoute; + + if (sessionInfo == null) { + notifyTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute); + return; + } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) { + Log.w(TAG, "The session does not contain the requested route. " + + "(requestedRouteId=" + requestedRoute.getId() + + ", actualRoutes=" + sessionInfo.getSelectedRoutes() + + ")"); + notifyTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute); + return; + } else if (!TextUtils.equals(requestedRoute.getProviderId(), + sessionInfo.getProviderId())) { + Log.w(TAG, "The session's provider ID does not match the requested route's. " + + "(requested route's providerId=" + requestedRoute.getProviderId() + + ", actual providerId=" + sessionInfo.getProviderId() + + ")"); + notifyTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute); + return; + } + notifyTransferred(matchingRequest.mOldSessionInfo, sessionInfo); + } + + void handleFailureOnHandler(int requestId, int reason) { + TransferRequest matchingRequest = null; + for (TransferRequest request : mTransferRequests) { + if (request.mRequestId == requestId) { + matchingRequest = request; + break; + } + } + + if (matchingRequest != null) { + mTransferRequests.remove(matchingRequest); + notifyTransferFailed(matchingRequest.mOldSessionInfo, matchingRequest.mTargetRoute); + return; + } + notifyRequestFailed(reason); + } + + void handleSessionsUpdated(RoutingSessionInfo sessionInfo) { + for (TransferRequest request : mTransferRequests) { + String sessionId = request.mOldSessionInfo.getId(); + if (!TextUtils.equals(sessionId, sessionInfo.getId())) { + continue; + } + if (sessionInfo.getSelectedRoutes().contains(request.mTargetRoute.getId())) { + notifyTransferred(request.mOldSessionInfo, sessionInfo); + mTransferRequests.remove(request); + break; + } + } + notifySessionUpdated(sessionInfo); + } + private void notifyRoutesAdded(List<MediaRoute2Info> routes) { for (CallbackRecord record: mCallbackRecords) { record.mExecutor.execute( @@ -467,16 +543,9 @@ public final class MediaRouter2Manager { } } - void notifySessionCreated(RoutingSessionInfo sessionInfo) { + void notifySessionUpdated(RoutingSessionInfo sessionInfo) { for (CallbackRecord record : mCallbackRecords) { - record.mExecutor.execute(() -> record.mCallback.onSessionCreated( - new RoutingController(sessionInfo))); - } - } - - void notifySessionInfosChanged() { - for (CallbackRecord record : mCallbackRecords) { - record.mExecutor.execute(() -> record.mCallback.onSessionsUpdated()); + record.mExecutor.execute(() -> record.mCallback.onSessionUpdated(sessionInfo)); } } @@ -569,7 +638,7 @@ public final class MediaRouter2Manager { * * @see #getSelectedRoutes(RoutingSessionInfo) * @see #getSelectableRoutes(RoutingSessionInfo) - * @see Callback#onSessionsUpdated() + * @see Callback#onSessionUpdated(RoutingSessionInfo) */ public void selectRoute(@NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) { @@ -614,7 +683,7 @@ public final class MediaRouter2Manager { * * @see #getSelectedRoutes(RoutingSessionInfo) * @see #getDeselectableRoutes(RoutingSessionInfo) - * @see Callback#onSessionsUpdated() + * @see Callback#onSessionUpdated(RoutingSessionInfo) */ public void deselectRoute(@NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) { @@ -667,13 +736,15 @@ public final class MediaRouter2Manager { return; } + int requestId = mNextRequestId.getAndIncrement(); + mTransferRequests.add(new TransferRequest(requestId, sessionInfo, route)); + Client client; synchronized (sLock) { client = mClient; } if (client != null) { try { - int requestId = mNextRequestId.getAndIncrement(); mMediaRouterService.transferToRouteWithManager( mClient, requestId, sessionInfo.getId(), route); } catch (RemoteException ex) { @@ -884,20 +955,12 @@ public final class MediaRouter2Manager { public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {} /** - * Called when a routing session is created. - * - * @param controller the controller to control the created session + * Called when a session is changed. + * @param sessionInfo the updated session */ - public void onSessionCreated(@NonNull RoutingController controller) {} + public void onSessionUpdated(@NonNull RoutingSessionInfo sessionInfo) {} /** - * Called when at least one session info is changed. - * Call {@link #getActiveSessions()} to get current active session info. - */ - public void onSessionsUpdated() {} - - //TODO: Call this. - /** * Called when media is transferred. * * @param oldSession the previous session @@ -906,7 +969,6 @@ public final class MediaRouter2Manager { public void onTransferred(@NonNull RoutingSessionInfo oldSession, @Nullable RoutingSessionInfo newSession) { } - //TODO: Call this. /** * Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails. */ @@ -971,25 +1033,37 @@ public final class MediaRouter2Manager { } } + static final class TransferRequest { + public final int mRequestId; + public final RoutingSessionInfo mOldSessionInfo; + public final MediaRoute2Info mTargetRoute; + + TransferRequest(int requestId, @NonNull RoutingSessionInfo oldSessionInfo, + @NonNull MediaRoute2Info targetRoute) { + mRequestId = requestId; + mOldSessionInfo = oldSessionInfo; + mTargetRoute = targetRoute; + } + } + class Client extends IMediaRouter2Manager.Stub { @Override - public void notifySessionCreated(RoutingSessionInfo sessionInfo) { - mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifySessionCreated, - MediaRouter2Manager.this, sessionInfo)); + public void notifySessionCreated(int requestId, RoutingSessionInfo sessionInfo) { + mHandler.sendMessage(obtainMessage(MediaRouter2Manager::createSessionOnHandler, + MediaRouter2Manager.this, requestId, sessionInfo)); } @Override - public void notifySessionsUpdated() { - mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifySessionInfosChanged, - MediaRouter2Manager.this)); - // do nothing + public void notifySessionUpdated(RoutingSessionInfo sessionInfo) { + mHandler.sendMessage(obtainMessage(MediaRouter2Manager::handleSessionsUpdated, + MediaRouter2Manager.this, sessionInfo)); } @Override public void notifyRequestFailed(int requestId, int reason) { // Note: requestId is not used. - mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyRequestFailed, - MediaRouter2Manager.this, reason)); + mHandler.sendMessage(obtainMessage(MediaRouter2Manager::handleFailureOnHandler, + MediaRouter2Manager.this, requestId, reason)); } @Override diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java index 2e038e665520..68f2964dbeb2 100644 --- a/media/java/android/media/RouteDiscoveryPreference.java +++ b/media/java/android/media/RouteDiscoveryPreference.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import java.util.ArrayList; import java.util.Collection; @@ -29,6 +30,7 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; /** * A media route discovery preference describing the features of routes that media router @@ -169,8 +171,9 @@ public final class RouteDiscoveryPreference implements Parcelable { Bundle mExtras; public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) { - mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures, - "preferredFeatures must not be null")); + Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null"); + mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str)) + .collect(Collectors.toList()); mActiveScan = activeScan; } @@ -211,8 +214,9 @@ public final class RouteDiscoveryPreference implements Parcelable { */ @NonNull public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) { - mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures, - "preferredFeatures must not be null")); + Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null"); + mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str)) + .collect(Collectors.toList()); return this; } diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl index 80333070b7ce..06c39071cdf5 100644 --- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl +++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl @@ -39,10 +39,4 @@ interface ISoundTriggerMiddlewareService { * one of the handles from the returned list. */ ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback); - - /** - * Notify the service that external input capture is taking place. This may cause some of the - * active recognitions to be aborted. - */ - void setExternalCaptureState(boolean active); }
\ No newline at end of file diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index d4494acb7e7a..cf1f1b509ad6 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -602,6 +602,9 @@ public class Tuner implements AutoCloseable { */ @Nullable public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) { + if (mFrontend == null) { + throw new IllegalStateException("frontend is not initialized"); + } return nativeGetFrontendStatus(statusTypes); } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 4a7e8e1fa151..312e5fe14c37 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -124,7 +124,11 @@ using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtGuardInterval; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtSettings; +using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus; using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo; +using ::android::hardware::tv::tuner::V1_0::FrontendStatus; +using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo; +using ::android::hardware::tv::tuner::V1_0::FrontendStatusType; using ::android::hardware::tv::tuner::V1_0::FrontendType; using ::android::hardware::tv::tuner::V1_0::ITuner; using ::android::hardware::tv::tuner::V1_0::LnbPosition; @@ -1453,6 +1457,254 @@ jobject JTuner::getDemuxCaps() { numBytesInSectionFilter, filterCaps, linkCaps, bTimeFilter); } +jobject JTuner::getFrontendStatus(jintArray types) { + if (mFe == NULL) { + return NULL; + } + JNIEnv *env = AndroidRuntime::getJNIEnv(); + jsize size = env->GetArrayLength(types); + std::vector<FrontendStatusType> v(size); + env->GetIntArrayRegion(types, 0, size, reinterpret_cast<jint*>(&v[0])); + + Result res; + hidl_vec<FrontendStatus> status; + mFe->getStatus(v, + [&](Result r, const hidl_vec<FrontendStatus>& s) { + res = r; + status = s; + }); + if (res != Result::SUCCESS) { + return NULL; + } + + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendStatus"); + jmethodID init = env->GetMethodID(clazz, "<init>", "()V"); + jobject statusObj = env->NewObject(clazz, init); + + jclass intClazz = env->FindClass("java/lang/Integer"); + jmethodID initInt = env->GetMethodID(intClazz, "<init>", "(I)V"); + jclass booleanClazz = env->FindClass("java/lang/Boolean"); + jmethodID initBoolean = env->GetMethodID(booleanClazz, "<init>", "(Z)V"); + + for (auto s : status) { + switch(s.getDiscriminator()) { + case FrontendStatus::hidl_discriminator::isDemodLocked: { + jfieldID field = env->GetFieldID(clazz, "mIsDemodLocked", "Ljava/lang/Boolean;"); + jobject newBooleanObj = env->NewObject( + booleanClazz, initBoolean, static_cast<jboolean>(s.isDemodLocked())); + env->SetObjectField(statusObj, field, newBooleanObj); + break; + } + case FrontendStatus::hidl_discriminator::snr: { + jfieldID field = env->GetFieldID(clazz, "mSnr", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.snr())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::ber: { + jfieldID field = env->GetFieldID(clazz, "mBer", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.ber())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::per: { + jfieldID field = env->GetFieldID(clazz, "mPer", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.per())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::preBer: { + jfieldID field = env->GetFieldID(clazz, "mPerBer", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.preBer())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::signalQuality: { + jfieldID field = env->GetFieldID(clazz, "mSignalQuality", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.signalQuality())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::signalStrength: { + jfieldID field = env->GetFieldID(clazz, "mSignalStrength", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.signalStrength())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::symbolRate: { + jfieldID field = env->GetFieldID(clazz, "mSymbolRate", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.symbolRate())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::innerFec: { + jfieldID field = env->GetFieldID(clazz, "mInnerFec", "Ljava/lang/Long;"); + jclass longClazz = env->FindClass("java/lang/Long"); + jmethodID initLong = env->GetMethodID(longClazz, "<init>", "(J)V"); + jobject newLongObj = env->NewObject( + longClazz, initLong, static_cast<jlong>(s.innerFec())); + env->SetObjectField(statusObj, field, newLongObj); + break; + } + case FrontendStatus::hidl_discriminator::modulation: { + jfieldID field = env->GetFieldID(clazz, "mModulation", "Ljava/lang/Integer;"); + FrontendModulationStatus modulation = s.modulation(); + jint intModulation; + bool valid = true; + switch(modulation.getDiscriminator()) { + case FrontendModulationStatus::hidl_discriminator::dvbc: { + intModulation = static_cast<jint>(modulation.dvbc()); + break; + } + case FrontendModulationStatus::hidl_discriminator::dvbs: { + intModulation = static_cast<jint>(modulation.dvbs()); + break; + } + case FrontendModulationStatus::hidl_discriminator::isdbs: { + intModulation = static_cast<jint>(modulation.isdbs()); + break; + } + case FrontendModulationStatus::hidl_discriminator::isdbs3: { + intModulation = static_cast<jint>(modulation.isdbs3()); + break; + } + case FrontendModulationStatus::hidl_discriminator::isdbt: { + intModulation = static_cast<jint>(modulation.isdbt()); + break; + } + default: { + valid = false; + break; + } + } + if (valid) { + jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation); + env->SetObjectField(statusObj, field, newIntegerObj); + } + break; + } + case FrontendStatus::hidl_discriminator::inversion: { + jfieldID field = env->GetFieldID(clazz, "mInversion", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.inversion())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::lnbVoltage: { + jfieldID field = env->GetFieldID(clazz, "mLnbVoltage", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.lnbVoltage())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::plpId: { + jfieldID field = env->GetFieldID(clazz, "mPlpId", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.plpId())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::isEWBS: { + jfieldID field = env->GetFieldID(clazz, "mIsEwbs", "Ljava/lang/Boolean;"); + jobject newBooleanObj = env->NewObject( + booleanClazz, initBoolean, static_cast<jboolean>(s.isEWBS())); + env->SetObjectField(statusObj, field, newBooleanObj); + break; + } + case FrontendStatus::hidl_discriminator::agc: { + jfieldID field = env->GetFieldID(clazz, "mAgc", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.agc())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::isLnaOn: { + jfieldID field = env->GetFieldID(clazz, "mIsLnaOn", "Ljava/lang/Boolean;"); + jobject newBooleanObj = env->NewObject( + booleanClazz, initBoolean, static_cast<jboolean>(s.isLnaOn())); + env->SetObjectField(statusObj, field, newBooleanObj); + break; + } + case FrontendStatus::hidl_discriminator::isLayerError: { + jfieldID field = env->GetFieldID(clazz, "mIsLayerErrors", "[Z"); + hidl_vec<bool> layerErr = s.isLayerError(); + + jbooleanArray valObj = env->NewBooleanArray(layerErr.size()); + + for (size_t i = 0; i < layerErr.size(); i++) { + jboolean x = layerErr[i]; + env->SetBooleanArrayRegion(valObj, i, 1, &x); + } + env->SetObjectField(statusObj, field, valObj); + break; + } + case FrontendStatus::hidl_discriminator::mer: { + jfieldID field = env->GetFieldID(clazz, "mMer", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.mer())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::freqOffset: { + jfieldID field = env->GetFieldID(clazz, "mFreqOffset", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.freqOffset())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::hierarchy: { + jfieldID field = env->GetFieldID(clazz, "mHierarchy", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.hierarchy())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::isRfLocked: { + jfieldID field = env->GetFieldID(clazz, "mIsRfLocked", "Ljava/lang/Boolean;"); + jobject newBooleanObj = env->NewObject( + booleanClazz, initBoolean, static_cast<jboolean>(s.isRfLocked())); + env->SetObjectField(statusObj, field, newBooleanObj); + break; + } + case FrontendStatus::hidl_discriminator::plpInfo: { + jfieldID field = env->GetFieldID(clazz, "mPlpInfo", + "[Landroid/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo;"); + jclass plpClazz = env->FindClass( + "android/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo"); + jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZI)V"); + + hidl_vec<FrontendStatusAtsc3PlpInfo> plpInfos = s.plpInfo(); + + jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, NULL); + for (int i = 0; i < plpInfos.size(); i++) { + auto info = plpInfos[i]; + jint plpId = (jint) info.plpId; + jboolean isLocked = (jboolean) info.isLocked; + jint uec = (jint) info.uec; + + jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec); + env->SetObjectArrayElement(valObj, i, plpObj); + } + + env->SetObjectField(statusObj, field, valObj); + break; + } + default: { + break; + } + } + } + + return statusObj; +} + } // namespace android //////////////////////////////////////////////////////////////////////////////// @@ -2086,8 +2338,10 @@ static int android_media_tv_Tuner_set_lna(JNIEnv *env, jobject thiz, jboolean en return tuner->setLna(enable); } -static jobject android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) { - return NULL; +static jobject android_media_tv_Tuner_get_frontend_status( + JNIEnv* env, jobject thiz, jintArray types) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->getFrontendStatus(types); } static jobject android_media_tv_Tuner_get_av_sync_hw_id( diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 7e860b9c872f..e6f10b24c840 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -187,6 +187,7 @@ struct JTuner : public RefBase { jobject openDescrambler(); jobject openDvr(DvrType type, jlong bufferSize); jobject getDemuxCaps(); + jobject getFrontendStatus(jintArray types); protected: Result openDemux(); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java index 77e8f9719294..6ca564fb34cc 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java @@ -231,9 +231,10 @@ public class MediaRouter2ManagerTest { addRouterCallback(new RouteCallback() {}); addManagerCallback(new MediaRouter2Manager.Callback() { @Override - public void onSessionCreated(MediaRouter2Manager.RoutingController controller) { - if (TextUtils.equals(mPackageName, controller.getClientPackageName()) - && createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) { + public void onTransferred(RoutingSessionInfo oldSessionInfo, + RoutingSessionInfo newSessionInfo) { + if (TextUtils.equals(mPackageName, newSessionInfo.getClientPackageName()) + && newSessionInfo.getSelectedRoutes().contains(ROUTE_ID1)) { latch.countDown(); } } @@ -268,8 +269,9 @@ public class MediaRouter2ManagerTest { addManagerCallback(new MediaRouter2Manager.Callback() { @Override - public void onSessionCreated(MediaRouter2Manager.RoutingController controller) { - assertNotNull(controller); + public void onTransferred(RoutingSessionInfo oldSessionInfo, + RoutingSessionInfo newSessionInfo) { + assertNotNull(newSessionInfo); onSessionCreatedLatch.countDown(); } }); @@ -352,8 +354,9 @@ public class MediaRouter2ManagerTest { // create a controller addManagerCallback(new MediaRouter2Manager.Callback() { @Override - public void onSessionCreated(MediaRouter2Manager.RoutingController controller) { - assertNotNull(controller); + public void onTransferred(RoutingSessionInfo oldSessionInfo, + RoutingSessionInfo newSessionInfo) { + assertNotNull(newSessionInfo); onSessionCreatedLatch.countDown(); } }); @@ -383,13 +386,12 @@ public class MediaRouter2ManagerTest { addManagerCallback(new MediaRouter2Manager.Callback() { @Override - public void onSessionsUpdated() { - List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName); - if (sessions.size() != 2) { + public void onSessionUpdated(RoutingSessionInfo updatedSessionInfo) { + if (!TextUtils.equals(sessionInfo.getId(), updatedSessionInfo.getId())) { return; } - if (sessions.get(1).getVolume() == targetVolume) { + if (updatedSessionInfo.getVolume() == targetVolume) { volumeChangedLatch.countDown(); } } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 02604d870986..cd45fc908db4 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -65,6 +65,7 @@ import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.SuperStatusBarViewFactory; @@ -225,6 +226,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt KeyguardIndicationController keyguardIndicationController, DismissCallbackRegistry dismissCallbackRegistry, StatusBarTouchableRegionManager statusBarTouchableRegionManager, + Lazy<NotificationShadeDepthController> depthControllerLazy, /* Car Settings injected components. */ CarNavigationBarController carNavigationBarController) { super( @@ -304,6 +306,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt phoneStatusBarPolicy, keyguardIndicationController, dismissCallbackRegistry, + depthControllerLazy, statusBarTouchableRegionManager); mUserSwitcherController = userSwitcherController; mScrimController = scrimController; diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java index 1baa1f6891ee..e163173daefb 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.SuperStatusBarViewFactory; @@ -200,6 +201,7 @@ public class CarStatusBarModule { KeyguardIndicationController keyguardIndicationController, DismissCallbackRegistry dismissCallbackRegistry, StatusBarTouchableRegionManager statusBarTouchableRegionManager, + Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy, CarNavigationBarController carNavigationBarController) { return new CarStatusBar( context, @@ -278,6 +280,7 @@ public class CarStatusBarModule { keyguardIndicationController, dismissCallbackRegistry, statusBarTouchableRegionManager, + notificationShadeDepthControllerLazy, carNavigationBarController); } } diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 5d363f34bc67..68bd4071de05 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -351,9 +351,6 @@ public class BugreportProgressService extends Service { @Override public void onProgress(float progress) { synchronized (mLock) { - if (progress == 0) { - trackInfoWithIdLocked(); - } checkProgressUpdatedLocked(mInfo, (int) progress); } } @@ -365,7 +362,6 @@ public class BugreportProgressService extends Service { @Override public void onError(@BugreportErrorCode int errorCode) { synchronized (mLock) { - trackInfoWithIdLocked(); stopProgressLocked(mInfo.id); } Log.e(TAG, "Bugreport API callback onError() errorCode = " + errorCode); @@ -382,10 +378,10 @@ public class BugreportProgressService extends Service { } /** - * Reads bugreport id and links it to the bugreport info to track the bugreport's - * progress/completion/error. id is incremented in dumpstate code. This function is called - * when dumpstate calls one of the callback functions (onProgress, onFinished, onError) - * after the id has been incremented. + * Reads bugreport id and links it to the bugreport info to track a bugreport that is in + * process. id is incremented in the dumpstate code. + * We do not track a bugreport if there is already a bugreport with the same id being + * tracked. */ @GuardedBy("mLock") private void trackInfoWithIdLocked() { @@ -408,7 +404,6 @@ public class BugreportProgressService extends Service { sendRemoteBugreportFinishedBroadcast(mContext, bugreportFilePath, mInfo.bugreportFile); } else { - trackInfoWithIdLocked(); cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir); final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED); intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath); @@ -638,8 +633,11 @@ public class BugreportProgressService extends Service { BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(info); try { - mBugreportManager.startBugreport(bugreportFd, screenshotFd, - new BugreportParams(bugreportType), executor, bugreportCallback); + synchronized (mLock) { + mBugreportManager.startBugreport(bugreportFd, screenshotFd, + new BugreportParams(bugreportType), executor, bugreportCallback); + bugreportCallback.trackInfoWithIdLocked(); + } } catch (RuntimeException e) { Log.i(TAG, "Error in generating bugreports: ", e); // The binder call didn't go through successfully, so need to close the fds. @@ -756,7 +754,7 @@ public class BugreportProgressService extends Service { != (info.lastProgress.intValue() / LOG_PROGRESS_STEP))) { Log.d(TAG, "Progress #" + info.id + ": " + percentageText); } - info.lastProgress = new AtomicInteger(progress); + info.lastProgress.set(progress); sendForegroundabledNotification(info.id, builder.build()); } @@ -1025,7 +1023,7 @@ public class BugreportProgressService extends Service { } Log.d(TAG, "Bugreport finished with title: " + info.getTitle() + " and shareDescription: " + info.shareDescription); - info.finished = new AtomicBoolean(true); + info.finished.set(true); synchronized (mLock) { // Stop running on foreground, otherwise share notification cannot be dismissed. @@ -1809,18 +1807,18 @@ public class BugreportProgressService extends Service { * Current value of progress (in percentage) of the bugreport generation as * displayed by the UI. */ - AtomicInteger progress = new AtomicInteger(0); + final AtomicInteger progress = new AtomicInteger(0); /** * Last value of progress (in percentage) of the bugreport generation for which * system notification was updated. */ - AtomicInteger lastProgress = new AtomicInteger(0); + final AtomicInteger lastProgress = new AtomicInteger(0); /** * Time of the last progress update. */ - AtomicLong lastUpdate = new AtomicLong(System.currentTimeMillis()); + final AtomicLong lastUpdate = new AtomicLong(System.currentTimeMillis()); /** * Time of the last progress update when Parcel was created. @@ -1840,7 +1838,7 @@ public class BugreportProgressService extends Service { /** * Whether dumpstate sent an intent informing it has finished. */ - AtomicBoolean finished = new AtomicBoolean(false); + final AtomicBoolean finished = new AtomicBoolean(false); /** * Whether the details entries have been added to the bugreport yet. @@ -2075,8 +2073,8 @@ public class BugreportProgressService extends Service { initialName = in.readString(); title = in.readString(); description = in.readString(); - progress = new AtomicInteger(in.readInt()); - lastUpdate = new AtomicLong(in.readLong()); + progress.set(in.readInt()); + lastUpdate.set(in.readLong()); formattedLastUpdate = in.readString(); bugreportFile = readFile(in); @@ -2085,7 +2083,7 @@ public class BugreportProgressService extends Service { screenshotFiles.add(readFile(in)); } - finished = new AtomicBoolean(in.readInt() == 1); + finished.set(in.readInt() == 1); screenshotCounter = in.readInt(); shareDescription = in.readString(); shareTitle = in.readString(); @@ -2157,8 +2155,8 @@ public class BugreportProgressService extends Service { + ") from " + info.progress.intValue() + " to " + progress); } } - info.progress = new AtomicInteger(progress); - info.lastUpdate = new AtomicLong(System.currentTimeMillis()); + info.progress.set(progress); + info.lastUpdate.set(System.currentTimeMillis()); updateProgress(info); } diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 23be78bd6a77..a9e5fa9cf4ae 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -477,6 +477,7 @@ <dimen name="qs_tile_height">106dp</dimen> <dimen name="qs_tile_layout_margin_side">6dp</dimen> <dimen name="qs_tile_margin_horizontal">18dp</dimen> + <dimen name="qs_tile_margin_horizontal_two_line">2dp</dimen> <dimen name="qs_tile_margin_vertical">24dp</dimen> <dimen name="qs_tile_margin_top_bottom">12dp</dimen> <dimen name="qs_tile_margin_top_bottom_negative">-12dp</dimen> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java index fc29f5cddb26..2f103940f3e4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java @@ -57,27 +57,27 @@ public interface KeyguardViewController { /** * Called when the device started going to sleep. */ - void onStartedGoingToSleep(); + default void onStartedGoingToSleep() {}; /** * Called when the device has finished going to sleep. */ - void onFinishedGoingToSleep(); + default void onFinishedGoingToSleep() {}; /** * Called when the device started waking up. */ - void onStartedWakingUp(); + default void onStartedWakingUp() {}; /** * Called when the device started turning on. */ - void onScreenTurningOn(); + default void onScreenTurningOn() {}; /** * Called when the device has finished turning on. */ - void onScreenTurnedOn(); + default void onScreenTurnedOn() {}; /** * Sets whether the Keyguard needs input. diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index cc4ee89f2208..f6368c466e91 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -109,7 +109,7 @@ public class AssistManager { protected static final String CONSTRAINED_KEY = "should_constrain"; public static final int INVOCATION_TYPE_GESTURE = 1; - public static final int INVOCATION_TYPE_ACTIVE_EDGE = 2; + public static final int INVOCATION_TYPE_OTHER = 2; public static final int INVOCATION_TYPE_VOICE = 3; public static final int INVOCATION_TYPE_QUICK_SEARCH_BAR = 4; public static final int INVOCATION_HOME_BUTTON_LONG_PRESS = 5; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 5c66462f2a5b..496456deccee 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -125,6 +125,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList // Custom options so there is no activity transition animation ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(), 0 /* enterResId */, 0 /* exitResId */); + options.setTaskAlwaysOnTop(true); // Post to keep the lifecycle normal post(() -> { if (DEBUG_BUBBLE_EXPANDED_VIEW) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 3bed3384c91f..f7f9afdd2928 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -16,6 +16,9 @@ package com.android.systemui.doze; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; + import android.annotation.MainThread; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Trace; @@ -368,8 +371,8 @@ public class DozeMachine { case DOZE_PULSE_DONE: final State nextState; @Wakefulness int wakefulness = mWakefulnessLifecycle.getWakefulness(); - if (wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE - || wakefulness == WakefulnessLifecycle.WAKEFULNESS_WAKING) { + if (state != State.INITIALIZED && (wakefulness == WAKEFULNESS_AWAKE + || wakefulness == WAKEFULNESS_WAKING)) { nextState = State.FINISH; } else if (mDockManager.isDocked()) { nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED; diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt index f710f7fc47e2..448531a132df 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt @@ -25,13 +25,18 @@ import com.android.systemui.qs.TileLayout.exactly class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTileLayout { + companion object { + private const val NUM_LINES = 2 + } + protected val mRecords = ArrayList<QSPanel.TileRecord>() private var _listening = false private var smallTileSize = 0 private val twoLineHeight - get() = smallTileSize * 2 + cellMarginVertical + get() = smallTileSize * NUM_LINES + cellMarginVertical * (NUM_LINES - 1) private var cellMarginHorizontal = 0 private var cellMarginVertical = 0 + private var tilesToShow = 0 init { isFocusableInTouchMode = true @@ -68,7 +73,7 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil override fun updateResources(): Boolean { with(mContext.resources) { smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size) - cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) + cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal_two_line) cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin) } requestLayout() @@ -83,11 +88,12 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil } } - override fun getNumVisibleTiles() = mRecords.size + override fun getNumVisibleTiles() = tilesToShow override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) updateResources() + postInvalidate() } override fun onFinishInflate() { @@ -95,39 +101,58 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - var previousView: View = this - var tiles = 0 mRecords.forEach { - val tileView = it.tileView - if (tileView.visibility != View.GONE) { - tileView.updateAccessibilityOrder(previousView) - previousView = tileView - tiles++ - tileView.measure(exactly(smallTileSize), exactly(smallTileSize)) - } + it.tileView.measure(exactly(smallTileSize), exactly(smallTileSize)) } val height = twoLineHeight - val columns = tiles / 2 - val width = paddingStart + paddingEnd + - columns * smallTileSize + - (columns - 1) * cellMarginHorizontal - setMeasuredDimension(width, height) + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height) + } + + private fun calculateMaxColumns(availableWidth: Int): Int { + if (smallTileSize + cellMarginHorizontal == 0) { + return 0 + } else { + return (availableWidth - smallTileSize) / (smallTileSize + cellMarginHorizontal) + 1 + } } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { - val tiles = mRecords.filter { it.tileView.visibility != View.GONE } - tiles.forEachIndexed { - index, tile -> - val column = index % (tiles.size / 2) - val left = getLeftForColumn(column) - val top = if (index < tiles.size / 2) 0 else getTopBottomRow() - tile.tileView.layout(left, top, left + smallTileSize, top + smallTileSize) + val availableWidth = r - l - paddingLeft - paddingRight + val maxColumns = calculateMaxColumns(availableWidth) + val actualColumns = Math.min(maxColumns, mRecords.size / NUM_LINES) + if (actualColumns == 0) { + // No tileSize or horizontal margin + return + } + tilesToShow = actualColumns * NUM_LINES + + val interTileSpace = if (actualColumns <= 2) { + // Extra "column" of padding to be distributed on each end + (availableWidth - actualColumns * smallTileSize) / actualColumns + } else { + (availableWidth - actualColumns * smallTileSize) / (actualColumns - 1) + } + + for (index in 0 until mRecords.size) { + val tileView = mRecords[index].tileView + if (index >= tilesToShow) { + tileView.visibility = View.GONE + } else { + tileView.visibility = View.VISIBLE + if (index > 0) tileView.updateAccessibilityOrder(mRecords[index - 1].tileView) + val column = index % actualColumns + val left = getLeftForColumn(column, interTileSpace, actualColumns <= 2) + val top = if (index < actualColumns) 0 else getTopBottomRow() + tileView.layout(left, top, left + smallTileSize, top + smallTileSize) + } } } - private fun getLeftForColumn(column: Int) = column * (smallTileSize + cellMarginHorizontal) + private fun getLeftForColumn(column: Int, interSpace: Int, sideMargin: Boolean): Int { + return (if (sideMargin) interSpace / 2 else 0) + column * (smallTileSize + interSpace) + } private fun getTopBottomRow() = smallTileSize + cellMarginVertical }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index a978cad1127a..fd44f04a0d80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -32,6 +32,7 @@ import com.android.systemui.Dumpable import com.android.systemui.Interpolators import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.notification.ActivityLaunchAnimator import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK import com.android.systemui.statusbar.phone.NotificationShadeWindowController @@ -39,7 +40,6 @@ import com.android.systemui.statusbar.phone.PanelExpansionListener import com.android.systemui.statusbar.policy.KeyguardStateController import java.io.FileDescriptor import java.io.PrintWriter -import java.lang.IllegalArgumentException import javax.inject.Inject import javax.inject.Singleton import kotlin.math.max @@ -69,11 +69,41 @@ class NotificationShadeDepthController @Inject constructor( private var notificationAnimator: Animator? = null private var updateScheduled: Boolean = false private var shadeExpansion = 0f + private var ignoreShadeBlurUntilHidden: Boolean = false @VisibleForTesting var shadeSpring = DepthAnimation() @VisibleForTesting var globalActionsSpring = DepthAnimation() + @VisibleForTesting + var brightnessMirrorSpring = DepthAnimation() + var brightnessMirrorVisible: Boolean = false + set(value) { + field = value + brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f) + else 0) + } + + /** + * When launching an app from the shade, the animations progress should affect how blurry the + * shade is, overriding the expansion amount. + */ + var notificationLaunchAnimationParams: ActivityLaunchAnimator.ExpandAnimationParameters? = null + set(value) { + field = value + if (value != null) { + scheduleUpdate() + return + } + + if (shadeSpring.radius == 0) { + return + } + ignoreShadeBlurUntilHidden = true + shadeSpring.animateTo(0) + shadeSpring.finishIfRunning() + } + /** * Blur radius of the wake-up animation on this frame. */ @@ -91,7 +121,19 @@ class NotificationShadeDepthController @Inject constructor( val updateBlurCallback = Choreographer.FrameCallback { updateScheduled = false - val blur = max(max(shadeSpring.radius, wakeAndUnlockBlurRadius), globalActionsSpring.radius) + var shadeRadius = max(shadeSpring.radius, wakeAndUnlockBlurRadius).toFloat() + shadeRadius *= 1f - brightnessMirrorSpring.ratio + val launchProgress = notificationLaunchAnimationParams?.linearProgress ?: 0f + shadeRadius *= (1f - launchProgress) * (1f - launchProgress) + + if (ignoreShadeBlurUntilHidden) { + if (shadeRadius == 0f) { + ignoreShadeBlurUntilHidden = false + } else { + shadeRadius = 0f + } + } + val blur = max(shadeRadius.toInt(), globalActionsSpring.radius) blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur) try { wallpaperManager.setWallpaperZoomOut(root.windowToken, @@ -148,6 +190,7 @@ class NotificationShadeDepthController @Inject constructor( if (isDozing) { shadeSpring.finishIfRunning() globalActionsSpring.finishIfRunning() + brightnessMirrorSpring.finishIfRunning() } } } @@ -176,7 +219,6 @@ class NotificationShadeDepthController @Inject constructor( if (statusBarStateController.state == StatusBarState.SHADE) { newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion) } - shadeSpring.animateTo(newBlur) } @@ -199,7 +241,11 @@ class NotificationShadeDepthController @Inject constructor( it.increaseIndent() it.println("shadeRadius: ${shadeSpring.radius}") it.println("globalActionsRadius: ${globalActionsSpring.radius}") + it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}") it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius") + it.println("notificationLaunchAnimationProgress: " + + "${notificationLaunchAnimationParams?.linearProgress}") + it.println("ignoreShadeBlurUntilHidden: $ignoreShadeBlurUntilHidden") } } @@ -212,7 +258,12 @@ class NotificationShadeDepthController @Inject constructor( * Blur radius visible on the UI, in pixels. */ var radius = 0 - private set + + /** + * Depth ratio of the current blur radius. + */ + val ratio + get() = blurUtils.ratioOfBlurRadius(radius) /** * Radius that we're animating to. @@ -239,7 +290,7 @@ class NotificationShadeDepthController @Inject constructor( init { springAnimation.spring = SpringForce(0.0f) springAnimation.spring.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY - springAnimation.spring.stiffness = SpringForce.STIFFNESS_MEDIUM + springAnimation.spring.stiffness = SpringForce.STIFFNESS_HIGH springAnimation.addEndListener { _, _, _, _ -> pendingRadius = -1 } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java index 7c061574f19c..6aef6b407f37 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -34,6 +34,7 @@ import android.view.View; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.systemui.Interpolators; +import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; @@ -57,6 +58,7 @@ public class ActivityLaunchAnimator { private final NotificationListContainer mNotificationContainer; private final float mWindowCornerRadius; private final NotificationShadeWindowViewController mNotificationShadeWindowViewController; + private final NotificationShadeDepthController mDepthController; private Callback mCallback; private final Runnable mTimeoutRunnable = () -> { setAnimationPending(false); @@ -70,9 +72,11 @@ public class ActivityLaunchAnimator { NotificationShadeWindowViewController notificationShadeWindowViewController, Callback callback, NotificationPanelViewController notificationPanel, + NotificationShadeDepthController depthController, NotificationListContainer container) { mNotificationPanel = notificationPanel; mNotificationContainer = container; + mDepthController = depthController; mNotificationShadeWindowViewController = notificationShadeWindowViewController; mCallback = callback; mWindowCornerRadius = ScreenDecorationsUtils @@ -212,7 +216,7 @@ public class ActivityLaunchAnimator { mWindowCornerRadius, progress); applyParamsToWindow(primary); applyParamsToNotification(mParams); - applyParamsToNotificationList(mParams); + applyParamsToNotificationShade(mParams); } }); anim.addListener(new AnimatorListenerAdapter() { @@ -256,14 +260,15 @@ public class ActivityLaunchAnimator { if (!running) { mCallback.onExpandAnimationFinished(mIsFullScreenLaunch); applyParamsToNotification(null); - applyParamsToNotificationList(null); + applyParamsToNotificationShade(null); } } - private void applyParamsToNotificationList(ExpandAnimationParameters params) { + private void applyParamsToNotificationShade(ExpandAnimationParameters params) { mNotificationContainer.applyExpandAnimationParams(params); mNotificationPanel.applyExpandAnimationParams(params); + mDepthController.setNotificationLaunchAnimationParams(params); } private void applyParamsToNotification(ExpandAnimationParameters params) { @@ -295,7 +300,7 @@ public class ActivityLaunchAnimator { }; public static class ExpandAnimationParameters { - float linearProgress; + public float linearProgress; int[] startPosition; float startTranslationZ; int left; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9792defe0e0b..19b5f5c79ea2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1669,8 +1669,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public void showAppOpsIcons(ArraySet<Integer> activeOps) { - if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) { - mChildrenContainer.getHeaderView().showAppOpsIcons(activeOps); + if (mIsSummaryWithChildren) { + mChildrenContainer.showAppOpsIcons(activeOps); } mPrivateLayout.showAppOpsIcons(activeOps); mPublicLayout.showAppOpsIcons(activeOps); @@ -1697,8 +1697,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false); private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) { - if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) { - mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(audiblyAlertedRecently); + if (mIsSummaryWithChildren) { + mChildrenContainer.setRecentlyAudiblyAlerted(audiblyAlertedRecently); } mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 9b9225e0bde0..8efdc1b56e8e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1468,27 +1468,27 @@ public class NotificationContentView extends FrameLayout { } public void showAppOpsIcons(ArraySet<Integer> activeOps) { - if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) { - mContractedWrapper.getNotificationHeader().showAppOpsIcons(activeOps); + if (mContractedChild != null) { + mContractedWrapper.showAppOpsIcons(activeOps); } - if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) { - mExpandedWrapper.getNotificationHeader().showAppOpsIcons(activeOps); + if (mExpandedChild != null) { + mExpandedWrapper.showAppOpsIcons(activeOps); } - if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) { - mHeadsUpWrapper.getNotificationHeader().showAppOpsIcons(activeOps); + if (mHeadsUpChild != null) { + mHeadsUpWrapper.showAppOpsIcons(activeOps); } } /** Sets whether the notification being displayed audibly alerted the user. */ public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) { - if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) { - mContractedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted); + if (mContractedChild != null) { + mContractedWrapper.setRecentlyAudiblyAlerted(audiblyAlerted); } - if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) { - mExpandedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted); + if (mExpandedChild != null) { + mExpandedWrapper.setRecentlyAudiblyAlerted(audiblyAlerted); } - if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) { - mHeadsUpWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted); + if (mHeadsUpChild != null) { + mHeadsUpWrapper.setRecentlyAudiblyAlerted(audiblyAlerted); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index 82e5f0a3b130..8d675f86c343 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -47,6 +47,18 @@ public abstract class StackScrollerDecorView extends ExpandableView { } }; + private boolean mSecondaryAnimating = false; + private final Runnable mSecondaryVisibilityEndRunnable = () -> { + mSecondaryAnimating = false; + // If we were on screen, become GONE to avoid touches + if (mSecondaryView == null) return; + if (getVisibility() != View.GONE + && mSecondaryView.getVisibility() != View.GONE + && !mIsSecondaryVisible) { + mSecondaryView.setVisibility(View.GONE); + } + }; + public StackScrollerDecorView(Context context, AttributeSet attrs) { super(context, attrs); setClipChildren(false); @@ -88,9 +100,11 @@ public abstract class StackScrollerDecorView extends ExpandableView { private void setContentVisible(boolean contentVisible, boolean animate) { if (mContentVisible != contentVisible) { mContentAnimating = animate; - setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable); mContentVisible = contentVisible; - } if (!mContentAnimating) { + setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable); + } + + if (!mContentAnimating) { mContentVisibilityEndRunnable.run(); } } @@ -136,8 +150,13 @@ public abstract class StackScrollerDecorView extends ExpandableView { */ public void setSecondaryVisible(boolean nowVisible, boolean animate) { if (mIsSecondaryVisible != nowVisible) { - setViewVisible(mSecondaryView, nowVisible, animate, null /* endRunnable */); + mSecondaryAnimating = animate; mIsSecondaryVisible = nowVisible; + setViewVisible(mSecondaryView, nowVisible, animate, mSecondaryVisibilityEndRunnable); + } + + if (!mSecondaryAnimating) { + mSecondaryVisibilityEndRunnable.run(); } } @@ -170,6 +189,12 @@ public abstract class StackScrollerDecorView extends ExpandableView { if (view == null) { return; } + + // Make sure we're visible so animations work + if (view.getVisibility() != View.VISIBLE) { + view.setVisibility(View.VISIBLE); + } + // cancel any previous animations view.animate().cancel(); float endValue = nowVisible ? 1.0f : 0.0f; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 7808a4b2dc74..0c311b403c48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.row.wrapper; import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y; +import android.annotation.NonNull; +import android.app.AppOpsManager; import android.app.Notification; import android.content.Context; import android.util.ArraySet; @@ -60,6 +62,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { protected NotificationHeaderView mNotificationHeader; private TextView mHeaderText; private ImageView mWorkProfileImage; + private View mCameraIcon; + private View mMicIcon; + private View mOverlayIcon; + private View mAppOps; + private View mAudiblyAlertedIcon; private boolean mIsLowPriority; private boolean mTransformLowPriorityTitle; @@ -107,6 +114,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button); mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge); mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header); + mCameraIcon = mView.findViewById(com.android.internal.R.id.camera); + mMicIcon = mView.findViewById(com.android.internal.R.id.mic); + mOverlayIcon = mView.findViewById(com.android.internal.R.id.overlay); + mAppOps = mView.findViewById(com.android.internal.R.id.app_ops); + mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon); if (mNotificationHeader != null) { mNotificationHeader.setShowExpandButtonAtEnd(mShowExpandButtonAtEnd); mColor = mNotificationHeader.getOriginalIconColor(); @@ -114,8 +126,35 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } private void addAppOpsOnClickListener(ExpandableNotificationRow row) { + View.OnClickListener listener = row.getAppOpsOnClickListener(); if (mNotificationHeader != null) { - mNotificationHeader.setAppOpsOnClickListener(row.getAppOpsOnClickListener()); + mNotificationHeader.setAppOpsOnClickListener(listener); + } + mAppOps.setOnClickListener(listener); + mCameraIcon.setOnClickListener(listener); + mMicIcon.setOnClickListener(listener); + mOverlayIcon.setOnClickListener(listener); + } + + /** + * Shows or hides 'app op in use' icons based on app usage. + */ + @Override + public void showAppOpsIcons(ArraySet<Integer> appOps) { + if (appOps == null) { + return; + } + if (mOverlayIcon != null) { + mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) + ? View.VISIBLE : View.GONE); + } + if (mCameraIcon != null) { + mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA) + ? View.VISIBLE : View.GONE); + } + if (mMicIcon != null) { + mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO) + ? View.VISIBLE : View.GONE); } } @@ -184,6 +223,18 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mHeaderText); } + if (mCameraIcon != null) { + mTransformationHelper.addViewTransformingToSimilar(mCameraIcon); + } + if (mMicIcon != null) { + mTransformationHelper.addViewTransformingToSimilar(mMicIcon); + } + if (mOverlayIcon != null) { + mTransformationHelper.addViewTransformingToSimilar(mOverlayIcon); + } + if (mAudiblyAlertedIcon != null) { + mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon); + } } @Override @@ -195,6 +246,13 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } @Override + public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) { + if (mAudiblyAlertedIcon != null) { + mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE); + } + } + + @Override public NotificationHeaderView getNotificationHeader() { return mNotificationHeader; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index e4fb2f7c42d4..fa7f282be74a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -29,6 +29,7 @@ import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; +import android.util.ArraySet; import android.view.NotificationHeaderView; import android.view.View; import android.view.ViewGroup; @@ -95,6 +96,14 @@ public abstract class NotificationViewWrapper implements TransformableView { public void onContentUpdated(ExpandableNotificationRow row) { } + /** + * Show a set of app opp icons in the layout. + * + * @param appOps which app ops to show + */ + public void showAppOpsIcons(ArraySet<Integer> appOps) { + } + public void onReinflated() { if (shouldClearBackgroundOnReapply()) { mBackgroundColor = 0; @@ -362,4 +371,10 @@ public abstract class NotificationViewWrapper implements TransformableView { public int getExtraMeasureHeight() { return 0; } + + /** + * Set the view to have recently visibly alerted. + */ + public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 3d0bf3f4c1c6..400e794b820b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -22,6 +22,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.drawable.ColorDrawable; import android.service.notification.StatusBarNotification; +import android.util.ArraySet; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.NotificationHeaderView; @@ -1265,4 +1266,27 @@ public class NotificationChildrenContainer extends ViewGroup { mHeaderVisibleAmount = headerVisibleAmount; mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader); } + + /** + * Show a set of app opp icons in the layout. + * + * @param appOps which app ops to show + */ + public void showAppOpsIcons(ArraySet<Integer> appOps) { + if (mNotificationHeaderWrapper != null) { + mNotificationHeaderWrapper.showAppOpsIcons(appOps); + } + if (mNotificationHeaderWrapperLowPriority != null) { + mNotificationHeaderWrapperLowPriority.showAppOpsIcons(appOps); + } + } + + public void setRecentlyAudiblyAlerted(boolean audiblyAlertedRecently) { + if (mNotificationHeaderWrapper != null) { + mNotificationHeaderWrapper.setRecentlyAudiblyAlerted(audiblyAlertedRecently); + } + if (mNotificationHeaderWrapperLowPriority != null) { + mNotificationHeaderWrapperLowPriority.setRecentlyAudiblyAlerted(audiblyAlertedRecently); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index be8cd6a6481c..31797d1faa61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; +import static java.lang.Float.isNaN; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -2009,7 +2011,12 @@ public class NotificationPanelViewController extends PanelViewController { @Override protected float getOverExpansionAmount() { - return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); + float result = mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); + if (isNaN(result)) { + Log.wtf(TAG, "OverExpansionAmount is NaN!"); + } + + return result; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 83cc4e33e2db..f7d403f667cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static java.lang.Float.isNaN; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -638,6 +640,9 @@ public abstract class PanelViewController { } public void setExpandedHeightInternal(float h) { + if (isNaN(h)) { + Log.wtf(TAG, "ExpandedHeight set to NaN"); + } if (mExpandLatencyTracking && h != 0f) { DejankUtils.postAfterTraversal( () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); 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 119662c75857..fa55b74606c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -176,6 +176,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.PulseExpansionHandler; @@ -589,6 +590,7 @@ public class StatusBar extends SystemUI implements DemoMode, private ActivityLaunchAnimator mActivityLaunchAnimator; protected StatusBarNotificationPresenter mPresenter; private NotificationActivityStarter mNotificationActivityStarter; + private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy; private final BubbleController mBubbleController; private final BubbleController.BubbleExpandListener mBubbleExpandListener; @@ -679,6 +681,7 @@ public class StatusBar extends SystemUI implements DemoMode, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, DismissCallbackRegistry dismissCallbackRegistry, + Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy, StatusBarTouchableRegionManager statusBarTouchableRegionManager) { super(context); mNotificationsController = notificationsController; @@ -735,6 +738,7 @@ public class StatusBar extends SystemUI implements DemoMode, mScreenPinningRequest = screenPinningRequest; mDozeScrimController = dozeScrimController; mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; + mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy; mVolumeComponent = volumeComponent; mCommandQueue = commandQueue; mRecentsOptional = recentsOptional; @@ -1135,6 +1139,7 @@ public class StatusBar extends SystemUI implements DemoMode, mBrightnessMirrorController = new BrightnessMirrorController( mNotificationShadeWindowView, mNotificationPanelViewController, + mNotificationShadeDepthControllerLazy.get(), (visible) -> { mBrightnessMirrorVisible = visible; updateScrimController(); @@ -1230,6 +1235,7 @@ public class StatusBar extends SystemUI implements DemoMode, // Set up the initial notification state. mActivityLaunchAnimator = new ActivityLaunchAnimator( mNotificationShadeWindowViewController, this, mNotificationPanelViewController, + mNotificationShadeDepthControllerLazy.get(), (NotificationListContainer) mStackScroller); // TODO: inject this. @@ -3333,12 +3339,12 @@ public class StatusBar extends SystemUI implements DemoMode, Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0); Trace.beginSection("StatusBar#updateDozingState"); - boolean sleepingFromKeyguard = - mStatusBarKeyguardViewManager.isGoingToSleepVisibleNotOccluded(); + boolean visibleNotOccluded = mStatusBarKeyguardViewManager.isShowing() + && !mStatusBarKeyguardViewManager.isOccluded(); boolean wakeAndUnlock = mBiometricUnlockController.getMode() == BiometricUnlockController.MODE_WAKE_AND_UNLOCK; boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock) - || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && sleepingFromKeyguard); + || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && visibleNotOccluded); mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation); updateQsExpansionEnabled(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 31db8eb404a9..45719c7f3936 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -168,7 +168,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastIsDocked; private boolean mLastPulsing; private int mLastBiometricMode; - private boolean mGoingToSleepVisibleNotOccluded; private boolean mLastLockVisible; private OnDismissAction mAfterKeyguardGoneAction; @@ -450,37 +449,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } - public boolean isGoingToSleepVisibleNotOccluded() { - return mGoingToSleepVisibleNotOccluded; - } - - @Override - public void onStartedGoingToSleep() { - mGoingToSleepVisibleNotOccluded = isShowing() && !isOccluded(); - } - @Override public void onFinishedGoingToSleep() { - mGoingToSleepVisibleNotOccluded = false; mBouncer.onScreenTurnedOff(); } @Override - public void onStartedWakingUp() { - // TODO: remove - } - - @Override - public void onScreenTurningOn() { - // TODO: remove - } - - @Override - public void onScreenTurnedOn() { - // TODO: remove - } - - @Override public void onRemoteInputActive(boolean active) { mRemoteInputActive = active; updateStates(); @@ -999,7 +973,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb pw.println(" mOccluded: " + mOccluded); pw.println(" mRemoteInputActive: " + mRemoteInputActive); pw.println(" mDozing: " + mDozing); - pw.println(" mGoingToSleepVisibleNotOccluded: " + mGoingToSleepVisibleNotOccluded); pw.println(" mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction); pw.println(" mAfterKeyguardGoneRunnables: " + mAfterKeyguardGoneRunnables); pw.println(" mPendingWakeupAction: " + mPendingWakeupAction); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index bbc7e7ab8c06..b81a5198b498 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.SuperStatusBarViewFactory; @@ -197,6 +198,7 @@ public interface StatusBarPhoneModule { UserInfoControllerImpl userInfoControllerImpl, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, + Lazy<NotificationShadeDepthController> notificationShadeDepthController, DismissCallbackRegistry dismissCallbackRegistry, StatusBarTouchableRegionManager statusBarTouchableRegionManager) { return new StatusBar( @@ -276,6 +278,7 @@ public interface StatusBarPhoneModule { phoneStatusBarPolicy, keyguardIndicationController, dismissCallbackRegistry, + notificationShadeDepthController, statusBarTouchableRegionManager); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java index d62da10de3d5..78111fb61fd0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java @@ -24,6 +24,7 @@ import android.view.View; import android.widget.FrameLayout; import com.android.systemui.R; +import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; @@ -39,16 +40,19 @@ public class BrightnessMirrorController private final NotificationShadeWindowView mStatusBarWindow; private final Consumer<Boolean> mVisibilityCallback; private final NotificationPanelViewController mNotificationPanel; + private final NotificationShadeDepthController mDepthController; private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>(); private final int[] mInt2Cache = new int[2]; private View mBrightnessMirror; public BrightnessMirrorController(NotificationShadeWindowView statusBarWindow, NotificationPanelViewController notificationPanelViewController, + NotificationShadeDepthController notificationShadeDepthController, @NonNull Consumer<Boolean> visibilityCallback) { mStatusBarWindow = statusBarWindow; mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror); mNotificationPanel = notificationPanelViewController; + mDepthController = notificationShadeDepthController; mNotificationPanel.setPanelAlphaEndAction(() -> { mBrightnessMirror.setVisibility(View.INVISIBLE); }); @@ -59,11 +63,13 @@ public class BrightnessMirrorController mBrightnessMirror.setVisibility(View.VISIBLE); mVisibilityCallback.accept(true); mNotificationPanel.setPanelAlpha(0, true /* animate */); + mDepthController.setBrightnessMirrorVisible(true); } public void hideMirror() { mVisibilityCallback.accept(false); mNotificationPanel.setPanelAlpha(255, true /* animate */); + mDepthController.setBrightnessMirrorVisible(false); } public void setLocation(View original) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index 956bfd0337de..6b7a3bfce5ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.notification.ActivityLaunchAnimator import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.NotificationShadeWindowController import com.android.systemui.statusbar.policy.KeyguardStateController @@ -43,9 +44,9 @@ import org.mockito.Mockito.anyFloat import org.mockito.Mockito.anyString import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.doThrow +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -import java.lang.IllegalArgumentException @RunWith(AndroidTestingRunner::class) @RunWithLooper @@ -64,6 +65,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Mock private lateinit var viewRootImpl: ViewRootImpl @Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation @Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation + @Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation @JvmField @Rule val mockitoRule = MockitoJUnit.rule() private lateinit var statusBarStateListener: StatusBarStateController.StateListener @@ -83,6 +85,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { keyguardStateController, choreographer, wallpaperManager, notificationShadeWindowController, dumpManager) notificationShadeDepthController.shadeSpring = shadeSpring + notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring notificationShadeDepthController.globalActionsSpring = globalActionsSpring notificationShadeDepthController.root = root @@ -127,6 +130,23 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { } @Test + fun updateBlurCallback_setsBlur_whenExpanded() { + `when`(shadeSpring.radius).thenReturn(maxBlur) + notificationShadeDepthController.updateBlurCallback.doFrame(0) + verify(blurUtils).applyBlur(any(), eq(maxBlur)) + } + + @Test + fun updateBlurCallback_appLaunchAnimation_overridesZoom() { + `when`(shadeSpring.radius).thenReturn(maxBlur) + val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters() + animProgress.linearProgress = 1f + notificationShadeDepthController.notificationLaunchAnimationParams = animProgress + notificationShadeDepthController.updateBlurCallback.doFrame(0) + verify(blurUtils).applyBlur(any(), eq(0)) + } + + @Test fun updateBlurCallback_invalidWindow() { doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager) .setWallpaperZoomOut(any(), anyFloat()) @@ -134,6 +154,48 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat()) } + @Test + fun brightnessMirrorVisible_whenVisible() { + notificationShadeDepthController.brightnessMirrorVisible = true + verify(brightnessSpring).animateTo(eq(maxBlur), any()) + } + + @Test + fun brightnessMirrorVisible_whenHidden() { + notificationShadeDepthController.brightnessMirrorVisible = false + verify(brightnessSpring).animateTo(eq(0), any()) + } + + @Test + fun brightnessMirror_hidesShadeBlur() { + // Brightness mirror is fully visible + `when`(brightnessSpring.ratio).thenReturn(1f) + // And shade is blurred + `when`(shadeSpring.radius).thenReturn(maxBlur) + + notificationShadeDepthController.updateBlurCallback.doFrame(0) + verify(notificationShadeWindowController).setBackgroundBlurRadius(0) + verify(blurUtils).applyBlur(safeEq(viewRootImpl), eq(0)) + } + + @Test + fun setNotificationLaunchAnimationParams_schedulesFrame() { + val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters() + animProgress.linearProgress = 0.5f + notificationShadeDepthController.notificationLaunchAnimationParams = animProgress + verify(choreographer).postFrameCallback( + eq(notificationShadeDepthController.updateBlurCallback)) + } + + @Test + fun setNotificationLaunchAnimationParams_whennNull_ignoresIfShadeHasNoBlur() { + val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters() + animProgress.linearProgress = 0.5f + `when`(shadeSpring.radius).thenReturn(0) + notificationShadeDepthController.notificationLaunchAnimationParams = animProgress + verify(shadeSpring, never()).animateTo(anyInt(), any()) + } + private fun <T : Any> safeEq(value: T): T { return eq(value) ?: value } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java index a07cfc3c3226..cdef49d6c94d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -31,6 +30,7 @@ import android.view.RemoteAnimationAdapter; import android.view.View; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.NotificationPanelViewController; @@ -39,8 +39,12 @@ import com.android.systemui.statusbar.phone.NotificationShadeWindowViewControlle import org.junit.Assert; 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; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -48,14 +52,22 @@ import org.junit.runner.RunWith; public class ActivityLaunchAnimatorTest extends SysuiTestCase { private ActivityLaunchAnimator mLaunchAnimator; - private ActivityLaunchAnimator.Callback mCallback = mock(ActivityLaunchAnimator.Callback.class); - private NotificationShadeWindowViewController mNotificationShadeWindowViewController = mock( - NotificationShadeWindowViewController.class); - private NotificationShadeWindowView mNotificationShadeWindowView = mock( - NotificationShadeWindowView.class); - private NotificationListContainer mNotificationContainer - = mock(NotificationListContainer.class); - private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class); + @Mock + private ActivityLaunchAnimator.Callback mCallback; + @Mock + private NotificationShadeWindowViewController mNotificationShadeWindowViewController; + @Mock + private NotificationShadeWindowView mNotificationShadeWindowView; + @Mock + private NotificationListContainer mNotificationContainer; + @Mock + private ExpandableNotificationRow mRow; + @Mock + private NotificationShadeDepthController mNotificationShadeDepthController; + @Mock + private NotificationPanelViewController mNotificationPanelViewController; + @Rule + public MockitoRule rule = MockitoJUnit.rule(); @Before public void setUp() throws Exception { @@ -66,7 +78,8 @@ public class ActivityLaunchAnimatorTest extends SysuiTestCase { mLaunchAnimator = new ActivityLaunchAnimator( mNotificationShadeWindowViewController, mCallback, - mock(NotificationPanelViewController.class), + mNotificationPanelViewController, + mNotificationShadeDepthController, mNotificationContainer); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index c356e0d16512..cb379208eb94 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -229,22 +229,19 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testShowAppOpsIcons_header() { - NotificationHeaderView mockHeader = mock(NotificationHeaderView.class); - NotificationContentView publicLayout = mock(NotificationContentView.class); mGroupRow.setPublicLayout(publicLayout); NotificationContentView privateLayout = mock(NotificationContentView.class); mGroupRow.setPrivateLayout(privateLayout); NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class); when(mockContainer.getNotificationChildCount()).thenReturn(1); - when(mockContainer.getHeaderView()).thenReturn(mockHeader); mGroupRow.setChildrenContainer(mockContainer); ArraySet<Integer> ops = new ArraySet<>(); ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS); mGroupRow.showAppOpsIcons(ops); - verify(mockHeader, times(1)).showAppOpsIcons(ops); + verify(mockContainer, times(1)).showAppOpsIcons(ops); verify(privateLayout, times(1)).showAppOpsIcons(ops); verify(publicLayout, times(1)).showAppOpsIcons(ops); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java index 84c651368dc9..0f268984a996 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java @@ -76,14 +76,14 @@ public class NotificationContentViewTest extends SysuiTestCase { @Test @UiThreadTest public void testShowAppOpsIcons() { - NotificationHeaderView mockContracted = mock(NotificationHeaderView.class); - when(mockContracted.findViewById(com.android.internal.R.id.notification_header)) + View mockContracted = mock(View.class); + when(mockContracted.findViewById(com.android.internal.R.id.mic)) .thenReturn(mockContracted); - NotificationHeaderView mockExpanded = mock(NotificationHeaderView.class); - when(mockExpanded.findViewById(com.android.internal.R.id.notification_header)) + View mockExpanded = mock(View.class); + when(mockExpanded.findViewById(com.android.internal.R.id.mic)) .thenReturn(mockExpanded); - NotificationHeaderView mockHeadsUp = mock(NotificationHeaderView.class); - when(mockHeadsUp.findViewById(com.android.internal.R.id.notification_header)) + View mockHeadsUp = mock(View.class); + when(mockHeadsUp.findViewById(com.android.internal.R.id.mic)) .thenReturn(mockHeadsUp); mView.setContractedChild(mockContracted); @@ -91,11 +91,11 @@ public class NotificationContentViewTest extends SysuiTestCase { mView.setHeadsUpChild(mockHeadsUp); ArraySet<Integer> ops = new ArraySet<>(); - ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS); + ops.add(AppOpsManager.OP_RECORD_AUDIO); mView.showAppOpsIcons(ops); - verify(mockContracted, times(1)).showAppOpsIcons(ops); - verify(mockExpanded, times(1)).showAppOpsIcons(ops); - verify(mockHeadsUp, times(1)).showAppOpsIcons(any()); + verify(mockContracted, times(1)).setVisibility(View.VISIBLE); + verify(mockExpanded, times(1)).setVisibility(View.VISIBLE); + verify(mockHeadsUp, times(1)).setVisibility(View.VISIBLE); } } 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 679ac2224128..b905bddb98f5 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 @@ -102,6 +102,7 @@ import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.RemoteInputController; @@ -249,6 +250,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private ExtensionController mExtensionController; @Mock private UserInfoControllerImpl mUserInfoControllerImpl; @Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy; + @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy; private ShadeController mShadeController; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private InitController mInitController = new InitController(); @@ -404,6 +406,7 @@ public class StatusBarTest extends SysuiTestCase { mPhoneStatusBarPolicy, mKeyguardIndicationController, mDismissCallbackRegistry, + mNotificationShadeDepthControllerLazy, mStatusBarTouchableRegionManager); when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn( diff --git a/services/core/Android.bp b/services/core/Android.bp index 052026c2746a..5faed43dd6e6 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -126,6 +126,7 @@ java_library_static { "android.hardware.rebootescrow-java", "android.hardware.soundtrigger-V2.3-java", "android.hidl.manager-V1.2-java", + "capture_state_listener-aidl-java", "dnsresolver_aidl_interface-V2-java", "netd_event_listener_interface-java", "overlayable_policy_aidl-java", diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index 808d322020cb..bfcde97d6c91 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -98,8 +98,8 @@ public class RescueParty { private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device"; - - private static final String DEVICE_CONFIG_DISABLE_FLAG = "disable_rescue_party"; + private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = + "persist.device_config.configuration.disable_rescue_party"; private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; @@ -118,8 +118,7 @@ public class RescueParty { // We're disabled if the DeviceConfig disable flag is set to true. // This is in case that an emergency rollback of the feature is needed. - if (DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_CONFIGURATION, DEVICE_CONFIG_DISABLE_FLAG, false)) { + if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) { Slog.v(TAG, "Disabled because of DeviceConfig flag"); return true; } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 9018caa8d7b6..067147703b0c 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -234,6 +234,7 @@ class StorageManagerService extends IStorageManager.Stub private static final String FUSE_ENABLED = "fuse_enabled"; private static final boolean DEFAULT_FUSE_ENABLED = true; + @GuardedBy("mLock") private final Set<Integer> mFuseMountedUser = new ArraySet<>(); public static class Lifecycle extends SystemService { @@ -810,7 +811,7 @@ class StorageManagerService extends IStorageManager.Stub } case H_VOLUME_STATE_CHANGED: { final SomeArgs args = (SomeArgs) msg.obj; - onVolumeStateChangedInternal((VolumeInfo) args.arg1, (int) args.arg2, + onVolumeStateChangedAsync((VolumeInfo) args.arg1, (int) args.arg2, (int) args.arg3); } } @@ -1337,6 +1338,7 @@ class StorageManagerService extends IStorageManager.Stub args.arg2 = oldState; args.arg3 = newState; mHandler.obtainMessage(H_VOLUME_STATE_CHANGED, args).sendToTarget(); + onVolumeStateChangedLocked(vol, oldState, newState); } } } @@ -1509,11 +1511,45 @@ class StorageManagerService extends IStorageManager.Stub return true; } - private void onVolumeStateChangedInternal(VolumeInfo vol, int oldState, int newState) { - synchronized (mLock) { - if (vol.type == VolumeInfo.TYPE_EMULATED && newState != VolumeInfo.STATE_MOUNTED) { + + private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) { + if (vol.type == VolumeInfo.TYPE_EMULATED) { + if (newState != VolumeInfo.STATE_MOUNTED) { mFuseMountedUser.remove(vol.getMountUserId()); + } else { + final int userId = vol.getMountUserId(); + mFuseMountedUser.add(userId); + // Async remount app storage so it won't block the main thread. + new Thread(() -> { + Map<Integer, String> pidPkgMap = null; + // getProcessesWithPendingBindMounts() could fail when a new app process is + // starting and it's not planning to mount storage dirs in zygote, but it's + // rare, so we retry 5 times and hope we can get the result successfully. + for (int i = 0; i < 5; i++) { + try { + pidPkgMap = LocalServices.getService(ActivityManagerInternal.class) + .getProcessesWithPendingBindMounts(vol.getMountUserId()); + break; + } catch (IllegalStateException e) { + Slog.i(TAG, "Some processes are starting, retry"); + // Wait 100ms and retry so hope the pending process is started. + SystemClock.sleep(100); + } + } + if (pidPkgMap != null) { + remountAppStorageDirs(pidPkgMap, userId); + } else { + Slog.wtf(TAG, "Not able to getStorageNotOptimizedProcesses() after" + + " 5 retries"); + } + }).start(); } + } + } + + + private void onVolumeStateChangedAsync(VolumeInfo vol, int oldState, int newState) { + synchronized (mLock) { // Remember that we saw this volume so we're ready to accept user // metadata, or so we can annoy them when a private volume is ejected if (!TextUtils.isEmpty(vol.fsUuid)) { @@ -2161,35 +2197,6 @@ class StorageManagerService extends IStorageManager.Stub } }); Slog.i(TAG, "Mounted volume " + vol); - if (vol.type == VolumeInfo.TYPE_EMULATED) { - final int userId = vol.getMountUserId(); - mFuseMountedUser.add(userId); - // Async remount app storage so it won't block the main thread. - new Thread(() -> { - Map<Integer, String> pidPkgMap = null; - // getProcessesWithPendingBindMounts() could fail when a new app process is - // starting and it's not planning to mount storage dirs in zygote, but it's - // rare, so we retry 5 times and hope we can get the result successfully. - for (int i = 0; i < 5; i++) { - try { - pidPkgMap = LocalServices.getService(ActivityManagerInternal.class) - .getProcessesWithPendingBindMounts(vol.getMountUserId()); - break; - } catch (IllegalStateException e) { - Slog.i(TAG, "Some processes are starting, retry"); - // Wait 100ms and retry so hope the pending process is started. - SystemClock.sleep(100); - } - } - if (pidPkgMap != null) { - remountAppStorageDirs(pidPkgMap, userId); - } else { - Slog.wtf(TAG, "Not able to getStorageNotOptimizedProcesses() after" - + " 5 retries"); - } - - }).start(); - } } catch (Exception e) { Slog.wtf(TAG, e); } @@ -4445,9 +4452,11 @@ class StorageManagerService extends IStorageManager.Stub @Override public boolean prepareStorageDirs(int userId, Set<String> packageList, String processName) { - if (!mFuseMountedUser.contains(userId)) { - Slog.w(TAG, "User " + userId + " is not unlocked yet so skip mounting obb"); - return false; + synchronized (mLock) { + if (!mFuseMountedUser.contains(userId)) { + Slog.w(TAG, "User " + userId + " is not unlocked yet so skip mounting obb"); + return false; + } } try { final IVold vold = IVold.Stub.asInterface( diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 059eb6ad724c..df160588e66a 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -32,7 +32,7 @@ "name": "CtsWindowManagerDeviceTestCases", "options": [ { - "include-filter": "android.server.wm.ToastTest" + "include-filter": "android.server.wm.ToastWindowTest" } ], "file_patterns": ["NotificationManagerService\\.java"] diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 689f64d01054..85d288317b6a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -17698,7 +17698,7 @@ public class ActivityManagerService extends IActivityManager.Stub proc.setReportedForegroundServiceTypes(fgServiceTypes); ProcessChangeItem item = enqueueProcessChangeItemLocked(proc.pid, proc.info.uid); - item.changes = ProcessChangeItem.CHANGE_FOREGROUND_SERVICES; + item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES; item.foregroundServiceTypes = fgServiceTypes; } if (oomAdj) { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 1412112651c4..dbcb3da3e2f4 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2385,7 +2385,7 @@ public final class OomAdjuster { "Changes in " + app + ": " + changes); ActivityManagerService.ProcessChangeItem item = mService.enqueueProcessChangeItemLocked(app.pid, app.info.uid); - item.changes = changes; + item.changes |= changes; item.foregroundActivities = app.repForegroundActivities; item.capability = app.setCapability; if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index b546120e2b95..c2c79d361996 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -324,7 +324,7 @@ import java.io.PrintWriter; } /*package*/ void setBluetoothScoOn(boolean on, String eventSource) { - //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource); + //Log.i(TAG, "setBluetoothScoOn: " + on + " " + eventSource); synchronized (mDeviceStateLock) { if (on) { // do not accept SCO ON if SCO audio is not connected diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 36332c0ad25c..93d1bede9de8 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -58,6 +58,7 @@ public class BtHelper { } // List of clients having issued a SCO start request + @GuardedBy("BtHelper.this") private final @NonNull ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>(); // BluetoothHeadset API to control SCO connection @@ -356,9 +357,8 @@ public class BtHelper { // client is created. final long ident = Binder.clearCallingIdentity(); try { - eventSource += " client count before=" + client.getCount(); AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); - client.incCount(scoAudioMode); + client.requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); } catch (NullPointerException e) { Log.e(TAG, "Null ScoClient", e); } @@ -375,9 +375,15 @@ public class BtHelper { // and this must be done on behalf of system server to make sure permissions are granted. final long ident = Binder.clearCallingIdentity(); if (client != null) { - eventSource += " client count before=" + client.getCount(); AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); - client.decCount(); + client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, + SCO_MODE_VIRTUAL_CALL); + // If a disconnection is pending, the client will be removed whne clearAllScoClients() + // is called form receiveBtEvent() + if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ + && mScoAudioState != SCO_STATE_DEACTIVATING) { + client.remove(false /*stop */, true /*unregister*/); + } } Binder.restoreCallingIdentity(ident); } @@ -657,96 +663,40 @@ public class BtHelper { @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void scoClientDied(Object obj) { final ScoClient client = (ScoClient) obj; + client.remove(true /*stop*/, false /*unregister*/); Log.w(TAG, "SCO client died"); - int index = mScoClients.indexOf(client); - if (index < 0) { - Log.w(TAG, "unregistered SCO client died"); - } else { - client.clearCount(true); - mScoClients.remove(client); - } } private class ScoClient implements IBinder.DeathRecipient { private IBinder mCb; // To be notified of client's death private int mCreatorPid; - private int mStartcount; // number of SCO connections started by this client ScoClient(IBinder cb) { mCb = cb; mCreatorPid = Binder.getCallingPid(); - mStartcount = 0; - } - - @Override - public void binderDied() { - // process this from DeviceBroker's message queue to take the right locks since - // this event can impact SCO mode and requires querying audio mode stack - mDeviceBroker.postScoClientDied(this); } - // @GuardedBy("AudioDeviceBroker.mSetModeLock") - // @GuardedBy("AudioDeviceBroker.mDeviceStateLock") - @GuardedBy("BtHelper.this") - void incCount(int scoAudioMode) { - if (!requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode)) { - Log.e(TAG, "Request sco connected with scoAudioMode(" - + scoAudioMode + ") failed"); - return; - } - if (mStartcount == 0) { - try { - mCb.linkToDeath(this, 0); - } catch (RemoteException e) { - // client has already died! - Log.w(TAG, "ScoClient incCount() could not link to " - + mCb + " binder death"); - } - } - mStartcount++; - } - - // @GuardedBy("AudioDeviceBroker.mSetModeLock") - // @GuardedBy("AudioDeviceBroker.mDeviceStateLock") - @GuardedBy("BtHelper.this") - void decCount() { - if (mStartcount == 0) { - Log.w(TAG, "ScoClient.decCount() already 0"); - } else { - mStartcount--; - if (mStartcount == 0) { - try { - mCb.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.w(TAG, "decCount() going to 0 but not registered to binder"); - } - } - if (!requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0)) { - Log.w(TAG, "Request sco disconnected with scoAudioMode(0) failed"); - } + public void registerDeathRecipient() { + try { + mCb.linkToDeath(this, 0); + } catch (RemoteException e) { + Log.w(TAG, "ScoClient could not link to " + mCb + " binder death"); } } - // @GuardedBy("AudioDeviceBroker.mSetModeLock") - // @GuardedBy("AudioDeviceBroker.mDeviceStateLock") - @GuardedBy("BtHelper.this") - void clearCount(boolean stopSco) { - if (mStartcount != 0) { - try { - mCb.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.w(TAG, "clearCount() mStartcount: " - + mStartcount + " != 0 but not registered to binder"); - } - } - mStartcount = 0; - if (stopSco) { - requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); + public void unregisterDeathRecipient() { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "ScoClient could not not unregistered to binder"); } } - int getCount() { - return mStartcount; + @Override + public void binderDied() { + // process this from DeviceBroker's message queue to take the right locks since + // this event can impact SCO mode and requires querying audio mode stack + mDeviceBroker.postScoClientDied(this); } IBinder getBinder() { @@ -757,23 +707,14 @@ public class BtHelper { return mCreatorPid; } - private int totalCount() { - int count = 0; - for (ScoClient mScoClient : mScoClients) { - count += mScoClient.getCount(); - } - return count; - } - // @GuardedBy("AudioDeviceBroker.mSetModeLock") //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") @GuardedBy("BtHelper.this") private boolean requestScoState(int state, int scoAudioMode) { checkScoAudioState(); - int clientCount = totalCount(); - if (clientCount != 0) { + if (mScoClients.size() != 1) { Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode - + ", clientCount=" + clientCount); + + ", num SCO clients=" + mScoClients.size()); return true; } if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { @@ -842,12 +783,14 @@ public class BtHelper { mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); break; + case SCO_STATE_ACTIVE_INTERNAL: + Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return"); + break; default: Log.w(TAG, "requestScoState: failed to connect in state " + mScoAudioState + ", scoAudioMode=" + scoAudioMode); broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); return false; - } } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { switch (mScoAudioState) { @@ -893,6 +836,18 @@ public class BtHelper { } return true; } + + @GuardedBy("BtHelper.this") + void remove(boolean stop, boolean unregister) { + if (unregister) { + unregisterDeathRecipient(); + } + if (stop) { + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, + SCO_MODE_VIRTUAL_CALL); + } + mScoClients.remove(this); + } } //----------------------------------------------------- @@ -946,6 +901,7 @@ public class BtHelper { } + @GuardedBy("BtHelper.this") private ScoClient getScoClient(IBinder cb, boolean create) { for (ScoClient existingClient : mScoClients) { if (existingClient.getBinder() == cb) { @@ -954,6 +910,7 @@ public class BtHelper { } if (create) { ScoClient newClient = new ScoClient(cb); + newClient.registerDeathRecipient(); mScoClients.add(newClient); return newClient; } @@ -964,18 +921,16 @@ public class BtHelper { //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") @GuardedBy("BtHelper.this") private void clearAllScoClients(int exceptPid, boolean stopSco) { - ScoClient savedClient = null; + final ArrayList<ScoClient> clients = new ArrayList<ScoClient>(); for (ScoClient cl : mScoClients) { if (cl.getPid() != exceptPid) { - cl.clearCount(stopSco); - } else { - savedClient = cl; + clients.add(cl); } } - mScoClients.clear(); - if (savedClient != null) { - mScoClients.add(savedClient); + for (ScoClient cl : clients) { + cl.remove(stopSco, true /*unregister*/); } + } private boolean getBluetoothHeadset() { diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java index 65f221899818..32c6cc32a78d 100644 --- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java +++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java @@ -150,14 +150,14 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin final AudioRecordingConfiguration config = createRecordingConfiguration( uid, session, source, recordingInfo, portId, silenced, activeSource, clientEffects, effects); - if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX) { + if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX + && (event == AudioManager.RECORD_CONFIG_EVENT_START + || event == AudioManager.RECORD_CONFIG_EVENT_UPDATE)) { final AudioDeviceInfo device = config.getAudioDevice(); - if (AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS.equals(device.getAddress())) { + if (device != null + && AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS.equals(device.getAddress())) { mLegacyRemoteSubmixRiid.set(riid); - if (event == AudioManager.RECORD_CONFIG_EVENT_START - || event == AudioManager.RECORD_CONFIG_EVENT_UPDATE) { - mLegacyRemoteSubmixActive.set(true); - } + mLegacyRemoteSubmixActive.set(true); } } if (MediaRecorder.isSystemOnlyAudioSource(source)) { diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 0bba1723931d..52e9d7c67605 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -54,11 +54,9 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; @@ -70,10 +68,8 @@ class MediaRouter2ServiceImpl { private static final String TAG = "MR2ServiceImpl"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - /** - * TODO: Change this with the real request ID from MediaRouter2 when - * MediaRouter2 needs to get notified for the failures. - */ + // TODO: (In Android S or later) if we add callback methods for generic failures + // in MediaRouter2, remove this constant and replace the usages with the real request IDs. private static final long DUMMY_REQUEST_ID = -1; private final Context mContext; @@ -493,7 +489,7 @@ class MediaRouter2ServiceImpl { } } - //TODO: Review this is handling multi-user properly. + //TODO(b/136703681): Review this is handling multi-user properly. void switchUser() { synchronized (mLock) { int userId = ActivityManager.getCurrentUser(); @@ -568,7 +564,9 @@ class MediaRouter2ServiceImpl { UserRecord userRecord = routerRecord.mUserRecord; userRecord.mRouterRecords.remove(routerRecord); - //TODO: update discovery request + userRecord.mHandler.sendMessage( + obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler, + userRecord.mHandler)); routerRecord.dispose(); disposeUserIfNeededLocked(userRecord); // since router removed from user } @@ -793,7 +791,7 @@ class MediaRouter2ServiceImpl { } long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId); - //TODO: Use MediaRouter2's OnCreateSessionListener to send proper session hints. + //TODO(b/152851868): Use MediaRouter2's OnCreateSessionListener to send session hints. routerRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::requestCreateSessionOnHandler, routerRecord.mUserRecord.mHandler, @@ -1146,38 +1144,26 @@ class MediaRouter2ServiceImpl { return mSessionToRouterMap.get(uniqueSessionId); } - //TODO: notify session info updates private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) { int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId()); - MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo(); + MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo(); MediaRoute2ProviderInfo prevInfo = (providerInfoIndex < 0) ? null : mLastProviderInfos.get(providerInfoIndex); + if (Objects.equals(prevInfo, currentInfo)) return; - if (Objects.equals(prevInfo, providerInfo)) return; - + List<MediaRoute2Info> addedRoutes = new ArrayList<>(); + List<MediaRoute2Info> removedRoutes = new ArrayList<>(); + List<MediaRoute2Info> changedRoutes = new ArrayList<>(); if (prevInfo == null) { - mLastProviderInfos.add(providerInfo); - Collection<MediaRoute2Info> addedRoutes = providerInfo.getRoutes(); - if (addedRoutes.size() > 0) { - sendMessage(PooledLambda.obtainMessage(UserHandler::notifyRoutesAddedToRouters, - this, getRouters(), new ArrayList<>(addedRoutes))); - } - } else if (providerInfo == null) { + mLastProviderInfos.add(currentInfo); + addedRoutes.addAll(currentInfo.getRoutes()); + } else if (currentInfo == null) { mLastProviderInfos.remove(prevInfo); - Collection<MediaRoute2Info> removedRoutes = prevInfo.getRoutes(); - if (removedRoutes.size() > 0) { - sendMessage(PooledLambda.obtainMessage( - UserHandler::notifyRoutesRemovedToRouters, - this, getRouters(), new ArrayList<>(removedRoutes))); - } + removedRoutes.addAll(prevInfo.getRoutes()); } else { - mLastProviderInfos.set(providerInfoIndex, providerInfo); - List<MediaRoute2Info> addedRoutes = new ArrayList<>(); - List<MediaRoute2Info> removedRoutes = new ArrayList<>(); - List<MediaRoute2Info> changedRoutes = new ArrayList<>(); - - final Collection<MediaRoute2Info> currentRoutes = providerInfo.getRoutes(); - final Set<String> updatedRouteIds = new HashSet<>(); + mLastProviderInfos.set(providerInfoIndex, currentInfo); + final Collection<MediaRoute2Info> prevRoutes = prevInfo.getRoutes(); + final Collection<MediaRoute2Info> currentRoutes = currentInfo.getRoutes(); for (MediaRoute2Info route : currentRoutes) { if (!route.isValid()) { @@ -1185,37 +1171,33 @@ class MediaRouter2ServiceImpl { continue; } MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId()); - - if (prevRoute != null) { - if (!Objects.equals(prevRoute, route)) { - changedRoutes.add(route); - } - updatedRouteIds.add(route.getId()); - } else { + if (prevRoute == null) { addedRoutes.add(route); + } else if (!Objects.equals(prevRoute, route)) { + changedRoutes.add(route); } } for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) { - if (!updatedRouteIds.contains(prevRoute.getId())) { + if (currentInfo.getRoute(prevRoute.getOriginalId()) == null) { removedRoutes.add(prevRoute); } } + } - List<IMediaRouter2> routers = getRouters(); - List<IMediaRouter2Manager> managers = getManagers(); - if (addedRoutes.size() > 0) { - notifyRoutesAddedToRouters(routers, addedRoutes); - notifyRoutesAddedToManagers(managers, addedRoutes); - } - if (removedRoutes.size() > 0) { - notifyRoutesRemovedToRouters(routers, removedRoutes); - notifyRoutesRemovedToManagers(managers, removedRoutes); - } - if (changedRoutes.size() > 0) { - notifyRoutesChangedToRouters(routers, changedRoutes); - notifyRoutesChangedToManagers(managers, changedRoutes); - } + List<IMediaRouter2> routers = getRouters(); + List<IMediaRouter2Manager> managers = getManagers(); + if (addedRoutes.size() > 0) { + notifyRoutesAddedToRouters(routers, addedRoutes); + notifyRoutesAddedToManagers(managers, addedRoutes); + } + if (removedRoutes.size() > 0) { + notifyRoutesRemovedToRouters(routers, removedRoutes); + notifyRoutesRemovedToManagers(managers, removedRoutes); + } + if (changedRoutes.size() > 0) { + notifyRoutesChangedToRouters(routers, changedRoutes); + notifyRoutesChangedToManagers(managers, changedRoutes); } } @@ -1323,7 +1305,7 @@ class MediaRouter2ServiceImpl { return true; } - //TODO: Handle RCN case. + //TODO(b/152950479): Handle RCN case. if (routerRecord == null) { Slog.w(TAG, "Ignoring " + description + " route from unknown router."); return false; @@ -1403,7 +1385,8 @@ class MediaRouter2ServiceImpl { private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider, long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) { - notifySessionCreatedToManagers(getManagers(), sessionInfo); + notifySessionCreatedToManagers(getManagers(), + toOriginalRequestId(uniqueRequestId), sessionInfo); if (uniqueRequestId == REQUEST_ID_NONE) { // The session is created without any matching request. @@ -1457,7 +1440,7 @@ class MediaRouter2ServiceImpl { private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo) { List<IMediaRouter2Manager> managers = getManagers(); - notifySessionInfosChangedToManagers(managers); + notifySessionInfoChangedToManagers(managers, sessionInfo); // For system provider, notify all routers. if (provider == mSystemProvider) { @@ -1480,7 +1463,7 @@ class MediaRouter2ServiceImpl { private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo) { List<IMediaRouter2Manager> managers = getManagers(); - notifySessionInfosChangedToManagers(managers); + notifySessionInfoChangedToManagers(managers, sessionInfo); RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId()); if (routerRecord == null) { @@ -1558,7 +1541,8 @@ class MediaRouter2ServiceImpl { private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord, int requestId) { try { - routerRecord.mRouter.notifySessionCreated(requestId, /* sessionInfo= */ null); + routerRecord.mRouter.notifySessionCreated(requestId, + /* sessionInfo= */ null); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify router of the session creation failure." + " Router probably died.", ex); @@ -1731,10 +1715,10 @@ class MediaRouter2ServiceImpl { } private void notifySessionCreatedToManagers(@NonNull List<IMediaRouter2Manager> managers, - @NonNull RoutingSessionInfo sessionInfo) { + int requestId, @NonNull RoutingSessionInfo sessionInfo) { for (IMediaRouter2Manager manager : managers) { try { - manager.notifySessionCreated(sessionInfo); + manager.notifySessionCreated(requestId, sessionInfo); } catch (RemoteException ex) { Slog.w(TAG, "notifySessionCreatedToManagers: " + "failed to notify. Manager probably died.", ex); @@ -1742,11 +1726,12 @@ class MediaRouter2ServiceImpl { } } - private void notifySessionInfosChangedToManagers( - @NonNull List<IMediaRouter2Manager> managers) { + private void notifySessionInfoChangedToManagers( + @NonNull List<IMediaRouter2Manager> managers, + @NonNull RoutingSessionInfo sessionInfo) { for (IMediaRouter2Manager manager : managers) { try { - manager.notifySessionsUpdated(); + manager.notifySessionUpdated(sessionInfo); } catch (RemoteException ex) { Slog.w(TAG, "notifySessionInfosChangedToManagers: " + "failed to notify. Manager probably died.", ex); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 4aeddc89f6ed..e8d8ed7a462d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -107,7 +107,6 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -155,6 +154,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; +import android.content.pm.ShortcutInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; @@ -1730,6 +1730,11 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting + void setShortcutHelper(ShortcutHelper helper) { + mShortcutHelper = helper; + } + + @VisibleForTesting void setHints(int hints) { mListenerHints = hints; } @@ -3459,10 +3464,14 @@ public class NotificationManagerService extends SystemService { ArrayList<ConversationChannelWrapper> conversations = mPreferencesHelper.getConversations(onlyImportant); for (ConversationChannelWrapper conversation : conversations) { - conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo( - conversation.getNotificationChannel().getConversationId(), - conversation.getPkg(), - UserHandle.of(UserHandle.getUserId(conversation.getUid())))); + if (mShortcutHelper == null) { + conversation.setShortcutInfo(null); + } else { + conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo( + conversation.getNotificationChannel().getConversationId(), + conversation.getPkg(), + UserHandle.of(UserHandle.getUserId(conversation.getUid())))); + } } return new ParceledListSlice<>(conversations); } @@ -3482,10 +3491,14 @@ public class NotificationManagerService extends SystemService { ArrayList<ConversationChannelWrapper> conversations = mPreferencesHelper.getConversations(pkg, uid); for (ConversationChannelWrapper conversation : conversations) { - conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo( - conversation.getNotificationChannel().getConversationId(), - pkg, - UserHandle.of(UserHandle.getUserId(uid)))); + if (mShortcutHelper == null) { + conversation.setShortcutInfo(null); + } else { + conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo( + conversation.getNotificationChannel().getConversationId(), + pkg, + UserHandle.of(UserHandle.getUserId(uid)))); + } } return new ParceledListSlice<>(conversations); } @@ -5680,8 +5693,10 @@ public class NotificationManagerService extends SystemService { } } - r.setShortcutInfo(mShortcutHelper.getValidShortcutInfo( - notification.getShortcutId(), pkg, user)); + ShortcutInfo info = mShortcutHelper != null + ? mShortcutHelper.getValidShortcutInfo(notification.getShortcutId(), pkg, user) + : null; + r.setShortcutInfo(info); if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r, r.getSbn().getOverrideGroupKey() != null)) { @@ -6214,8 +6229,11 @@ public class NotificationManagerService extends SystemService { cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName, mSendDelete, childrenFlagChecker); updateLightsLocked(); - mShortcutHelper.maybeListenForShortcutChangesForBubbles(r, true /* isRemoved */, - mHandler); + if (mShortcutHelper != null) { + mShortcutHelper.maybeListenForShortcutChangesForBubbles(r, + true /* isRemoved */, + mHandler); + } } else { // No notification was found, assume that it is snoozed and cancel it. if (mReason != REASON_SNOOZED) { @@ -6453,9 +6471,11 @@ public class NotificationManagerService extends SystemService { + n.getPackageName()); } - mShortcutHelper.maybeListenForShortcutChangesForBubbles(r, - false /* isRemoved */, - mHandler); + if (mShortcutHelper != null) { + mShortcutHelper.maybeListenForShortcutChangesForBubbles(r, + false /* isRemoved */, + mHandler); + } maybeRecordInterruptionLocked(r); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 32cfaf614ab9..dbb246e9fbe8 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -20,6 +20,7 @@ import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES; @@ -1798,6 +1799,7 @@ public class PreferencesHelper implements RankingConfig { .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES); final PackagePreferences r = mPackagePreferences.valueAt(i); event.writeInt(r.uid); + event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); event.writeInt(r.importance); event.writeInt(r.visibility); event.writeInt(r.lockedAppFields); @@ -1825,6 +1827,7 @@ public class PreferencesHelper implements RankingConfig { StatsEvent.Builder event = StatsEvent.newBuilder() .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES); event.writeInt(r.uid); + event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); event.writeString(channel.getId()); event.writeString(channel.getName().toString()); event.writeString(channel.getDescription()); @@ -1856,6 +1859,7 @@ public class PreferencesHelper implements RankingConfig { StatsEvent.Builder event = StatsEvent.newBuilder() .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES); event.writeInt(r.uid); + event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); event.writeString(groupChannel.getId()); event.writeString(groupChannel.getName().toString()); event.writeString(groupChannel.getDescription()); diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 805d91852d8e..09b782d768d2 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -674,7 +674,8 @@ public class AppsFilter { Trace.endSection(); if (callingPkgSetting != null) { - if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) { + if (callingPkgSetting.pkg != null + && !mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "DISABLED"); } @@ -682,7 +683,8 @@ public class AppsFilter { } } else { for (int i = callingSharedPkgSettings.size() - 1; i >= 0; i--) { - if (!mFeatureConfig.packageIsEnabled(callingSharedPkgSettings.valueAt(i).pkg)) { + final AndroidPackage pkg = callingSharedPkgSettings.valueAt(i).pkg; + if (pkg != null && !mFeatureConfig.packageIsEnabled(pkg)) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "DISABLED"); } diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java index f497f114c05f..f1e14331e33f 100644 --- a/services/core/java/com/android/server/pm/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/ComponentResolver.java @@ -54,6 +54,7 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.server.IntentResolver; import com.android.server.pm.parsing.PackageInfoUtils; @@ -207,33 +208,57 @@ public class ComponentResolver { } /** Returns the given activity */ - ParsedActivity getActivity(ComponentName component) { + @Nullable + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public ParsedActivity getActivity(@NonNull ComponentName component) { synchronized (mLock) { return mActivities.mActivities.get(component); } } /** Returns the given provider */ - ParsedProvider getProvider(ComponentName component) { + @Nullable + ParsedProvider getProvider(@NonNull ComponentName component) { synchronized (mLock) { return mProviders.mProviders.get(component); } } /** Returns the given receiver */ - ParsedActivity getReceiver(ComponentName component) { + @Nullable + ParsedActivity getReceiver(@NonNull ComponentName component) { synchronized (mLock) { return mReceivers.mActivities.get(component); } } /** Returns the given service */ - ParsedService getService(ComponentName component) { + @Nullable + ParsedService getService(@NonNull ComponentName component) { synchronized (mLock) { return mServices.mServices.get(component); } } + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean componentExists(@NonNull ComponentName componentName) { + synchronized (mLock) { + ParsedMainComponent component = mActivities.mActivities.get(componentName); + if (component != null) { + return true; + } + component = mReceivers.mActivities.get(componentName); + if (component != null) { + return true; + } + component = mServices.mServices.get(componentName); + if (component != null) { + return true; + } + return mProviders.mProviders.get(componentName) != null; + } + } + @Nullable List<ResolveInfo> queryActivities(Intent intent, String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, int userId) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8850f29e9bc2..f96ab1d9a042 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -674,7 +674,7 @@ public class PackageManagerService extends IPackageManager.Stub final ServiceThread mHandlerThread; - final PackageHandler mHandler; + final Handler mHandler; private final ProcessLoggingHandler mProcessLoggingHandler; @@ -1033,6 +1033,62 @@ public class PackageManagerService extends IPackageManager.Stub } } + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class TestParams { + public ApexManager apexManager; + public @Nullable String appPredictionServicePackage; + public ArtManagerService artManagerService; + public @Nullable String configuratorPackage; + public int defParseFlags; + public DexManager dexManager; + public List<ScanPartition> dirsToScanAsSystem; + public @Nullable String documenterPackage; + public boolean factoryTest; + public ArrayMap<String, FeatureInfo> availableFeatures; + public Handler handler; + public ServiceThread handlerThread; + public @Nullable String incidentReportApproverPackage; + public IncrementalManager incrementalManager; + public PackageInstallerService installerService; + public InstantAppRegistry instantAppRegistry; + public InstantAppResolverConnection instantAppResolverConnection; + public ComponentName instantAppResolverSettingsComponent; + public @Nullable IntentFilterVerifier<ParsedIntentInfo> intentFilterVerifier; + public @Nullable ComponentName intentFilterVerifierComponent; + public boolean isPreNmr1Upgrade; + public boolean isPreNupgrade; + public boolean isPreQupgrade; + public boolean isUpgrade; + public DisplayMetrics Metrics; + public ModuleInfoProvider moduleInfoProvider; + public MoveCallbacks moveCallbacks; + public boolean onlyCore; + public OverlayConfig overlayConfig; + public PackageDexOptimizer packageDexOptimizer; + public PackageParser2.Callback packageParserCallback; + public IPermissionManager permissionManagerService; + public PendingPackageBroadcasts pendingPackageBroadcasts; + public PackageManagerInternal pmInternal; + public ProcessLoggingHandler processLoggingHandler; + public ProtectedPackages protectedPackages; + public @NonNull String requiredInstallerPackage; + public @NonNull String requiredPermissionControllerPackage; + public @NonNull String requiredUninstallerPackage; + public @Nullable String requiredVerifierPackage; + public String[] separateProcesses; + public @NonNull String servicesExtensionPackageName; + public @Nullable String setupWizardPackage; + public @NonNull String sharedSystemSharedLibraryPackageName; + public @Nullable String storageManagerPackage; + public @Nullable String defaultTextClassifierPackage; + public @Nullable String systemTextClassifierPackage; + public ViewCompiler viewCompiler; + public @Nullable String wellbeingPackage; + public @Nullable String retailDemoPackage; + public ComponentName resolveComponentName; + public ArrayMap<String, AndroidPackage> packages; + } + private final AppsFilter mAppsFilter; final PackageParser2.Callback mPackageParserCallback; @@ -1396,7 +1452,8 @@ public class PackageManagerService extends IPackageManager.Stub } // Set of pending broadcasts for aggregating enable/disable of components. - static class PendingPackageBroadcasts { + @VisibleForTesting(visibility = Visibility.PACKAGE) + public static class PendingPackageBroadcasts { // for each user id, a map of <package name -> components within that package> final SparseArray<ArrayMap<String, ArrayList<String>>> mUidMap; @@ -1459,7 +1516,7 @@ public class PackageManagerService extends IPackageManager.Stub return map; } } - final PendingPackageBroadcasts mPendingBroadcasts = new PendingPackageBroadcasts(); + final PendingPackageBroadcasts mPendingBroadcasts; static final int SEND_PENDING_BROADCAST = 1; static final int INIT_COPY = 5; @@ -2676,6 +2733,82 @@ public class PackageManagerService extends IPackageManager.Stub } } + /** + * A extremely minimal constructor designed to start up a PackageManagerService instance for + * testing. + * + * It is assumed that all methods under test will mock the internal fields and thus + * none of the initialization is needed. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public PackageManagerService(@NonNull Injector injector, @NonNull TestParams testParams) { + mInjector = injector; + mInjector.bootstrap(this); + mAppsFilter = injector.getAppsFilter(); + mComponentResolver = injector.getComponentResolver(); + mContext = injector.getContext(); + mInstaller = injector.getInstaller(); + mInstallLock = injector.getInstallLock(); + mLock = injector.getLock(); + mPermissionManager = injector.getPermissionManagerServiceInternal(); + mSettings = injector.getSettings(); + mUserManager = injector.getUserManagerService(); + + mApexManager = testParams.apexManager; + mArtManagerService = testParams.artManagerService; + mAvailableFeatures = testParams.availableFeatures; + mDefParseFlags = testParams.defParseFlags; + mDexManager = testParams.dexManager; + mDirsToScanAsSystem = testParams.dirsToScanAsSystem; + mFactoryTest = testParams.factoryTest; + mHandler = testParams.handler; + mHandlerThread = testParams.handlerThread; + mIncrementalManager = testParams.incrementalManager; + mInstallerService = testParams.installerService; + mInstantAppRegistry = testParams.instantAppRegistry; + mInstantAppResolverConnection = testParams.instantAppResolverConnection; + mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent; + mIntentFilterVerifier = testParams.intentFilterVerifier; + mIntentFilterVerifierComponent = testParams.intentFilterVerifierComponent; + mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade; + mIsPreNUpgrade = testParams.isPreNupgrade; + mIsPreQUpgrade = testParams.isPreQupgrade; + mIsUpgrade = testParams.isUpgrade; + mMetrics = testParams.Metrics; + mModuleInfoProvider = testParams.moduleInfoProvider; + mMoveCallbacks = testParams.moveCallbacks; + mOnlyCore = testParams.onlyCore; + mOverlayConfig = testParams.overlayConfig; + mPackageDexOptimizer = testParams.packageDexOptimizer; + mPackageParserCallback = testParams.packageParserCallback; + mPendingBroadcasts = testParams.pendingPackageBroadcasts; + mPermissionManagerService = testParams.permissionManagerService; + mPmInternal = testParams.pmInternal; + mProcessLoggingHandler = testParams.processLoggingHandler; + mProtectedPackages = testParams.protectedPackages; + mSeparateProcesses = testParams.separateProcesses; + mViewCompiler = testParams.viewCompiler; + mRequiredVerifierPackage = testParams.requiredVerifierPackage; + mRequiredInstallerPackage = testParams.requiredInstallerPackage; + mRequiredUninstallerPackage = testParams.requiredUninstallerPackage; + mRequiredPermissionControllerPackage = testParams.requiredPermissionControllerPackage; + mSetupWizardPackage = testParams.setupWizardPackage; + mStorageManagerPackage = testParams.storageManagerPackage; + mDefaultTextClassifierPackage = testParams.defaultTextClassifierPackage; + mSystemTextClassifierPackageName = testParams.systemTextClassifierPackage; + mWellbeingPackage = testParams.wellbeingPackage; + mRetailDemoPackage = testParams.retailDemoPackage; + mDocumenterPackage = testParams.documenterPackage; + mConfiguratorPackage = testParams.configuratorPackage; + mAppPredictionServicePackage = testParams.appPredictionServicePackage; + mIncidentReportApproverPackage = testParams.incidentReportApproverPackage; + mServicesExtensionPackageName = testParams.servicesExtensionPackageName; + mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName; + + mResolveComponentName = testParams.resolveComponentName; + mPackages.putAll(testParams.packages); + } + public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) { PackageManager.invalidatePackageInfoCache(); PackageManager.disableApplicationInfoCache(); @@ -2683,6 +2816,8 @@ public class PackageManagerService extends IPackageManager.Stub final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing", Trace.TRACE_TAG_PACKAGE_MANAGER); + mPendingBroadcasts = new PendingPackageBroadcasts(); + mInjector = injector; mInjector.bootstrap(this); mLock = injector.getLock(); @@ -5168,7 +5303,7 @@ public class PackageManagerService extends IPackageManager.Stub AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName()); if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) { - PackageSetting ps = mSettings.mPackages.get(component.getPackageName()); + PackageSetting ps = mSettings.getPackageLPr(component.getPackageName()); if (ps == null) return null; if (shouldFilterApplicationLocked( ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) { @@ -15609,6 +15744,12 @@ public class PackageManagerService extends IPackageManager.Stub // these install state changes will be persisted in the // upcoming call to mSettings.writeLPr(). } + + if (allUsers != null) { + for (int currentUserId : allUsers) { + ps.resetOverrideComponentLabelIcon(currentUserId); + } + } } // Retrieve the overlays for shared libraries of the package. @@ -20177,6 +20318,86 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public void overrideLabelAndIcon(@NonNull ComponentName componentName, + @NonNull String nonLocalizedLabel, int icon, int userId) { + if (TextUtils.isEmpty(nonLocalizedLabel)) { + throw new IllegalArgumentException("Override label should be a valid String"); + } + updateComponentLabelIcon(componentName, nonLocalizedLabel, icon, userId); + } + + @Override + public void restoreLabelAndIcon(@NonNull ComponentName componentName, int userId) { + updateComponentLabelIcon(componentName, null, null, userId); + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public void updateComponentLabelIcon(/*@NonNull*/ ComponentName componentName, + @Nullable String nonLocalizedLabel, @Nullable Integer icon, int userId) { + if (componentName == null) { + throw new IllegalArgumentException("Must specify a component"); + } + + boolean componentExists = mComponentResolver.componentExists(componentName); + if (!componentExists) { + throw new IllegalArgumentException("Component " + componentName + " not found"); + } + + int callingUid = Binder.getCallingUid(); + + String componentPkgName = componentName.getPackageName(); + int componentUid = getPackageUid(componentPkgName, 0, userId); + if (!UserHandle.isSameApp(callingUid, componentUid)) { + throw new SecurityException("The calling UID (" + callingUid + ")" + + " does not match the target UID"); + } + + String allowedCallerPkg = mContext.getString(R.string.config_overrideComponentUiPackage); + if (TextUtils.isEmpty(allowedCallerPkg)) { + throw new SecurityException( + "There is no package defined as allowed to change a component's label or icon"); + } + + int allowedCallerUid = getPackageUid(allowedCallerPkg, PackageManager.MATCH_SYSTEM_ONLY, + userId); + if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) { + throw new SecurityException("The calling UID (" + callingUid + ")" + + " is not allowed to change a component's label or icon"); + } + + synchronized (mLock) { + AndroidPackage pkg = mPackages.get(componentPkgName); + PackageSetting pkgSetting = getPackageSetting(componentPkgName); + if (pkg == null || pkgSetting == null + || (!pkg.isSystem() && !pkgSetting.getPkgState().isUpdatedSystemApp())) { + throw new SecurityException( + "Changing the label is not allowed for " + componentName); + } + + if (!pkgSetting.overrideNonLocalizedLabelAndIcon(componentName, nonLocalizedLabel, + icon, userId)) { + // Nothing changed + return; + } + } + + ArrayList<String> components = mPendingBroadcasts.get(userId, componentPkgName); + if (components == null) { + components = new ArrayList<>(); + mPendingBroadcasts.put(userId, componentPkgName, components); + } + + String className = componentName.getClassName(); + if (!components.contains(className)) { + components.add(className); + } + + if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) { + mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY); + } + } + + @Override public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags, int userId) { if (!mUserManager.exists(userId)) return; diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 9a8692d029e0..432d7f335ebc 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -26,6 +26,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.permission.PermissionsState; import com.android.server.pm.pkg.PackageStateUnserialized; @@ -43,6 +44,7 @@ import java.util.Set; public class PackageSetting extends PackageSettingBase { int appId; + @Nullable public AndroidPackage pkg; /** * WARNING. The object reference is important. We perform integer equality and NOT @@ -68,7 +70,8 @@ public class PackageSetting extends PackageSettingBase { @NonNull private PackageStateUnserialized pkgState = new PackageStateUnserialized(); - PackageSetting(String name, String realName, File codePath, File resourcePath, + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public PackageSetting(String name, String realName, File codePath, File resourcePath, String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, String cpuAbiOverrideString, long pVersionCode, int pkgFlags, int privateFlags, diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 7cb3df5a0350..00a5fe766593 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -21,6 +21,9 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; @@ -697,6 +700,26 @@ public abstract class PackageSettingBase extends SettingBase { return userState.harmfulAppWarning; } + /** + * @see PackageUserState#overrideLabelAndIcon(ComponentName, String, Integer) + * + * @param userId the specific user to change the label/icon for + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean overrideNonLocalizedLabelAndIcon(@NonNull ComponentName component, + @Nullable String label, @Nullable Integer icon, @UserIdInt int userId) { + return modifyUserState(userId).overrideLabelAndIcon(component, label, icon); + } + + /** + * @see PackageUserState#resetOverrideComponentLabelIcon() + * + * @param userId the specific user to reset + */ + public void resetOverrideComponentLabelIcon(@UserIdInt int userId) { + modifyUserState(userId).resetOverrideComponentLabelIcon(); + } + protected PackageSettingBase updateFrom(PackageSettingBase other) { super.copyFrom(other); this.codePath = other.codePath; diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java index ec9746dabceb..3e2ab05e83ec 100644 --- a/services/core/java/com/android/server/pm/SettingBase.java +++ b/services/core/java/com/android/server/pm/SettingBase.java @@ -18,9 +18,11 @@ package com.android.server.pm; import android.content.pm.ApplicationInfo; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.permission.PermissionsState; -abstract class SettingBase { +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +public abstract class SettingBase { int pkgFlags; int pkgPrivateFlags; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index f6ca87df482f..091535dfc792 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -91,6 +91,7 @@ import android.util.Xml; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -418,6 +419,21 @@ public final class Settings { /** Settings and other information about permissions */ final PermissionSettings mPermissions; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public Settings(Map<String, PackageSetting> pkgSettings) { + mLock = new Object(); + mPackages.putAll(pkgSettings); + mSystemDir = null; + mPermissions = null; + mRuntimePermissionsPersistence = null; + mSettingsFilename = null; + mBackupSettingsFilename = null; + mPackageListFilename = null; + mStoppedPackagesFilename = null; + mBackupStoppedPackagesFilename = null; + mKernelMappingFilename = null; + } + Settings(File dataDir, PermissionSettings permission, Object lock) { mLock = lock; @@ -4328,8 +4344,9 @@ public final class Settings { return userState.isMatch(componentInfo, flags); } - boolean isEnabledAndMatchLPr(AndroidPackage pkg, ParsedMainComponent component, int flags, - int userId) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean isEnabledAndMatchLPr(AndroidPackage pkg, ParsedMainComponent component, + int flags, int userId) { final PackageSetting ps = mPackages.get(component.getPackageName()); if (ps == null) return false; diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 8768ab0a683b..8d53d1554619 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -2385,6 +2385,30 @@ public class ShortcutService extends IShortcutService.Stub { } } + public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage, + @NonNull String packageName, @NonNull String shortcutId, int userId, + @NonNull IntentFilter filter) { + verifyCaller(callingPackage, callingUserId); + enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, + "isSharingShortcut"); + + synchronized (mLock) { + throwIfUserLockedL(userId); + throwIfUserLockedL(callingUserId); + + final List<ShortcutManager.ShareShortcutInfo> matchedTargets = + getPackageShortcutsLocked(packageName, userId) + .getMatchingShareTargets(filter); + final int matchedSize = matchedTargets.size(); + for (int i = 0; i < matchedSize; i++) { + if (matchedTargets.get(i).getShortcutInfo().getId().equals(shortcutId)) { + return true; + } + } + } + return false; + } + @GuardedBy("mLock") private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) { @@ -2969,6 +2993,18 @@ public class ShortcutService extends IShortcutService.Stub { callingPackage, intentFilter, userId).getList(); } + @Override + public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage, + @NonNull String packageName, @NonNull String shortcutId, int userId, + @NonNull IntentFilter filter) { + Preconditions.checkStringNotEmpty(callingPackage, "callingPackage"); + Preconditions.checkStringNotEmpty(packageName, "packageName"); + Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); + + return ShortcutService.this.isSharingShortcut(callingUserId, callingPackage, + packageName, shortcutId, userId, filter); + } + private void updateCachedShortcutsInternal(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId, boolean doCache) { diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 5a1e8e2661b8..137e0aa831d6 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -48,6 +48,7 @@ import android.content.pm.parsing.component.ParsedService; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import android.util.Slog; import com.android.internal.util.ArrayUtils; @@ -271,7 +272,7 @@ public class PackageInfoUtils { ActivityInfo info = PackageInfoWithoutStateUtils.generateActivityInfoUnchecked(a, applicationInfo); - assignSharedFieldsForComponentInfo(info, a, pkgSetting); + assignSharedFieldsForComponentInfo(info, a, pkgSetting, userId); return info; } @@ -306,7 +307,7 @@ public class PackageInfoUtils { ServiceInfo info = PackageInfoWithoutStateUtils.generateServiceInfoUnchecked(s, applicationInfo); - assignSharedFieldsForComponentInfo(info, s, pkgSetting); + assignSharedFieldsForComponentInfo(info, s, pkgSetting, userId); return info; } @@ -333,7 +334,7 @@ public class PackageInfoUtils { } ProviderInfo info = PackageInfoWithoutStateUtils.generateProviderInfoUnchecked(p, flags, applicationInfo); - assignSharedFieldsForComponentInfo(info, p, pkgSetting); + assignSharedFieldsForComponentInfo(info, p, pkgSetting, userId); return info; } @@ -358,7 +359,7 @@ public class PackageInfoUtils { info.nativeLibraryDir = pkg.getNativeLibraryDir(); info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir(); - assignStateFieldsForPackageItemInfo(info, i, pkgSetting); + assignStateFieldsForPackageItemInfo(info, i, pkgSetting, userId); return info; } @@ -426,8 +427,9 @@ public class PackageInfoUtils { } private static void assignSharedFieldsForComponentInfo(@NonNull ComponentInfo componentInfo, - @NonNull ParsedMainComponent mainComponent, @Nullable PackageSetting pkgSetting) { - assignStateFieldsForPackageItemInfo(componentInfo, mainComponent, pkgSetting); + @NonNull ParsedMainComponent mainComponent, @Nullable PackageSetting pkgSetting, + int userId) { + assignStateFieldsForPackageItemInfo(componentInfo, mainComponent, pkgSetting, userId); componentInfo.descriptionRes = mainComponent.getDescriptionRes(); componentInfo.directBootAware = mainComponent.isDirectBootAware(); componentInfo.enabled = mainComponent.isEnabled(); @@ -436,8 +438,12 @@ public class PackageInfoUtils { private static void assignStateFieldsForPackageItemInfo( @NonNull PackageItemInfo packageItemInfo, @NonNull ParsedComponent component, - @Nullable PackageSetting pkgSetting) { - // TODO(b/135203078): Add setting related state + @Nullable PackageSetting pkgSetting, int userId) { + Pair<CharSequence, Integer> labelAndIcon = + ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting, + userId); + packageItemInfo.nonLocalizedLabel = labelAndIcon.first; + packageItemInfo.icon = labelAndIcon.second; } @CheckResult diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java new file mode 100644 index 000000000000..54466ac8f26b --- /dev/null +++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 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.pm.parsing; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.parsing.component.ParsedComponent; +import android.util.Pair; + +import com.android.server.pm.PackageSetting; + +/** + * For exposing internal fields to the rest of the server, enforcing that any overridden state from + * a {@link com.android.server.pm.PackageSetting} is applied. + * + * TODO(chiuwinson): The fields on ParsedComponent are not actually hidden. Will need to find a + * way to enforce the mechanism now that they exist in core instead of server. Can't rely on + * package-private. + * + * @hide + */ +public class ParsedComponentStateUtils { + + @NonNull + public static Pair<CharSequence, Integer> getNonLocalizedLabelAndIcon(ParsedComponent component, + @Nullable PackageSetting pkgSetting, int userId) { + CharSequence label = component.getNonLocalizedLabel(); + int icon = component.getIcon(); + + Pair<String, Integer> overrideLabelIcon = pkgSetting == null ? null : + pkgSetting.readUserState(userId) + .getOverrideLabelIconForComponent(component.getComponentName()); + if (overrideLabelIcon != null) { + if (overrideLabelIcon.first != null) { + label = overrideLabelIcon.first; + } + if (overrideLabelIcon.second != null) { + icon = overrideLabelIcon.second; + } + } + + return Pair.create(label, icon); + } +} diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 27288d852fb2..161f30449a52 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -30,7 +30,6 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.AppOpsManagerInternal; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -48,7 +47,6 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManagerInternal; import android.permission.PermissionControllerManager; -import android.provider.Settings; import android.provider.Telephony; import android.telecom.TelecomManager; import android.util.ArrayMap; @@ -72,9 +70,7 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.ExecutionException; /** @@ -184,6 +180,8 @@ public final class PermissionPolicyService extends SystemService { intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); intentFilter.addDataScheme("package"); + + /* TODO ntmyren: enable receiver when test flakes are fixed getContext().registerReceiverAsUser(new BroadcastReceiver() { final List<Integer> mUserSetupUids = new ArrayList<>(200); final Map<UserHandle, PermissionControllerManager> mPermControllerManagers = @@ -234,6 +232,7 @@ public final class PermissionPolicyService extends SystemService { manager.updateUserSensitiveForApp(uid); } }, UserHandle.ALL, intentFilter, null, null); + */ } /** diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 7eb3f01798c5..d89605a9ddbd 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -542,12 +542,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { void unregisterPointerEventListener(PointerEventListener listener, int displayId); /** - * Retrieves the {@param outBounds} from the stack matching the {@param windowingMode} and - * {@param activityType}. - */ - void getStackBounds(int windowingMode, int activityType, Rect outBounds); - - /** * @return The currently active input method window. */ WindowState getInputMethodWindowLw(); diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java b/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java new file mode 100644 index 000000000000..7977e931c37f --- /dev/null +++ b/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 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.soundtrigger_middleware; + +import android.media.ICaptureStateListener; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import java.util.concurrent.Semaphore; +import java.util.function.Consumer; + +/** + * This is a never-give-up listener for sound trigger external capture state notifications, as + * published by the audio policy service. + * + * This class will constantly try to connect to the service over a background thread and tolerate + * its death. The client will be notified by a single provided function that is called in a + * synchronized manner. + * For simplicity, there is currently no way to stop the tracker. This is possible to add if the + * need ever arises. + */ +class ExternalCaptureStateTracker { + private static final String TAG = "CaptureStateTracker"; + /** Our client's listener. */ + private final Consumer<Boolean> mListener; + /** This semaphore will get a permit every time we need to reconnect. */ + private final Semaphore mNeedToConnect = new Semaphore(1); + + /** + * Constructor. Will start a background thread to do the work. + * + * @param listener A client provided listener that will be called on state + * changes. May be + * called multiple consecutive times with the same value. Never + * called + * concurrently. + */ + ExternalCaptureStateTracker(Consumer<Boolean> listener) { + mListener = listener; + new Thread(this::run).start(); + } + + /** + * Routine for the background thread. Keeps trying to reconnect. + */ + private void run() { + while (true) { + mNeedToConnect.acquireUninterruptibly(); + connect(); + } + } + + /** + * Connect to the service, install listener and death notifier. + */ + private native void connect(); + + /** + * Called by native code to invoke the client listener. + * + * @param active true when external capture is active. + */ + private void setCaptureState(boolean active) { + mListener.accept(active); + } + + /** + * Called by native code when the remote service died. + */ + private void binderDied() { + Log.w(TAG, "Audio policy service died"); + mNeedToConnect.release(); + } +} diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java new file mode 100644 index 000000000000..5def7621c148 --- /dev/null +++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 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.soundtrigger_middleware; + +import android.media.ICaptureStateListener; +import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; + +/** + * This interface unifies ISoundTriggerMiddlewareService with ICaptureStateListener. + */ +public interface ISoundTriggerMiddlewareInternal extends ISoundTriggerMiddlewareService, + ICaptureStateListener { +} diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java index 9f4b09a62aff..d76b1bf71a1c 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java @@ -50,7 +50,7 @@ import java.util.List; * * @hide */ -public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareService { +public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareInternal { static private final String TAG = "SoundTriggerMiddlewareImpl"; private final SoundTriggerModule[] mModules; @@ -124,7 +124,7 @@ public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareServic } @Override - public void setExternalCaptureState(boolean active) { + public void setCaptureState(boolean active) { for (SoundTriggerModule module : mModules) { module.setExternalCaptureState(active); } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java index fa78cb0931c2..04ba6bfeb4ee 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java @@ -62,11 +62,11 @@ import java.util.LinkedList; * String, Object, Object[])}, {@link #logVoidReturnWithObject(Object, String, Object[])} and {@link * #logExceptionWithObject(Object, String, Exception, Object[])}. */ -public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareService, Dumpable { +public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInternal, Dumpable { private static final String TAG = "SoundTriggerMiddlewareLogging"; - private final @NonNull ISoundTriggerMiddlewareService mDelegate; + private final @NonNull ISoundTriggerMiddlewareInternal mDelegate; - public SoundTriggerMiddlewareLogging(@NonNull ISoundTriggerMiddlewareService delegate) { + public SoundTriggerMiddlewareLogging(@NonNull ISoundTriggerMiddlewareInternal delegate) { mDelegate = delegate; } @@ -96,12 +96,12 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareSer } @Override - public void setExternalCaptureState(boolean active) throws RemoteException { + public void setCaptureState(boolean active) throws RemoteException { try { - mDelegate.setExternalCaptureState(active); - logVoidReturn("setExternalCaptureState", active); + mDelegate.setCaptureState(active); + logVoidReturn("setCaptureState", active); } catch (Exception e) { - logException("setExternalCaptureState", e, active); + logException("setCaptureState", e, active); throw e; } } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java index 0d8fc76e1bd2..929d92f56c44 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java @@ -63,14 +63,21 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic static private final String TAG = "SoundTriggerMiddlewareService"; @NonNull - private final ISoundTriggerMiddlewareService mDelegate; + private final ISoundTriggerMiddlewareInternal mDelegate; /** * Constructor for internal use only. Could be exposed for testing purposes in the future. * Users should access this class via {@link Lifecycle}. */ - private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareService delegate) { + private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareInternal delegate) { mDelegate = Objects.requireNonNull(delegate); + new ExternalCaptureStateTracker(active -> { + try { + mDelegate.setCaptureState(active); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + }); } @Override @@ -86,11 +93,6 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic return new ModuleService(mDelegate.attach(handle, callback)); } - @Override - public void setExternalCaptureState(boolean active) throws RemoteException { - mDelegate.setExternalCaptureState(active); - } - @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { if (mDelegate instanceof Dumpable) { ((Dumpable) mDelegate).dump(fout); diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java index 06f2d65c13b2..008933f643dd 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java @@ -105,7 +105,7 @@ import java.util.Set; * * {@hide} */ -public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddlewareService, Dumpable { +public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddlewareInternal, Dumpable { private static final String TAG = "SoundTriggerMiddlewareValidation"; private enum ModuleState { @@ -114,12 +114,12 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware DEAD }; - private final @NonNull ISoundTriggerMiddlewareService mDelegate; + private final @NonNull ISoundTriggerMiddlewareInternal mDelegate; private final @NonNull Context mContext; private Map<Integer, Set<ModuleService>> mModules; public SoundTriggerMiddlewareValidation( - @NonNull ISoundTriggerMiddlewareService delegate, @NonNull Context context) { + @NonNull ISoundTriggerMiddlewareInternal delegate, @NonNull Context context) { mDelegate = delegate; mContext = context; } @@ -213,21 +213,15 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware } @Override - public void setExternalCaptureState(boolean active) { - // Permission check. - checkPreemptPermissions(); - // Input validation (always valid). - - // State validation (always valid). - + public void setCaptureState(boolean active) { + // This is an internal call. No permissions needed. + // // Normally, we would acquire a lock here. However, we do not access any state here so it // is safe to not lock. This call is typically done from a different context than all the // other calls and may result in a deadlock if we lock here (between the audio server and // the system server). - - // From here on, every exception isn't client's fault. try { - mDelegate.setExternalCaptureState(active); + mDelegate.setCaptureState(active); } catch (Exception e) { throw handleException(e); } @@ -252,16 +246,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware /** * Throws a {@link SecurityException} if caller permanently doesn't have the given permission, * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if - * caller temporarily doesn't have the right permissions to preempt active sound trigger - * sessions. - */ - void checkPreemptPermissions() { - enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER); - } - - /** - * Throws a {@link SecurityException} if caller permanently doesn't have the given permission, - * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if * caller temporarily doesn't have the given permission. * * @param permission The permission to check. @@ -806,4 +790,4 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware } } } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 155b2e09284a..24ab89b027b2 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -29,6 +29,7 @@ import static android.os.storage.VolumeInfo.TYPE_PUBLIC; import static android.util.MathUtils.abs; import static android.util.MathUtils.constrain; +import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; @@ -750,6 +751,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent.Builder e = StatsEvent.newBuilder(); e.setAtomId(atomTag); e.writeInt(entry.uid); + e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); if (withFgbg) { e.writeInt(entry.set); } @@ -920,6 +922,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(traffic.getUid()) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeLong(traffic.getRxBytes()) .writeLong(traffic.getTxBytes()) .build(); @@ -1006,6 +1009,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeLong(userTimeUs) .writeLong(systemTimeUs) .build(); @@ -1036,6 +1040,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeInt(freqIndex) .writeLong(cpuFreqTimeMs[freqIndex]) .build(); @@ -1066,6 +1071,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeLong(cpuActiveTimesMs) .build(); pulledData.add(e); @@ -1094,6 +1100,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeInt(i) .writeLong(cpuClusterTimesMs[i]) .build(); @@ -1289,6 +1296,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(processMemoryState.uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeString(processMemoryState.processName) .writeInt(processMemoryState.oomScore) .writeLong(memoryStat.pgfault) @@ -1331,6 +1339,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(managedProcess.uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeString(managedProcess.processName) // RSS high-water mark in bytes. .writeLong(snapshot.rssHighWaterMarkInKilobytes * 1024L) @@ -1350,6 +1359,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(snapshot.uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeString(processCmdlines.valueAt(i)) // RSS high-water mark in bytes. .writeLong(snapshot.rssHighWaterMarkInKilobytes * 1024L) @@ -1384,6 +1394,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(managedProcess.uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeString(managedProcess.processName) .writeInt(managedProcess.pid) .writeInt(managedProcess.oomScore) @@ -1409,6 +1420,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(snapshot.uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeString(processCmdlines.valueAt(i)) .writeInt(pid) .writeInt(-1001) // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1. @@ -1481,6 +1493,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(getUidForPid(allocations.pid)) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeString(readCmdlineFromProcfs(allocations.pid)) .writeInt((int) (allocations.totalSizeInBytes / 1024)) .writeInt(allocations.count) @@ -1593,6 +1606,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(callStat.workSourceUid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeString(callStat.className) .writeString(callStat.methodName) .writeLong(callStat.callCount) @@ -1669,6 +1683,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(entry.workSourceUid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeString(entry.handlerClassName) .writeString(entry.threadName) .writeString(entry.messageName) @@ -2112,6 +2127,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeLong(fgCharsRead) .writeLong(fgCharsWrite) .writeLong(fgBytesRead) @@ -2177,6 +2193,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(st.uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeString(st.name) .writeLong(st.base_utime) .writeLong(st.base_stime) @@ -2235,6 +2252,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent.Builder e = StatsEvent.newBuilder(); e.setAtomId(atomTag); e.writeInt(processCpuUsage.uid); + e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); e.writeInt(processCpuUsage.processId); e.writeInt(threadCpuUsage.threadId); e.writeString(processCpuUsage.processName); @@ -2326,6 +2344,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(bs.uidObj.getUid()) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah)) .build(); pulledData.add(e); @@ -2530,6 +2549,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(pkg.applicationInfo.uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeString(holderName) .writeString(roleName) .build(); @@ -2613,6 +2633,7 @@ public class StatsPullAtomService extends SystemService { e.setAtomId(atomTag); e.writeString(permName); e.writeInt(pkg.applicationInfo.uid); + e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); if (atomTag == FrameworkStatsLog.DANGEROUS_PERMISSION_STATE) { e.writeString(""); } @@ -2967,6 +2988,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent.Builder e = StatsEvent.newBuilder(); e.setAtomId(atomTag); e.writeInt(uid); + e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); e.writeString(packageName); if (atomTag == FrameworkStatsLog.ATTRIBUTED_APP_OPS) { e.writeString(attributionTag); @@ -3015,6 +3037,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent.Builder e = StatsEvent.newBuilder(); e.setAtomId(atomTag); e.writeInt(message.getUid()); + e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); e.writeString(message.getPackageName()); e.writeString(message.getOp()); if (message.getAttributionTag() == null) { diff --git a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java index 8bd10359bc6c..165419a7a38b 100644 --- a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java +++ b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java @@ -22,7 +22,7 @@ import android.annotation.Nullable; import com.android.internal.util.IndentingPrintWriter; -import java.util.LinkedList; +import java.util.ArrayDeque; /** * A class that behaves like the following definition, except it stores the history of values set @@ -50,11 +50,18 @@ import java.util.LinkedList; */ public final class ReferenceWithHistory<V> { - /** The size the history linked list is allowed to grow to. */ + private static final Object NULL_MARKER = "{null marker}"; + + /** The maximum number of references to store. */ private final int mMaxHistorySize; + /** + * The history storage. Note that ArrayDeque doesn't support {@code null} so this stores Object + * and not V. Use {@link #packNullIfRequired(Object)} and {@link #unpackNullIfRequired(Object)} + * to convert to / from the storage object. + */ @Nullable - private LinkedList<V> mValues; + private ArrayDeque<Object> mValues; /** * Creates an instance that records, at most, the specified number of values. @@ -69,22 +76,31 @@ public final class ReferenceWithHistory<V> { /** Returns the current value, or {@code null} if it has never been set. */ @Nullable public V get() { - return (mValues == null || mValues.isEmpty()) ? null : mValues.getFirst(); + if (mValues == null || mValues.isEmpty()) { + return null; + } + Object value = mValues.getFirst(); + return unpackNullIfRequired(value); } - /** Sets the current value. Returns the previous value, or {@code null}. */ + /** + * Sets the current value. Returns the previous value, which can be {@code null} if the + * reference has never been set, or if the reference has been set to {@code null}. + */ @Nullable public V set(@Nullable V newValue) { if (mValues == null) { - mValues = new LinkedList<>(); + mValues = new ArrayDeque<>(mMaxHistorySize); } - V previous = get(); - - mValues.addFirst(newValue); - if (mValues.size() > mMaxHistorySize) { + if (mValues.size() >= mMaxHistorySize) { mValues.removeLast(); } + + V previous = get(); + + Object nullSafeValue = packNullIfRequired(newValue); + mValues.addFirst(nullSafeValue); return previous; } @@ -96,8 +112,8 @@ public final class ReferenceWithHistory<V> { ipw.println("{Empty}"); } else { int i = 0; - for (V value : mValues) { - ipw.println(i + ": " + value); + for (Object value : mValues) { + ipw.println(i + ": " + unpackNullIfRequired(value)); i++; } } @@ -115,4 +131,23 @@ public final class ReferenceWithHistory<V> { public String toString() { return String.valueOf(get()); } + + /** + * Turns a non-nullable Object into a nullable value. See also + * {@link #packNullIfRequired(Object)}. + */ + @SuppressWarnings("unchecked") + @Nullable + private V unpackNullIfRequired(@NonNull Object value) { + return value == NULL_MARKER ? null : (V) value; + } + + /** + * Turns a nullable value into a non-nullable Object. See also + * {@link #unpackNullIfRequired(Object)}. + */ + @NonNull + private Object packNullIfRequired(@Nullable V value) { + return value == null ? NULL_MARKER : value; + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 01081404a502..ce7e79714c39 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -503,6 +503,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */ final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>(); + /** Windows whose client's insets states are not up-to-date. */ + final ArrayList<WindowState> mWinInsetsChanged = new ArrayList<>(); + private ScreenRotationAnimation mScreenRotationAnimation; /** @@ -708,7 +711,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } // Sets mBehindIme for each window. Windows behind IME can get IME insets. - w.mBehindIme = mTmpWindowsBehindIme; + if (w.mBehindIme != mTmpWindowsBehindIme) { + w.mBehindIme = mTmpWindowsBehindIme; + mWinInsetsChanged.add(w); + } if (w == mInputMethodWindow) { mTmpWindowsBehindIme = true; } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 367151cf0f79..221258e94cb2 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -3246,9 +3246,14 @@ public class DisplayPolicy { mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState); final int dockedAppearance = updateLightStatusBarAppearanceLw(0 /* vis */, mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState); - mService.getStackBounds( - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds); - final boolean inSplitScreen = !mDockedStackBounds.isEmpty(); + final boolean inSplitScreen = + mService.mRoot.getDefaultDisplay().mTaskContainers.isSplitScreenModeActivated(); + if (inSplitScreen) { + mService.getStackBounds(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, + mDockedStackBounds); + } else { + mDockedStackBounds.setEmpty(); + } mService.getStackBounds(inSplitScreen ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds); diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 9e954f29438c..007af249c580 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -398,7 +398,7 @@ class InsetsPolicy { /** Called on SurfaceAnimationThread without global WM lock held. */ @Override - public void scheduleApplyChangeInsets() { + public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) { InsetsState state = getState(); if (mAnimationControl.applyChangeInsets(state)) { mAnimationControl.finish(mAnimatingShown); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 04454a5b33ec..ba14d48d38ea 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -227,10 +227,19 @@ class InsetsStateController { for (int i = mProviders.size() - 1; i >= 0; i--) { mProviders.valueAt(i).onPostLayout(); } + final ArrayList<WindowState> winInsetsChanged = mDisplayContent.mWinInsetsChanged; if (!mLastState.equals(mState)) { mLastState.set(mState, true /* copySources */); notifyInsetsChanged(); + } else { + // The global insets state has not changed but there might be windows whose conditions + // (e.g., z-order) have changed. They can affect the insets states that we dispatch to + // the clients. + for (int i = winInsetsChanged.size() - 1; i >= 0; i--) { + winInsetsChanged.get(i).notifyInsetsChanged(); + } } + winInsetsChanged.clear(); } void onInsetsModified(InsetsControlTarget windowState, InsetsState state) { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 29a2e18f46a8..e43f4b485349 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -304,7 +304,11 @@ class WallpaperController { } } - boolean updateWallpaperOffset(WindowState wallpaperWin, int dw, int dh, boolean sync) { + boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) { + final DisplayInfo displayInfo = wallpaperWin.getDisplayInfo(); + final int dw = displayInfo.logicalWidth; + final int dh = displayInfo.logicalHeight; + int xOffset = 0; int yOffset = 0; boolean rawChanged = false; @@ -444,10 +448,6 @@ class WallpaperController { } private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) { - final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo(); - final int dw = displayInfo.logicalWidth; - final int dh = displayInfo.logicalHeight; - WindowState target = mWallpaperTarget; if (target != null) { if (target.mWallpaperX >= 0) { @@ -484,7 +484,7 @@ class WallpaperController { } for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { - mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(dw, dh, sync); + mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(sync); } } diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index e29580beca50..203ca25ecf6e 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -73,11 +73,11 @@ class WallpaperWindowToken extends WindowToken { } } - void updateWallpaperOffset(int dw, int dh, boolean sync) { + void updateWallpaperOffset(boolean sync) { final WallpaperController wallpaperController = mDisplayContent.mWallpaperController; for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { final WindowState wallpaper = mChildren.get(wallpaperNdx); - if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) { + if (wallpaperController.updateWallpaperOffset(wallpaper, sync)) { // We only want to be synchronous with one wallpaper. sync = false; } @@ -85,10 +85,6 @@ class WallpaperWindowToken extends WindowToken { } void updateWallpaperVisibility(boolean visible) { - final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo(); - final int dw = displayInfo.logicalWidth; - final int dh = displayInfo.logicalHeight; - if (isVisible() != visible) { // Need to do a layout to ensure the wallpaper now has the correct size. mDisplayContent.setLayoutNeeded(); @@ -98,7 +94,7 @@ class WallpaperWindowToken extends WindowToken { for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { final WindowState wallpaper = mChildren.get(wallpaperNdx); if (visible) { - wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false); + wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */); } wallpaper.dispatchWallpaperVisibility(visible); @@ -145,19 +141,11 @@ class WallpaperWindowToken extends WindowToken { } } - DisplayInfo displayInfo = getFixedRotationTransformDisplayInfo(); - if (displayInfo == null) { - displayInfo = mDisplayContent.getDisplayInfo(); - } - - final int dw = displayInfo.logicalWidth; - final int dh = displayInfo.logicalHeight; - for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { final WindowState wallpaper = mChildren.get(wallpaperNdx); if (visible) { - wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false); + wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */); } // First, make sure the client has the current visibility state. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8e457522c4b0..dfaa0ec47155 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2332,9 +2332,7 @@ public class WindowManagerService extends IWindowManager.Stub Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (toBeDisplayed && win.mIsWallpaper) { - DisplayInfo displayInfo = displayContent.getDisplayInfo(); - displayContent.mWallpaperController.updateWallpaperOffset( - win, displayInfo.logicalWidth, displayInfo.logicalHeight, false); + displayContent.mWallpaperController.updateWallpaperOffset(win, false /* sync */); } if (win.mActivityRecord != null) { win.mActivityRecord.updateReportedVisibilityLocked(); @@ -2782,7 +2780,6 @@ public class WindowManagerService extends IWindowManager.Stub aspectRatio); } - @Override public void getStackBounds(int windowingMode, int activityType, Rect bounds) { synchronized (mGlobalLock) { final ActivityStack stack = mRoot.getStack(windowingMode, activityType); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 7dcf37557692..b87d18143fc7 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1101,7 +1101,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - final ActivityStack stack = getRootTask(); layoutDisplayFrame = new Rect(windowFrames.mDisplayFrame); windowFrames.mDisplayFrame.set(windowFrames.mContainingFrame); layoutXDiff = mInsetFrame.left - windowFrames.mContainingFrame.left; @@ -1205,8 +1204,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mIsWallpaper && (fw != windowFrames.mFrame.width() || fh != windowFrames.mFrame.height())) { - dc.mWallpaperController.updateWallpaperOffset(this, - displayInfo.logicalWidth, displayInfo.logicalHeight, false /* sync */); + dc.mWallpaperController.updateWallpaperOffset(this, false /* sync */); } // Calculate relative frame diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 563710b9e41b..b25383b15421 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1381,7 +1381,8 @@ class WindowStateAnimator { return true; } - if (isEntrance && mWin.mAttrs.type == TYPE_INPUT_METHOD) { + final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD; + if (isEntrance && isImeWindow) { mWin.getDisplayContent().adjustForImeIfNeeded(); mWin.setDisplayLayoutNeeded(); mService.mWindowPlacerLocked.requestTraversal(); @@ -1435,11 +1436,11 @@ class WindowStateAnimator { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); mAnimationIsEntrance = isEntrance; } - } else { + } else if (!isImeWindow) { mWin.cancelAnimation(); } - if (!isEntrance && mWin.mAttrs.type == TYPE_INPUT_METHOD) { + if (!isEntrance && isImeWindow) { mWin.getDisplayContent().adjustForImeIfNeeded(); } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 74982c6918a2..4c3f73d2d129 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -40,6 +40,7 @@ cc_library_static { "com_android_server_security_VerityUtils.cpp", "com_android_server_SerialService.cpp", "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp", + "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp", "com_android_server_stats_pull_StatsPullAtomService.cpp", "com_android_server_storage_AppFuseBridge.cpp", "com_android_server_SystemServer.cpp", diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp index e9a5e58e49d1..853eba71d88a 100644 --- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp +++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp @@ -392,6 +392,7 @@ private: mArgs = params.arguments(); mIfs = ifs; mStatusListener = statusListener; + mIfs->setParams({.readLogsEnabled = true}); return true; } bool onStart() final { return true; } @@ -438,7 +439,7 @@ private: } const auto fileId = IncFs_FileIdFromMetadata(file.metadata); - const auto incfsFd(mIfs->openWrite(fileId)); + const base::unique_fd incfsFd(mIfs->openForSpecialOps(fileId).release()); if (incfsFd < 0) { ALOGE("Failed to open an IncFS file for metadata: %.*s, final file name is: %s. " "Error %d", @@ -716,7 +717,7 @@ private: auto& writeFd = writeFds[fileIdx]; if (writeFd < 0) { - writeFd.reset(this->mIfs->openWrite(fileId)); + writeFd.reset(this->mIfs->openForSpecialOps(fileId).release()); if (writeFd < 0) { ALOGE("Failed to open file %d for writing (%d). Aborting.", header.fileIdx, -writeFd); diff --git a/services/core/jni/com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp b/services/core/jni/com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp new file mode 100644 index 000000000000..ae6cb187fa47 --- /dev/null +++ b/services/core/jni/com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2020 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 <sstream> + +#define LOG_TAG "ExternalCaptureStateTracker" + +#include "core_jni_helpers.h" +#include <log/log.h> +#include <media/AudioSystem.h> + +namespace android { +namespace { + +#define PACKAGE "com/android/server/soundtrigger_middleware" +#define CLASSNAME PACKAGE "/ExternalCaptureStateTracker" + +jclass gExternalCaptureStateTrackerClassId; +jmethodID gSetCaptureStateMethodId; +jmethodID gBinderDiedMethodId; + +void PopulateIds(JNIEnv* env) { + gExternalCaptureStateTrackerClassId = + (jclass) env->NewGlobalRef(FindClassOrDie(env, CLASSNAME)); + gSetCaptureStateMethodId = GetMethodIDOrDie(env, + gExternalCaptureStateTrackerClassId, + "setCaptureState", + "(Z)V"); + gBinderDiedMethodId = GetMethodIDOrDie(env, + gExternalCaptureStateTrackerClassId, + "binderDied", + "()V"); +} + +class Listener : public AudioSystem::CaptureStateListener { +public: + Listener(JNIEnv* env, jobject obj) : mObj(env->NewGlobalRef(obj)) {} + + ~Listener() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mObj); + } + + void onStateChanged(bool active) override { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mObj, gSetCaptureStateMethodId, active); + } + + void onServiceDied() override { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mObj, gBinderDiedMethodId); + } + +private: + jobject mObj; +}; + +void connect(JNIEnv* env, jobject obj) { + sp<AudioSystem::CaptureStateListener> listener(new Listener(env, obj)); + status_t status = + AudioSystem::registerSoundTriggerCaptureStateListener(listener); + LOG_ALWAYS_FATAL_IF(status != NO_ERROR); +} + +const JNINativeMethod gMethods[] = { + {"connect", "()V", reinterpret_cast<void*>(connect)}, +}; + +} // namespace + +int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker( + JNIEnv* env) { + PopulateIds(env); + return RegisterMethodsOrDie(env, + CLASSNAME, + gMethods, + NELEM(gMethods)); +} + +} // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index eb486fea0116..b988bd45d786 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -58,6 +58,8 @@ int register_android_server_am_CachedAppOptimizer(JNIEnv* env); int register_android_server_am_LowMemDetector(JNIEnv* env); int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl( JNIEnv* env); +int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker( + JNIEnv* env); int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env); int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env); int register_android_server_AdbDebuggingManager(JNIEnv* env); @@ -112,6 +114,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_am_LowMemDetector(env); register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl( env); + register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker( + env); register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env); register_android_server_stats_pull_StatsPullAtomService(env); register_android_server_AdbDebuggingManager(env); diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 2dbbc5ac6806..97de1800cae2 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -155,6 +155,11 @@ binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) { return ok(); } +binder::Status BinderIncrementalService::setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) { + *_aidl_return = mImpl.setStorageParams(storage, enableReadLogs); + return ok(); +} + binder::Status BinderIncrementalService::makeDirectory(int32_t storageId, const std::string& path, int32_t* _aidl_return) { *_aidl_return = mImpl.makeDir(storageId, path); diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index 28613e101b7c..d0357d924586 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -71,6 +71,7 @@ public: binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath, const std::string& libDirRelativePath, const std::string& abi, bool* _aidl_return) final; + binder::Status setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) final; private: android::incremental::IncrementalService mImpl; diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 25da8fe4a2e8..5e3c337da11e 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -563,6 +563,36 @@ StorageId IncrementalService::findStorageId(std::string_view path) const { return it->second->second.storage; } +int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) { + const auto ifs = getIfs(storageId); + if (!ifs) { + return -EINVAL; + } + + using unique_fd = ::android::base::unique_fd; + ::android::os::incremental::IncrementalFileSystemControlParcel control; + control.cmd.reset(unique_fd(dup(ifs->control.cmd()))); + control.pendingReads.reset(unique_fd(dup(ifs->control.pendingReads()))); + auto logsFd = ifs->control.logs(); + if (logsFd >= 0) { + control.log.reset(unique_fd(dup(logsFd))); + } + + std::lock_guard l(mMountOperationLock); + const auto status = mVold->setIncFsMountOptions(control, enableReadLogs); + if (!status.isOk()) { + LOG(ERROR) << "Calling Vold::setIncFsMountOptions() failed: " << status.toString8(); + return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC + ? status.serviceSpecificErrorCode() > 0 ? -status.serviceSpecificErrorCode() + : status.serviceSpecificErrorCode() == 0 + ? -EFAULT + : status.serviceSpecificErrorCode() + : -EIO; + } + + return 0; +} + void IncrementalService::deleteStorage(StorageId storageId) { const auto ifs = getIfs(storageId); if (!ifs) { @@ -737,10 +767,12 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m if (auto ifs = getIfs(storage)) { std::string normPath = normalizePathToStorage(ifs, storage, path); if (normPath.empty()) { + LOG(ERROR) << "Internal error: storageId " << storage << " failed to normalize: " << path; return -EINVAL; } auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params); if (err) { + LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err; return err; } std::vector<uint8_t> metadataBytes; @@ -1182,8 +1214,8 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ success = false; break; } - android::base::unique_fd writeFd(mIncFs->openWrite(ifs->control, libFileId)); - if (writeFd < 0) { + const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId); + if (!writeFd.ok()) { LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd; success = false; break; diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index 406b32e51044..90d58a7adcf0 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -111,6 +111,8 @@ public: int unbind(StorageId storage, std::string_view target); void deleteStorage(StorageId storage); + int setStorageParams(StorageId storage, bool enableReadLogs); + int makeFile(StorageId storage, std::string_view path, int mode, FileId id, incfs::NewFileParams params); int makeDir(StorageId storage, std::string_view path, int mode = 0755); diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index c70a47d40c4e..c3300305f515 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -49,6 +49,7 @@ public: virtual binder::Status unmountIncFs(const std::string& dir) const = 0; virtual binder::Status bindMount(const std::string& sourceDir, const std::string& targetDir) const = 0; + virtual binder::Status setIncFsMountOptions(const ::android::os::incremental::IncrementalFileSystemControlParcel& control, bool enableReadLogs) const = 0; }; class DataLoaderManagerWrapper { @@ -76,7 +77,7 @@ public: virtual ErrorCode link(const Control& control, std::string_view from, std::string_view to) const = 0; virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0; - virtual base::unique_fd openWrite(const Control& control, FileId id) const = 0; + virtual base::unique_fd openForSpecialOps(const Control& control, FileId id) const = 0; virtual ErrorCode writeBlocks(Span<const DataBlock> blocks) const = 0; }; @@ -106,6 +107,9 @@ public: const std::string& targetDir) const override { return mInterface->bindMount(sourceDir, targetDir); } + binder::Status setIncFsMountOptions(const ::android::os::incremental::IncrementalFileSystemControlParcel& control, bool enableReadLogs) const override { + return mInterface->setIncFsMountOptions(control, enableReadLogs); + } private: sp<os::IVold> mInterface; @@ -177,8 +181,8 @@ public: ErrorCode unlink(const Control& control, std::string_view path) const override { return incfs::unlink(control, path); } - base::unique_fd openWrite(const Control& control, FileId id) const override { - return base::unique_fd{incfs::openWrite(control, id)}; + base::unique_fd openForSpecialOps(const Control& control, FileId id) const override { + return base::unique_fd{incfs::openForSpecialOps(control, id).release()}; } ErrorCode writeBlocks(Span<const DataBlock> blocks) const override { return incfs::writeBlocks(blocks); diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index c4b4d1746cbe..cde38fbb3ca2 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -52,6 +52,8 @@ public: MOCK_CONST_METHOD1(unmountIncFs, binder::Status(const std::string& dir)); MOCK_CONST_METHOD2(bindMount, binder::Status(const std::string& sourceDir, const std::string& argetDir)); + MOCK_CONST_METHOD2(setIncFsMountOptions, + binder::Status(const ::android::os::incremental::IncrementalFileSystemControlParcel&, bool)); void mountIncFsFails() { ON_CALL(*this, mountIncFs(_, _, _, _)) @@ -74,6 +76,14 @@ public: void bindMountSuccess() { ON_CALL(*this, bindMount(_, _)).WillByDefault(Return(binder::Status::ok())); } + void setIncFsMountOptionsFails() const { + ON_CALL(*this, setIncFsMountOptions(_, _)) + .WillByDefault( + Return(binder::Status::fromExceptionCode(1, String8("failed to set options")))); + } + void setIncFsMountOptionsSuccess() { + ON_CALL(*this, setIncFsMountOptions(_, _)).WillByDefault(Return(binder::Status::ok())); + } binder::Status getInvalidControlParcel(const std::string& imagePath, const std::string& targetDir, int32_t flags, IncrementalFileSystemControlParcel* _aidl_return) { @@ -175,7 +185,7 @@ public: MOCK_CONST_METHOD3(link, ErrorCode(const Control& control, std::string_view from, std::string_view to)); MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path)); - MOCK_CONST_METHOD2(openWrite, base::unique_fd(const Control& control, FileId id)); + MOCK_CONST_METHOD2(openForSpecialOps, base::unique_fd(const Control& control, FileId id)); MOCK_CONST_METHOD1(writeBlocks, ErrorCode(Span<const DataBlock> blocks)); void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); } @@ -390,6 +400,42 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderSuccess) { ASSERT_TRUE(mIncrementalService->startLoading(storageId)); } +TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) { + mVold->mountIncFsSuccess(); + mIncFs->makeFileSuccess(); + mVold->bindMountSuccess(); + mVold->setIncFsMountOptionsSuccess(); + mDataLoaderManager->initializeDataLoaderSuccess(); + mDataLoaderManager->getDataLoaderSuccess(); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); + EXPECT_CALL(*mVold, setIncFsMountOptions(_, _)); + TemporaryDir tempDir; + int storageId = + mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, + IncrementalService::CreateOptions::CreateNew); + ASSERT_GE(storageId, 0); + ASSERT_GE(mIncrementalService->setStorageParams(storageId, true), 0); +} + +TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) { + mVold->mountIncFsSuccess(); + mIncFs->makeFileSuccess(); + mVold->bindMountSuccess(); + mVold->setIncFsMountOptionsFails(); + mDataLoaderManager->initializeDataLoaderSuccess(); + mDataLoaderManager->getDataLoaderSuccess(); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); + EXPECT_CALL(*mVold, setIncFsMountOptions(_, _)); + TemporaryDir tempDir; + int storageId = + mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, + IncrementalService::CreateOptions::CreateNew); + ASSERT_GE(storageId, 0); + ASSERT_LT(mIncrementalService->setStorageParams(storageId, true), 0); +} + TEST_F(IncrementalServiceTest, testMakeDirectory) { mVold->mountIncFsSuccess(); mIncFs->makeFileSuccess(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 1939313ff59b..2a914ecf4db6 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -30,6 +30,7 @@ import android.annotation.StringRes; import android.app.ActivityThread; import android.app.AppCompatCallbacks; import android.app.INotificationManager; +import android.app.SystemServiceRegistry; import android.app.usage.UsageStatsManagerInternal; import android.content.ContentResolver; import android.content.Context; @@ -513,6 +514,8 @@ public final class SystemServer { Looper.getMainLooper().setSlowLogThresholdMs( SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS); + SystemServiceRegistry.sEnableServiceNotFoundWtf = true; + // Initialize native services. System.loadLibrary("android_servers"); diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java index 09e333ee3471..db464e732e91 100644 --- a/services/net/java/android/net/ip/IpClientManager.java +++ b/services/net/java/android/net/ip/IpClientManager.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.net.NattKeepalivePacketData; import android.net.ProxyInfo; import android.net.TcpKeepalivePacketData; +import android.net.shared.Layer2Information; import android.net.shared.ProvisioningConfiguration; import android.net.util.KeepalivePacketDataUtil; import android.os.Binder; @@ -292,4 +293,20 @@ public class IpClientManager { Binder.restoreCallingIdentity(token); } } + + /** + * Update the bssid, L2 key and group hint layer2 information. + */ + public boolean updateLayer2Information(Layer2Information info) { + final long token = Binder.clearCallingIdentity(); + try { + mIpClient.updateLayer2Information(info.toStableParcelable()); + return true; + } catch (RemoteException e) { + log("Error updating layer2 information", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } } diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index 136ee91dd685..c87ece29800c 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -220,7 +220,7 @@ public class DataManager { String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null; @Event.EventType int eventType = mimeTypeToShareEventType(mimeType); EventHistoryImpl eventHistory; - if (ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE.equals(event.getLaunchLocation())) { + if (ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE.equals(event.getLaunchLocation())) { // Direct share event if (appTarget.getShortcutInfo() == null) { return; diff --git a/services/tests/PackageManagerComponentOverrideTests/Android.bp b/services/tests/PackageManagerComponentOverrideTests/Android.bp new file mode 100644 index 000000000000..a2668a184fe0 --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/Android.bp @@ -0,0 +1,40 @@ +// +// Copyright (C) 2019 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. +// + +// NOTE: This test is separate from service tests since it relies on same vs different calling UID, +// and this is more representative of a real caller. It also uses Mockito extended, and this +// prevents converting the entire services test module. +android_test { + name: "PackageManagerComponentOverrideTests", + srcs: [ + "src/**/*.kt" + ], + static_libs: [ + "androidx.test.runner", + "mockito-target-extended-minus-junit4", + "services.core", + "servicestests-utils-mockito-extended", + "testng", // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows + "truth-prebuilt", + ], + + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + test_suites: ["device-tests"], + platform_apis: true, +} diff --git a/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml b/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml new file mode 100644 index 000000000000..c25e1122c730 --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.override"> + + <uses-sdk + android:minSdkVersion="1" + android:targetSdkVersion="28" /> + + <application android:debuggable="true"> + <uses-library android:name="android.test.mock" android:required="true" /> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:label="PackageManagerComponentOverrideTests" + android:targetPackage="com.android.server.pm.test.override" /> + +</manifest> diff --git a/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml b/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml new file mode 100644 index 000000000000..b83b1a8fb113 --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2020 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. + --> + +<configuration description="Test module config for PackageManagerComponentOverrideTests"> + <option name="test-tag" value="PackageManagerComponentOverrideTests" /> + + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="PackageManagerComponentOverrideTests.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.server.pm.test.override" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png b/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png Binary files differnew file mode 100644 index 000000000000..86b12fca81cc --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png diff --git a/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png b/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png Binary files differnew file mode 100644 index 000000000000..49dbb4fd7a46 --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png diff --git a/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml b/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml new file mode 100644 index 000000000000..73d11281c96f --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <public name="black16x16" type="drawable" id="0x7f080001"/> + <public name="white16x16" type="drawable" id="0x7f080002"/> +</resources> diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt new file mode 100644 index 000000000000..ecdb30f5e84b --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2020 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.pm.test.override + +import android.content.ComponentName +import android.content.Context +import android.content.pm.parsing.component.ParsedActivity +import android.os.Binder +import android.os.UserHandle +import android.util.ArrayMap +import com.android.server.pm.AppsFilter +import com.android.server.pm.ComponentResolver +import com.android.server.pm.PackageManagerService +import com.android.server.pm.PackageSetting +import com.android.server.pm.Settings +import com.android.server.pm.UserManagerService +import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.parsing.pkg.PackageImpl +import com.android.server.pm.parsing.pkg.ParsedPackage +import com.android.server.pm.permission.PermissionManagerServiceInternal +import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType +import com.android.server.pm.test.override.R +import com.android.server.testutils.TestHandler +import com.android.server.testutils.mock +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.spy +import com.android.server.testutils.whenever +import com.android.server.wm.ActivityTaskManagerInternal +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.Mockito.any +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.anyString +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.intThat +import org.mockito.Mockito.never +import org.mockito.Mockito.same +import org.mockito.Mockito.verify +import org.testng.Assert.assertThrows +import java.io.File + +@RunWith(Parameterized::class) +class PackageManagerComponentLabelIconOverrideTest { + + companion object { + private const val VALID_PKG = "com.android.server.pm.test.override" + private const val SHARED_PKG = "com.android.server.pm.test.override.shared" + private const val INVALID_PKG = "com.android.server.pm.test.override.invalid" + + private const val SEND_PENDING_BROADCAST = 1 // PackageManagerService.SEND_PENDING_BROADCAST + + private const val DEFAULT_LABEL = "DefaultLabel" + private const val TEST_LABEL = "TestLabel" + + private const val DEFAULT_ICON = R.drawable.black16x16 + private const val TEST_ICON = R.drawable.white16x16 + + private const val COMPONENT_CLASS_NAME = ".TestComponent" + + sealed class Result { + // Component label/icon changed, message sent to send broadcast + object Changed : Result() + + // Component label/icon changed, message was pending, not re-sent + object ChangedWithoutNotify : Result() + + // Component label/icon did not changed, was already equivalent + object NotChanged : Result() + + // Updating label/icon encountered a specific exception + data class Exception(val type: Class<out java.lang.Exception>) : Result() + } + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun parameters() = arrayOf( + // Start with an array of the simplest known inputs and expected outputs + Params(VALID_PKG, AppType.SYSTEM_APP, Result.Changed), + Params(SHARED_PKG, AppType.SYSTEM_APP, Result.Changed), + Params(INVALID_PKG, AppType.SYSTEM_APP, SecurityException::class.java) + ) + .flatMap { param -> + mutableListOf(param).apply { + if (param.result is Result.Changed) { + // For each param that would've succeeded, also verify that if a change + // happened, but a message was pending, another is not re-queued/reset + this += param.copy(result = Result.ChangedWithoutNotify) + // Also verify that when the component is already configured, no change + // is propagated + this += param.copy(result = Result.NotChanged) + } + // For all params, verify that an invalid component will cause an + // IllegalArgumentException, instead of result initially specified + this += param.copy(componentName = null, + result = Result.Exception(IllegalArgumentException::class.java)) + // Also verify an updated system app variant, which should have the same + // result as a vanilla system app + this += param.copy(appType = AppType.UPDATED_SYSTEM_APP) + // Also verify a non-system app will cause a failure, since normal apps + // are not allowed to edit their label/icon + this += param.copy(appType = AppType.NORMAL_APP, + result = Result.Exception(SecurityException::class.java)) + } + } + + data class Params( + val pkgName: String, + private val appType: AppType, + val result: Result, + val componentName: ComponentName? = ComponentName(pkgName, COMPONENT_CLASS_NAME) + ) { + constructor(pkgName: String, appType: AppType, exception: Class<out Exception>) + : this(pkgName, appType, Result.Exception(exception)) + + val expectedLabel = when (result) { + Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_LABEL + is Result.Exception -> DEFAULT_LABEL + } + + val expectedIcon = when (result) { + Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_ICON + is Result.Exception -> DEFAULT_ICON + } + + val isUpdatedSystemApp = appType == AppType.UPDATED_SYSTEM_APP + val isSystem = appType == AppType.SYSTEM_APP || isUpdatedSystemApp + + override fun toString(): String { + val resultString = when (result) { + Result.Changed -> "Changed" + Result.ChangedWithoutNotify -> "ChangedWithoutNotify" + Result.NotChanged -> "NotChanged" + is Result.Exception -> result.type.simpleName + } + + // Nicer formatting for the test method suffix + return "pkg=$pkgName, type=$appType, component=$componentName, result=$resultString" + } + + enum class AppType { SYSTEM_APP, UPDATED_SYSTEM_APP, NORMAL_APP } + } + } + + @Parameterized.Parameter(0) + lateinit var params: Params + + private lateinit var testHandler: TestHandler + private lateinit var mockPendingBroadcasts: PackageManagerService.PendingPackageBroadcasts + private lateinit var mockPkg: AndroidPackage + private lateinit var mockPkgSetting: PackageSetting + private lateinit var service: PackageManagerService + + private val userId = UserHandle.getCallingUserId() + private val userIdDifferent = userId + 1 + + @Before + fun setUpMocks() { + makeTestData() + + testHandler = TestHandler(null) + if (params.result is Result.ChangedWithoutNotify) { + // Case where the handler already has a message and so another should not be sent. + // This case will verify that only 1 message exists, which is the one added here. + testHandler.sendEmptyMessage(SEND_PENDING_BROADCAST) + } + + mockPendingBroadcasts = PackageManagerService.PendingPackageBroadcasts() + + service = mockService() + } + + @Test + fun updateComponentLabelIcon() { + fun runUpdate() { + service.updateComponentLabelIcon(params.componentName, TEST_LABEL, TEST_ICON, userId) + } + + when (val result = params.result) { + Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> { + runUpdate() + verify(mockPkgSetting).overrideNonLocalizedLabelAndIcon(params.componentName!!, + TEST_LABEL, TEST_ICON, userId) + } + is Result.Exception -> { + assertThrows(result.type) { runUpdate() } + verify(mockPkgSetting, never()).overrideNonLocalizedLabelAndIcon( + any<ComponentName>(), any(), anyInt(), anyInt()) + } + } + } + + @After + fun verifyExpectedResult() { + if (params.componentName != null) { + val activityInfo = service.getActivityInfo(params.componentName, 0, userId) + assertThat(activityInfo.nonLocalizedLabel).isEqualTo(params.expectedLabel) + assertThat(activityInfo.icon).isEqualTo(params.expectedIcon) + } + } + + @After + fun verifyDifferentUserUnchanged() { + when (params.result) { + Result.Changed, Result.ChangedWithoutNotify -> { + val activityInfo = service.getActivityInfo(params.componentName, 0, userIdDifferent) + assertThat(activityInfo.nonLocalizedLabel).isEqualTo(DEFAULT_LABEL) + assertThat(activityInfo.icon).isEqualTo(DEFAULT_ICON) + } + Result.NotChanged, is Result.Exception -> {} + }.run { /*exhaust*/ } + } + + @After + fun verifyHandlerHasMessage() { + when (params.result) { + is Result.Changed, is Result.ChangedWithoutNotify -> { + assertThat(testHandler.pendingMessages).hasSize(1) + assertThat(testHandler.pendingMessages.first().message.what) + .isEqualTo(SEND_PENDING_BROADCAST) + } + is Result.NotChanged, is Result.Exception -> { + assertThat(testHandler.pendingMessages).hasSize(0) + } + }.run { /*exhaust*/ } + } + + @After + fun verifyPendingBroadcast() { + when (params.result) { + is Result.Changed, Result.ChangedWithoutNotify -> { + assertThat(mockPendingBroadcasts.get(userId, params.pkgName)) + .containsExactly(params.componentName!!.className) + .inOrder() + } + is Result.NotChanged, is Result.Exception -> { + assertThat(mockPendingBroadcasts.get(userId, params.pkgName)).isNull() + } + }.run { /*exhaust*/ } + } + + private fun makePkg(pkgName: String, block: ParsedPackage.() -> Unit = {}) = + PackageImpl.forTesting(pkgName) + .setEnabled(true) + .let { it.hideAsParsed() as ParsedPackage } + .setSystem(params.isSystem) + .apply(block) + .hideAsFinal() + + private fun makePkgSetting(pkgName: String) = spy(PackageSetting(pkgName, null, File("/test"), + File("/test"), null, null, null, null, 0, 0, 0, 0, null, null, null)) { + this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp + } + + private fun makeTestData() { + mockPkg = makePkg(params.pkgName) + mockPkgSetting = makePkgSetting(params.pkgName) + + if (params.result is Result.NotChanged) { + // If verifying no-op behavior, set the current setting to the test values + mockPkgSetting.overrideNonLocalizedLabelAndIcon(params.componentName!!, TEST_LABEL, + TEST_ICON, userId) + // Then clear the mock because the line above just incremented it + clearInvocations(mockPkgSetting) + } + } + + private fun mockService(): PackageManagerService { + val mockedPkgs = mapOf( + // Must use the test app's UID so that PMS can match them when querying, since + // the static Binder.getCallingUid can't mocked as it's marked final + VALID_PKG to makePkg(VALID_PKG) { uid = Binder.getCallingUid() }, + SHARED_PKG to makePkg(SHARED_PKG) { uid = Binder.getCallingUid() }, + INVALID_PKG to makePkg(INVALID_PKG) { uid = Binder.getCallingUid() + 1 } + ) + val mockedPkgSettings = mapOf( + VALID_PKG to makePkgSetting(VALID_PKG), + SHARED_PKG to makePkgSetting(SHARED_PKG), + INVALID_PKG to makePkgSetting(INVALID_PKG) + ) + // Add pkgSetting under test so its attributes override the defaults added above + .plus(params.pkgName to mockPkgSetting) + + val mockActivity: ParsedActivity = mock { + whenever(this.packageName) { params.pkgName } + whenever(this.nonLocalizedLabel) { DEFAULT_LABEL } + whenever(this.icon) { DEFAULT_ICON } + whenever(this.componentName) { params.componentName } + whenever(this.name) { params.componentName?.className } + whenever(this.isEnabled) { true } + whenever(this.isDirectBootAware) { params.isSystem } + } + + val mockSettings = Settings(mockedPkgSettings) + val mockComponentResolver: ComponentResolver = mockThrowOnUnmocked { + params.componentName?.let { + whenever(this.componentExists(same(it))) { true } + whenever(this.getActivity(same(it))) { mockActivity } + } + } + val mockUserManagerService: UserManagerService = mockThrowOnUnmocked { + val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent } + whenever(this.exists(intThat(matcher))) { true } + whenever(this.isUserUnlockingOrUnlocked(intThat(matcher))) { true } + } + val mockPermissionManagerService: PermissionManagerServiceInternal = mockThrowOnUnmocked { + whenever(this.enforceCrossUserPermission(anyInt(), anyInt(), anyBoolean(), anyBoolean(), + anyString())) { } + } + val mockActivityTaskManager: ActivityTaskManagerInternal = mockThrowOnUnmocked { + whenever(this.isCallerRecents(anyInt())) { false } + } + val mockAppsFilter: AppsFilter = mockThrowOnUnmocked { + whenever(this.shouldFilterApplication(anyInt(), any<PackageSetting>(), + any<PackageSetting>(), anyInt())) { false } + } + val mockContext: Context = mockThrowOnUnmocked { + whenever(this.getString( + com.android.internal.R.string.config_overrideComponentUiPackage)) { VALID_PKG } + } + val mockInjector: PackageManagerService.Injector = mock { + whenever(this.lock) { Object() } + whenever(this.componentResolver) { mockComponentResolver } + whenever(this.userManagerService) { mockUserManagerService } + whenever(this.permissionManagerServiceInternal) { mockPermissionManagerService } + whenever(this.settings) { mockSettings } + whenever(this.activityTaskManagerInternal) { mockActivityTaskManager } + whenever(this.appsFilter) { mockAppsFilter } + whenever(this.context) { mockContext } + } + val testParams = PackageManagerService.TestParams().apply { + this.handler = testHandler + this.pendingPackageBroadcasts = mockPendingBroadcasts + this.resolveComponentName = ComponentName("android", ".Test") + this.packages = ArrayMap<String, AndroidPackage>().apply { putAll(mockedPkgs) } + } + + return PackageManagerService(mockInjector, testParams) + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 5c8220049d09..736a7be5e39e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -79,7 +79,8 @@ public class RescuePartyTest { private static final String CALLING_PACKAGE2 = "com.package.name2"; private static final String NAMESPACE1 = "namespace1"; private static final String NAMESPACE2 = "namespace2"; - private static final String DISABLE_RESCUE_PARTY_FLAG = "disable_rescue_party"; + private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = + "persist.device_config.configuration.disable_rescue_party"; private MockitoSession mSession; private HashMap<String, String> mSystemSettingsMap; @@ -172,6 +173,7 @@ public class RescuePartyTest { Integer.toString(RescueParty.LEVEL_NONE)); SystemProperties.set(RescueParty.PROP_RESCUE_BOOT_COUNT, Integer.toString(0)); SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); + SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false)); } @After @@ -317,13 +319,6 @@ public class RescuePartyTest { @Test public void testExplicitlyEnablingAndDisablingRescue() { - // mock the DeviceConfig get call to avoid hitting - // android.permission.READ_DEVICE_CONFIG when calling real DeviceConfig. - doReturn(true) - .when(() -> DeviceConfig.getBoolean( - eq(DeviceConfig.NAMESPACE_CONFIGURATION), - eq(DISABLE_RESCUE_PARTY_FLAG), - eq(false))); SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true)); assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, @@ -336,18 +331,15 @@ public class RescuePartyTest { @Test public void testDisablingRescueByDeviceConfigFlag() { - doReturn(true) - .when(() -> DeviceConfig.getBoolean( - eq(DeviceConfig.NAMESPACE_CONFIGURATION), - eq(DISABLE_RESCUE_PARTY_FLAG), - eq(false))); SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); + SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true)); assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false); // Restore the property value initalized in SetUp() SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); + SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false)); } @Test diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index e58e91179931..b457856e8630 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -107,6 +107,8 @@ java_library { name: "servicestests-utils", srcs: [ "utils/**/*.java", + "utils/**/*.kt", + "utils-mockito/**/*.kt", ], static_libs: [ "junit", @@ -117,6 +119,22 @@ java_library { ], } +java_library { + name: "servicestests-utils-mockito-extended", + srcs: [ + "utils/**/*.java", + "utils/**/*.kt", + "utils-mockito/**/*.kt", + ], + static_libs: [ + "junit", + "mockito-target-extended-minus-junit4", + ], + libs: [ + "android.test.runner", + ], +} + filegroup { name: "servicestests-SuspendTestApp-files", srcs: [ diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt index 063cd5dacc93..78c708084d38 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt +++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt @@ -18,6 +18,8 @@ package com.android.server.om import android.net.Uri import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 51996047a74b..728e1492c0d5 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -299,7 +299,7 @@ public final class DataManagerTest { .build(); AppTargetEvent appTargetEvent = new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH) - .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE) + .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE) .build(); IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg"); mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter); @@ -319,7 +319,7 @@ public final class DataManagerTest { .build(); AppTargetEvent appTargetEvent = new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH) - .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE) + .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE) .build(); IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg"); @@ -667,7 +667,7 @@ public final class DataManagerTest { .build(); AppTargetEvent appTargetEvent = new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH) - .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE) + .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE) .build(); IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg"); 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 2cbb6d5c5bd6..06b344b3b94f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -8595,6 +8595,56 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } } + public void testIsSharingShortcut() throws IntentFilter.MalformedMimeTypeException { + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), + R.xml.shortcut_share_targets); + updatePackageVersion(CALLING_PACKAGE_1, 1); + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); + + setCaller(CALLING_PACKAGE_1, USER_0); + + final ShortcutInfo s1 = makeShortcutWithCategory("s1", + set("com.test.category.CATEGORY1", "com.test.category.CATEGORY2")); + final ShortcutInfo s2 = makeShortcutWithCategory("s2", + set("com.test.category.CATEGORY5", "com.test.category.CATEGORY6")); + final ShortcutInfo s3 = makeShortcut("s3"); + + assertTrue(mManager.setDynamicShortcuts(list(s1, s2, s3))); + assertShortcutIds(assertAllNotKeyFieldsOnly(mManager.getDynamicShortcuts()), + "s1", "s2", "s3"); + + IntentFilter filter_cat1 = new IntentFilter(); + filter_cat1.addDataType("text/plain"); + IntentFilter filter_cat5 = new IntentFilter(); + filter_cat5.addDataType("video/*"); + IntentFilter filter_any = new IntentFilter(); + filter_any.addDataType("*/*"); + + setCaller(LAUNCHER_1, USER_0); + mCallerPermissions.add(permission.MANAGE_APP_PREDICTIONS); + + assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s1", USER_0, + filter_cat1)); + assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s1", USER_0, + filter_cat5)); + assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s1", USER_0, + filter_any)); + + assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s2", USER_0, + filter_cat1)); + assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s2", USER_0, + filter_cat5)); + assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s2", USER_0, + filter_any)); + + assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s3", USER_0, + filter_any)); + assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s4", USER_0, + filter_any)); + } + private Uri getFileUriFromResource(String fileName, int resId) throws IOException { File file = new File(getTestContext().getFilesDir(), fileName); // Make sure we are not leaving phantom files behind. diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt index 191c038c3052..5412bb5106ff 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt @@ -18,7 +18,9 @@ package com.android.server.pm.parsing import android.content.pm.PackageManager import android.platform.test.annotations.Presubmit +import com.google.common.truth.Expect import com.google.common.truth.Truth.assertWithMessage +import org.junit.Rule import org.junit.Test /** @@ -28,6 +30,9 @@ import org.junit.Test @Presubmit class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { + @get:Rule + val expect = Expect.create() + @Test fun applicationInfoEquality() { val flags = PackageManager.GET_META_DATA or PackageManager.GET_SHARED_LIBRARY_FILES @@ -41,7 +46,8 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { } else { "$firstName | $secondName" } - assertWithMessage(packageName).that(it.first?.dumpToString()) + expect.withMessage("${it.first?.sourceDir} $packageName") + .that(it.first?.dumpToString()) .isEqualTo(it.second?.dumpToString()) } } @@ -71,7 +77,8 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { } else { "$firstName | $secondName" } - assertWithMessage(packageName).that(it.first?.dumpToString()) + expect.withMessage("${it.first?.applicationInfo?.sourceDir} $packageName") + .that(it.first?.dumpToString()) .isEqualTo(it.second?.dumpToString()) } } diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt index f532dd87909e..7b1b2d2f5c2b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt @@ -29,14 +29,17 @@ import android.content.pm.PermissionInfo import android.content.pm.ProviderInfo import android.os.Debug import android.os.Environment +import android.os.ServiceManager import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.om.mockThrowOnUnmocked -import com.android.server.om.whenever +import com.android.internal.compat.IPlatformCompat import com.android.server.pm.PackageManagerService import com.android.server.pm.PackageSetting import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.pkg.PackageStateUnserialized +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever +import org.junit.After import org.junit.BeforeClass import org.mockito.Mockito import org.mockito.Mockito.anyInt @@ -59,7 +62,27 @@ open class AndroidPackageParsingTestBase { setCallback { false /* hasFeature */ } } - protected val packageParser2 = TestPackageParser2() + private val platformCompat = IPlatformCompat.Stub + .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)) + + protected val packageParser2 = PackageParser2(null /* separateProcesses */, + false /* onlyCoreApps */, context.resources.displayMetrics, null /* cacheDir */, + object : PackageParser2.Callback() { + override fun isChangeEnabled( + changeId: Long, + appInfo: ApplicationInfo + ): Boolean { + // This test queries PlatformCompat because prebuilts in the tree + // may not be updated to be compliant with the latest enforcement checks. + return platformCompat.isChangeEnabled(changeId, appInfo) + } + + // Assume the device doesn't support anything. This will affect permission + // parsing and will force <uses-permission/> declarations to include all + // requiredNotFeature permissions and exclude all requiredFeature permissions. + // This mirrors the old behavior. + override fun hasFeature(feature: String) = false + }) /** * It would be difficult to mock all possibilities, so just use the APKs on device. @@ -91,22 +114,29 @@ open class AndroidPackageParsingTestBase { lateinit var newPackages: List<AndroidPackage> + private val thrownInSetUp = mutableListOf<Throwable>() + @Suppress("ConstantConditionIf") @JvmStatic @BeforeClass fun setUpPackages() { - this.oldPackages = apks.map { - packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) + this.oldPackages = apks.mapNotNull { + tryOrNull { + packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) + } } - this.newPackages = apks.map { - packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) + this.newPackages = apks.mapNotNull { + tryOrNull { + packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) + } } if (DUMP_HPROF_TO_EXTERNAL) { System.gc() Environment.getExternalStorageDirectory() - .resolve("${AndroidPackageParsingTestBase::class.java.simpleName}.hprof") + .resolve( + "${AndroidPackageParsingTestBase::class.java.simpleName}.hprof") .absolutePath .run(Debug::dumpHprofData) } @@ -135,6 +165,36 @@ open class AndroidPackageParsingTestBase { this.pkg = aPkg whenever(pkgState) { PackageStateUnserialized() } } + + private fun <T> tryOrNull(block: () -> T) = try { + block() + } catch (t: Throwable) { + thrownInSetUp.add(t) + null + } + } + + @After + fun verifySetUpPackages() { + if (thrownInSetUp.isEmpty()) return + val exception = AssertionError("setUpPackages failed with ${thrownInSetUp.size} errors:\n" + + thrownInSetUp.joinToString(separator = "\n") { it.message.orEmpty() }) + + /* + Testing infrastructure doesn't currently support errors thrown in @AfterClass, + so instead it's thrown here. But to avoid throwing a massive repeated stack for every + test method, only throw on the first method run in the class, clearing the list so that + subsequent methods can run without failing. Doing this in @After lets true method + failures propagate, as those should throw before this does. + + This will cause the failure to be attached to a different method depending on run order, + which could make comparisons difficult. So if a failure points here, it's worth + checking failures for all methods in all subclasses. + + TODO: When infrastructure supports @AfterClass errors, move this + */ + thrownInSetUp.clear() + throw exception } // The following methods dump an exact set of fields from the object to compare, because diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java index 06b5fe48c4cf..ebcf10dd019f 100644 --- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java +++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java @@ -1106,7 +1106,7 @@ public class SoundTriggerMiddlewareImplTest { public void testAbortRecognition() throws Exception { // Make sure the HAL doesn't support concurrent capture. initService(false); - mService.setExternalCaptureState(false); + mService.setCaptureState(false); ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); @@ -1120,7 +1120,7 @@ public class SoundTriggerMiddlewareImplTest { startRecognition(module, handle, hwHandle); // Abort. - mService.setExternalCaptureState(true); + mService.setCaptureState(true); ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass( RecognitionEvent.class); @@ -1142,7 +1142,7 @@ public class SoundTriggerMiddlewareImplTest { verifyNotStartRecognition(); // Now enable it and make sure we are notified. - mService.setExternalCaptureState(false); + mService.setCaptureState(false); verify(callback).onRecognitionAvailabilityChange(true); // Unload the model. @@ -1154,7 +1154,7 @@ public class SoundTriggerMiddlewareImplTest { public void testAbortPhraseRecognition() throws Exception { // Make sure the HAL doesn't support concurrent capture. initService(false); - mService.setExternalCaptureState(false); + mService.setCaptureState(false); ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); @@ -1168,7 +1168,7 @@ public class SoundTriggerMiddlewareImplTest { startRecognition(module, handle, hwHandle); // Abort. - mService.setExternalCaptureState(true); + mService.setCaptureState(true); ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass( PhraseRecognitionEvent.class); @@ -1190,7 +1190,7 @@ public class SoundTriggerMiddlewareImplTest { verifyNotStartRecognition(); // Now enable it and make sure we are notified. - mService.setExternalCaptureState(false); + mService.setCaptureState(false); verify(callback).onRecognitionAvailabilityChange(true); // Unload the model. @@ -1216,7 +1216,7 @@ public class SoundTriggerMiddlewareImplTest { startRecognition(module, handle, hwHandle); // Signal concurrent capture. Shouldn't abort. - mService.setExternalCaptureState(true); + mService.setCaptureState(true); verify(callback, never()).onRecognition(anyInt(), any()); verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean()); @@ -1252,7 +1252,7 @@ public class SoundTriggerMiddlewareImplTest { startRecognition(module, handle, hwHandle); // Signal concurrent capture. Shouldn't abort. - mService.setExternalCaptureState(true); + mService.setCaptureState(true); verify(callback, never()).onPhraseRecognition(anyInt(), any()); verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean()); diff --git a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt index 0f915dbdcf6f..056fa886f640 100644 --- a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt +++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.om +package com.android.server.testutils import org.mockito.Answers import org.mockito.Mockito @@ -31,6 +31,13 @@ object MockitoUtils { else -> { val arguments = it.arguments ?.takeUnless { it.isEmpty() } + ?.mapIndexed { index, arg -> + try { + arg?.toString() + } catch (e: Exception) { + "toString[$index] threw ${e.message}" + } + } ?.joinToString() ?.let { "with $it" @@ -46,6 +53,8 @@ object MockitoUtils { inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block) +fun <T> spy(value: T, block: T.() -> Unit = {}) = Mockito.spy(value).apply(block) + fun <Type> Stubber.whenever(mock: Type) = Mockito.`when`(mock) fun <Type : Any?> whenever(mock: Type) = Mockito.`when`(mock) @@ -55,7 +64,7 @@ fun <Type : Any?> whenever(mock: Type, block: InvocationOnMock.() -> Any?) = fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { } -inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit): T { +inline fun <reified T> spyThrowOnUnmocked(value: T?, block: T.() -> Unit): T { val swappingAnswer = object : Answer<Any?> { var delegate: Answer<*> = Answers.RETURNS_DEFAULTS @@ -64,9 +73,12 @@ inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit): T { } } - return Mockito.mock(T::class.java, swappingAnswer).apply(block) + return Mockito.mock(T::class.java, Mockito.withSettings().spiedInstance(value) + .defaultAnswer(swappingAnswer)).apply(block) .also { // To allow when() usage inside block, only swap to throwing afterwards swappingAnswer.delegate = MockitoUtils.ANSWER_THROWS } } + +inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit) = spyThrowOnUnmocked<T>(null, block) diff --git a/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java index 69db38438609..355e7f333bb7 100644 --- a/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java +++ b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java @@ -139,7 +139,7 @@ public class TestHandler extends Handler { } } - private class MsgInfo implements Comparable<MsgInfo> { + public class MsgInfo implements Comparable<MsgInfo> { public final Message message; public final long sendTime; public final RuntimeException postPoint; 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 f083f0e707bd..f9596b53407f 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -6504,4 +6504,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertNull(conversations.get(0).getShortcutInfo()); assertNull(conversations.get(1).getShortcutInfo()); } + + @Test + public void testShortcutHelperNull_doesntCrashEnqueue() throws RemoteException { + mService.setShortcutHelper(null); + NotificationRecord nr = + generateMessageBubbleNotifRecord(mTestNotificationChannel, + "testShortcutHelperNull_doesntCrashEnqueue"); + try { + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + } catch (Exception e) { + fail(e.getMessage()); + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index 08e492a7b0ff..8b91c7e5d5f3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -73,7 +73,6 @@ public class AppTransitionTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void testKeyguardOverride() { mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */); @@ -81,7 +80,6 @@ public class AppTransitionTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void testKeyguardKeep() { mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */); mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); @@ -89,7 +87,6 @@ public class AppTransitionTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void testForceOverride() { mWm.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE, false /* alwaysKeepCurrent */); mDc.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, @@ -105,7 +102,6 @@ public class AppTransitionTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void testKeepKeyguard_withCrashing() { mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */); mWm.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */); @@ -113,7 +109,6 @@ public class AppTransitionTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void testAppTransitionStateForMultiDisplay() { // Create 2 displays & presume both display the state is ON for ready to display & animate. final DisplayContent dc1 = createNewDisplay(Display.STATE_ON); @@ -182,7 +177,6 @@ public class AppTransitionTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void testLoadAnimationSafely() { DisplayContent dc = createNewDisplay(Display.STATE_ON); assertNull(dc.mAppTransition.loadAnimationSafely( diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java index 6e78a271458a..b93a8fcc115b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java @@ -67,7 +67,6 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void clipAfterAnim_boundsLayerIsCreated() { mActivity.mNeedsAnimationBoundsLayer = true; @@ -91,7 +90,6 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void clipAfterAnim_boundsLayerIsDestroyed() { mActivity.mNeedsAnimationBoundsLayer = true; mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, @@ -126,7 +124,6 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void clipNoneAnim_boundsLayerIsNotCreated() { mActivity.mNeedsAnimationBoundsLayer = false; diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 9cfee344ce30..38b3d76b447d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1039,6 +1039,13 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(config90.orientation, app.getConfiguration().orientation); assertEquals(config90.windowConfiguration.getBounds(), app.getBounds()); + // Force the negative offset to verify it can be updated. + mWallpaperWindow.mWinAnimator.mXOffset = mWallpaperWindow.mWinAnimator.mYOffset = -1; + assertTrue(mDisplayContent.mWallpaperController.updateWallpaperOffset(mWallpaperWindow, + false /* sync */)); + assertThat(mWallpaperWindow.mWinAnimator.mXOffset).isGreaterThan(-1); + assertThat(mWallpaperWindow.mWinAnimator.mYOffset).isGreaterThan(-1); + mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token); // The animation in old rotation should be cancelled. diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 7928e7602df3..28ae36abbb5b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -677,7 +677,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - @FlakyTest(bugId = 149760800) public void layoutWindowLw_withLongEdgeDisplayCutout() { addLongEdgeDisplayCutout(); @@ -698,7 +697,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - @FlakyTest(bugId = 149760800) public void layoutWindowLw_withLongEdgeDisplayCutout_never() { addLongEdgeDisplayCutout(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index c370d6c7c516..d0fd50dc497b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -277,7 +277,6 @@ public class DisplayPolicyTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void testOverlappingWithNavBar() { final WindowState targetWin = createApplicationWindow(); final WindowFrames winFrame = targetWin.getWindowFrames(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index 956c200022e9..0eee3ca53c7d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -147,19 +147,16 @@ public class DragDropControllerTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void testDragFlow() { dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0); } @Test - @FlakyTest(bugId = 131005232) public void testPerformDrag_NullDataWithGrantUri() { dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0); } @Test - @FlakyTest(bugId = 131005232) public void testPerformDrag_NullDataToOtherUser() { final WindowState otherUsersWindow = createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index b21ea796396c..89bc65b5a44d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -58,7 +58,6 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@FlakyTest(detail = "Promote to pre-submit once confirmed stable.") @Presubmit @RunWith(WindowTestRunner.class) public class InsetsPolicyTest extends WindowTestsBase { diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index db7bce4c8753..61b74b0c1d0f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -32,6 +32,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; @@ -49,7 +53,6 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@FlakyTest(detail = "Promote to pre-submit once confirmed stable.") @Presubmit @RunWith(WindowTestRunner.class) public class InsetsStateControllerTest extends WindowTestsBase { @@ -68,7 +71,6 @@ public class InsetsStateControllerTest extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void testStripForDispatch_notOwn() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); @@ -102,7 +104,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null); - assertEquals(0, getController().getInsetsForDispatch(navBar).getSourcesCount()); + assertNull(getController().getInsetsForDispatch(navBar).peekSource(ITYPE_IME)); + assertNull(getController().getInsetsForDispatch(navBar).peekSource(ITYPE_STATUS_BAR)); } @Test @@ -169,6 +172,45 @@ public class InsetsStateControllerTest extends WindowTestsBase { } @Test + public void testStripForDispatch_imeOrderChanged() { + getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); + + // This window can be the IME target while app cannot be the IME target. + createWindow(null, TYPE_APPLICATION, "base"); + + // Send our spy window (app) into the system so that we can detect the invocation. + final WindowState win = createWindow(null, TYPE_APPLICATION, "app"); + final WindowToken parent = win.mToken; + parent.removeChild(win); + final WindowState app = spy(win); + parent.addWindow(app); + + // Adding FLAG_NOT_FOCUSABLE makes app above IME. + app.mAttrs.flags |= FLAG_NOT_FOCUSABLE; + mDisplayContent.computeImeTarget(true); + mDisplayContent.setLayoutNeeded(); + mDisplayContent.applySurfaceChangesTransaction(); + + // app won't get IME insets while above IME. + assertNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_IME)); + + // Reset invocation counter. + clearInvocations(app); + + // Removing FLAG_NOT_FOCUSABLE makes app below IME. + app.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE; + mDisplayContent.computeImeTarget(true); + mDisplayContent.setLayoutNeeded(); + mDisplayContent.applySurfaceChangesTransaction(); + + // Make sure app got notified. + verify(app, atLeast(1)).notifyInsetsChanged(); + + // app will get IME insets while below IME. + assertNotNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_IME)); + } + + @Test public void testStripForDispatch_childWindow_altFocusable() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); @@ -247,7 +289,6 @@ public class InsetsStateControllerTest extends WindowTestsBase { assertNull(getController().getControlsForDispatch(app)); } - @FlakyTest(bugId = 124088319) @Test public void testControlRevoked_animation() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 34ac835ae18d..67aab7ec3fbf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -146,7 +146,6 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { } @Test - @FlakyTest(bugId = 133372977) public void testTimeout() throws Exception { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord, diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java index 9fc160229d45..12ed3c28161f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java @@ -74,7 +74,6 @@ public class TaskPersisterTest { } @Test - @FlakyTest(bugId = 131005232) public void testTaskIdsPersistence() { SparseBooleanArray taskIdsOnFile = new SparseBooleanArray(); for (int i = 0; i < 100; i++) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java index ea52d7d4b189..93dcc9103640 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java @@ -518,7 +518,6 @@ public class TaskPositionerTests extends WindowTestsBase { assertEquals(expected, actual); } - @FlakyTest(bugId = 129492888) @Test public void testFinishingMovingWhenBinderDied() { spyOn(mWm.mTaskPositioningController); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java index ca84932b8f03..75226b7e66f7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java @@ -90,7 +90,6 @@ public class TaskPositioningControllerTests extends WindowTestsBase { assertNull(mTarget.getDragWindowHandleLocked()); } - @FlakyTest(bugId = 129507487) @Test public void testFinishPositioningWhenAppRequested() { assertFalse(mTarget.isPositioningLocked()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index 31d68a4a8c5b..f76809b06510 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -184,7 +184,6 @@ public class TaskRecordTests extends ActivityTestsBase { /** Ensures that bounds on freeform stacks are not clipped. */ @Test - @FlakyTest(bugId = 137879065) public void testAppBounds_FreeFormBounds() { final Rect freeFormBounds = new Rect(mParentBounds); freeFormBounds.offset(10, 10); @@ -194,7 +193,6 @@ public class TaskRecordTests extends ActivityTestsBase { /** Ensures that fully contained bounds are not clipped. */ @Test - @FlakyTest(bugId = 137879065) public void testAppBounds_ContainedBounds() { final Rect insetBounds = new Rect(mParentBounds); insetBounds.inset(5, 5, 5, 5); @@ -203,7 +201,6 @@ public class TaskRecordTests extends ActivityTestsBase { } @Test - @FlakyTest(bugId = 137879065) public void testFitWithinBounds() { final Rect parentBounds = new Rect(10, 10, 200, 200); DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay(); @@ -243,7 +240,6 @@ public class TaskRecordTests extends ActivityTestsBase { /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */ @Test - @FlakyTest(bugId = 137879065) public void testBoundsOnModeChangeFreeformToFullscreen() { DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay(); ActivityStack stack = new StackBuilder(mRootWindowContainer).setDisplay(display) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index e95ccab38960..820d3816a6f6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -449,7 +449,6 @@ public class WindowStateTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 74078662) public void testLayoutSeqResetOnReparent() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); app.mLayoutSeq = 1; @@ -508,7 +507,6 @@ public class WindowStateTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 74078662) public void testDisplayCutoutIsCalculatedRelativeToFrame() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); WindowFrames wf = app.getWindowFrames(); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 4e5be5c453b7..9ae905df8776 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -12344,6 +12344,9 @@ public class TelephonyManager { @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @IsMultiSimSupportedResult public int isMultiSimSupported() { + if (getSupportedModemCount() < 2) { + return TelephonyManager.MULTISIM_NOT_SUPPORTED_BY_HARDWARE; + } try { ITelephony service = getITelephony(); if (service != null) { diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 6fdc13e6a31b..d524299d7ede 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -493,6 +493,7 @@ public interface RILConstants { int RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT = 209; int RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS = 210; int RIL_REQUEST_GET_BARRING_INFO = 211; + int RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION = 212; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index 2b5720a47eb6..8de27e8eb281 100644 --- a/tests/net/common/java/android/net/LinkPropertiesTest.java +++ b/tests/net/common/java/android/net/LinkPropertiesTest.java @@ -445,14 +445,20 @@ public class LinkPropertiesTest { // Check comparisons work. LinkProperties lp2 = new LinkProperties(lp); assertAllRoutesHaveInterface("wlan0", lp2); - assertEquals(0, lp.compareAllRoutes(lp2).added.size()); - assertEquals(0, lp.compareAllRoutes(lp2).removed.size()); + // LinkProperties#compareAllRoutes exists both in R and before R, but the return type + // changed in R, so a test compiled with the R version of LinkProperties cannot run on Q. + if (isAtLeastR()) { + assertEquals(0, lp.compareAllRoutes(lp2).added.size()); + assertEquals(0, lp.compareAllRoutes(lp2).removed.size()); + } lp2.setInterfaceName("p2p0"); assertAllRoutesHaveInterface("p2p0", lp2); assertAllRoutesNotHaveInterface("wlan0", lp2); - assertEquals(3, lp.compareAllRoutes(lp2).added.size()); - assertEquals(3, lp.compareAllRoutes(lp2).removed.size()); + if (isAtLeastR()) { + assertEquals(3, lp.compareAllRoutes(lp2).added.size()); + assertEquals(3, lp.compareAllRoutes(lp2).removed.size()); + } // Remove route with incorrect interface, no route removed. lp.removeRoute(new RouteInfo(prefix2, null, null)); @@ -480,6 +486,8 @@ public class LinkPropertiesTest { assertEquals(1, rmnet0.getLinkAddresses().size()); assertEquals(1, rmnet0.getAllAddresses().size()); assertEquals(1, rmnet0.getAllLinkAddresses().size()); + assertEquals(1, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); rmnet0.addStackedLink(clat4); assertEquals(1, rmnet0.getStackedLinks().size()); @@ -487,6 +495,9 @@ public class LinkPropertiesTest { assertEquals(1, rmnet0.getLinkAddresses().size()); assertEquals(2, rmnet0.getAllAddresses().size()); assertEquals(2, rmnet0.getAllLinkAddresses().size()); + assertEquals(2, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); + assertEquals("clat4", rmnet0.getAllInterfaceNames().get(1)); rmnet0.addStackedLink(clat4); assertEquals(1, rmnet0.getStackedLinks().size()); @@ -494,6 +505,9 @@ public class LinkPropertiesTest { assertEquals(1, rmnet0.getLinkAddresses().size()); assertEquals(2, rmnet0.getAllAddresses().size()); assertEquals(2, rmnet0.getAllLinkAddresses().size()); + assertEquals(2, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); + assertEquals("clat4", rmnet0.getAllInterfaceNames().get(1)); assertEquals(0, clat4.getStackedLinks().size()); @@ -513,6 +527,8 @@ public class LinkPropertiesTest { assertEquals(1, rmnet0.getLinkAddresses().size()); assertEquals(1, rmnet0.getAllAddresses().size()); assertEquals(1, rmnet0.getAllLinkAddresses().size()); + assertEquals(1, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); assertFalse(rmnet0.removeStackedLink("clat4")); } @@ -936,7 +952,7 @@ public class LinkPropertiesTest { } - @Test + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) public void testCompareResult() { // Either adding or removing items compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1), @@ -1197,4 +1213,48 @@ public class LinkPropertiesTest { lp.clear(); assertNull(lp.getCaptivePortalData()); } + + private LinkProperties makeIpv4LinkProperties() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(NAME); + linkProperties.addLinkAddress(LINKADDRV4); + linkProperties.addDnsServer(DNS1); + linkProperties.addRoute(new RouteInfo(GATEWAY1)); + linkProperties.addRoute(new RouteInfo(GATEWAY2)); + return linkProperties; + } + + private LinkProperties makeIpv6LinkProperties() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(NAME); + linkProperties.addLinkAddress(LINKADDRV6); + linkProperties.addDnsServer(DNS6); + linkProperties.addRoute(new RouteInfo(GATEWAY61)); + linkProperties.addRoute(new RouteInfo(GATEWAY62)); + return linkProperties; + } + + @Test + public void testHasIpv4DefaultRoute() { + final LinkProperties Ipv4 = makeIpv4LinkProperties(); + assertTrue(Ipv4.hasIpv4DefaultRoute()); + final LinkProperties Ipv6 = makeIpv6LinkProperties(); + assertFalse(Ipv6.hasIpv4DefaultRoute()); + } + + @Test + public void testHasIpv4DnsServer() { + final LinkProperties Ipv4 = makeIpv4LinkProperties(); + assertTrue(Ipv4.hasIpv4DnsServer()); + final LinkProperties Ipv6 = makeIpv6LinkProperties(); + assertFalse(Ipv6.hasIpv4DnsServer()); + } + + @Test + public void testHasIpv6DnsServer() { + final LinkProperties Ipv4 = makeIpv4LinkProperties(); + assertFalse(Ipv4.hasIpv6DnsServer()); + final LinkProperties Ipv6 = makeIpv6LinkProperties(); + assertTrue(Ipv6.hasIpv6DnsServer()); + } } diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp index 3eabb14e3fd4..54c5b9059fb0 100644 --- a/tools/stats_log_api_gen/java_writer.cpp +++ b/tools/stats_log_api_gen/java_writer.cpp @@ -39,6 +39,15 @@ static int write_java_q_logger_class(FILE* out, const SignatureInfoMap& signatur return 0; } +static void write_java_annotation_constants(FILE* out) { + fprintf(out, " // Annotation constants.\n"); + + for (const auto& [id, name] : ANNOTATION_ID_CONSTANTS) { + fprintf(out, " public static final byte %s = %hhu;\n", name.c_str(), id); + } + fprintf(out, "\n"); +} + static void write_annotations(FILE* out, int argIndex, const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet) { FieldNumberToAtomDeclSet::const_iterator fieldNumberToAtomDeclSetIt = @@ -48,32 +57,28 @@ static void write_annotations(FILE* out, int argIndex, } const AtomDeclSet& atomDeclSet = fieldNumberToAtomDeclSetIt->second; for (const shared_ptr<AtomDecl>& atomDecl : atomDeclSet) { - fprintf(out, " if (code == %d) {\n", atomDecl->code); + const string atomConstant = make_constant_name(atomDecl->name); + fprintf(out, " if (%s == code) {\n", atomConstant.c_str()); const AnnotationSet& annotations = atomDecl->fieldNumberToAnnotations.at(argIndex); int resetState = -1; int defaultState = -1; for (const shared_ptr<Annotation>& annotation : annotations) { - // TODO(b/151786433): Write atom constant name instead of atom id literal. + const string& annotationConstant = + ANNOTATION_ID_CONSTANTS.at(annotation->annotationId); switch (annotation->type) { - // TODO(b/151776731): Check for reset state annotation and only include - // reset state when field value == default state annotation value. case ANNOTATION_TYPE_INT: - // TODO(b/151786433): Write annotation constant name instead of - // annotation id literal. if (ANNOTATION_ID_RESET_STATE == annotation->annotationId) { resetState = annotation->value.intValue; } else if (ANNOTATION_ID_DEFAULT_STATE == annotation->annotationId) { defaultState = annotation->value.intValue; } else { - fprintf(out, " builder.addIntAnnotation((byte) %d, %d);\n", - annotation->annotationId, annotation->value.intValue); + fprintf(out, " builder.addIntAnnotation(%s, %d);\n", + annotationConstant.c_str(), annotation->value.intValue); } break; case ANNOTATION_TYPE_BOOL: - // TODO(b/151786433): Write annotation constant name instead of - // annotation id literal. - fprintf(out, " builder.addBooleanAnnotation((byte) %d, %s);\n", - annotation->annotationId, + fprintf(out, " builder.addBooleanAnnotation(%s, %s);\n", + annotationConstant.c_str(), annotation->value.boolValue ? "true" : "false"); break; default: @@ -81,9 +86,11 @@ static void write_annotations(FILE* out, int argIndex, } } if (defaultState != -1 && resetState != -1) { + const string& annotationConstant = + ANNOTATION_ID_CONSTANTS.at(ANNOTATION_ID_RESET_STATE); fprintf(out, " if (arg%d == %d) {\n", argIndex, resetState); - fprintf(out, " builder.addIntAnnotation((byte) %d, %d);\n", - ANNOTATION_ID_RESET_STATE, defaultState); + fprintf(out, " builder.addIntAnnotation(%s, %d);\n", + annotationConstant.c_str(), defaultState); fprintf(out, " }\n"); } fprintf(out, " }\n"); @@ -311,6 +318,7 @@ int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl& attribut write_java_atom_codes(out, atoms); write_java_enum_values(out, atoms); + write_java_annotation_constants(out); int errors = 0; diff --git a/tools/stats_log_api_gen/native_writer.cpp b/tools/stats_log_api_gen/native_writer.cpp index c0d73fa6261f..d8db62087f8f 100644 --- a/tools/stats_log_api_gen/native_writer.cpp +++ b/tools/stats_log_api_gen/native_writer.cpp @@ -21,6 +21,16 @@ namespace android { namespace stats_log_api_gen { +static void write_native_annotation_constants(FILE* out) { + fprintf(out, "// Annotation constants.\n"); + + for (const auto& [id, name] : ANNOTATION_ID_CONSTANTS) { + fprintf(out, "const uint8_t %s = %hhu;\n", name.c_str(), id); + } + fprintf(out, "\n"); +} + + static void write_annotations(FILE* out, int argIndex, const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet, const string& methodPrefix, const string& methodSuffix) { @@ -31,33 +41,31 @@ static void write_annotations(FILE* out, int argIndex, } const AtomDeclSet& atomDeclSet = fieldNumberToAtomDeclSetIt->second; for (const shared_ptr<AtomDecl>& atomDecl : atomDeclSet) { - fprintf(out, " if (code == %d) {\n", atomDecl->code); + const string atomConstant = make_constant_name(atomDecl->name); + fprintf(out, " if (%s == code) {\n", atomConstant.c_str()); const AnnotationSet& annotations = atomDecl->fieldNumberToAnnotations.at(argIndex); int resetState = -1; int defaultState = -1; for (const shared_ptr<Annotation>& annotation : annotations) { - // TODO(b/151786433): Write atom constant name instead of atom id literal. + const string& annotationConstant = + ANNOTATION_ID_CONSTANTS.at(annotation->annotationId); switch (annotation->type) { - // TODO(b/151776731): Check for reset state annotation and only include - // reset state when field value == default state annotation value. case ANNOTATION_TYPE_INT: - // TODO(b/151786433): Write annotation constant name instead of - // annotation id literal. if (ANNOTATION_ID_RESET_STATE == annotation->annotationId) { resetState = annotation->value.intValue; } else if (ANNOTATION_ID_DEFAULT_STATE == annotation->annotationId) { defaultState = annotation->value.intValue; } else { - fprintf(out, " %saddInt32Annotation(%s%d, %d);\n", + fprintf(out, " %saddInt32Annotation(%s%s, %d);\n", methodPrefix.c_str(), methodSuffix.c_str(), - annotation->annotationId, annotation->value.intValue); + annotationConstant.c_str(), annotation->value.intValue); } break; case ANNOTATION_TYPE_BOOL: // TODO(b/151786433): Write annotation constant name instead of // annotation id literal. - fprintf(out, " %saddBoolAnnotation(%s%d, %s);\n", methodPrefix.c_str(), - methodSuffix.c_str(), annotation->annotationId, + fprintf(out, " %saddBoolAnnotation(%s%s, %s);\n", methodPrefix.c_str(), + methodSuffix.c_str(), annotationConstant.c_str(), annotation->value.boolValue ? "true" : "false"); break; default: @@ -65,9 +73,11 @@ static void write_annotations(FILE* out, int argIndex, } } if (defaultState != -1 && resetState != -1) { + const string& annotationConstant = + ANNOTATION_ID_CONSTANTS.at(ANNOTATION_ID_RESET_STATE); fprintf(out, " if (arg%d == %d) {\n", argIndex, resetState); - fprintf(out, " %saddInt32Annotation(%s%d, %d);\n", methodPrefix.c_str(), - methodSuffix.c_str(), ANNOTATION_ID_RESET_STATE, defaultState); + fprintf(out, " %saddInt32Annotation(%s%s, %d);\n", methodPrefix.c_str(), + methodSuffix.c_str(), annotationConstant.c_str(), defaultState); fprintf(out, " }\n"); } fprintf(out, " }\n"); @@ -314,6 +324,8 @@ int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl& attrib } } + write_native_annotation_constants(out); + fprintf(out, "struct BytesField {\n"); fprintf(out, " BytesField(char const* array, size_t len) : arg(array), " diff --git a/tools/stats_log_api_gen/utils.h b/tools/stats_log_api_gen/utils.h index 57b6f6254562..1f644426ffa9 100644 --- a/tools/stats_log_api_gen/utils.h +++ b/tools/stats_log_api_gen/utils.h @@ -38,6 +38,14 @@ const int JAVA_MODULE_REQUIRES_FLOAT = 0x01; const int JAVA_MODULE_REQUIRES_ATTRIBUTION = 0x02; const int JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS = 0x04; +const map<unsigned char, string> ANNOTATION_ID_CONSTANTS = { + { ANNOTATION_ID_IS_UID, "ANNOTATION_ID_IS_UID" }, + { ANNOTATION_ID_TRUNCATE_TIMESTAMP, "ANNOTATION_ID_TRUNCATE_TIMESTAMP" }, + { ANNOTATION_ID_STATE_OPTION, "ANNOTATION_ID_STATE_OPTION" }, + { ANNOTATION_ID_RESET_STATE, "ANNOTATION_ID_RESET_STATE" }, + { ANNOTATION_ID_STATE_NESTED, "ANNOTATION_ID_STATE_NESTED" } +}; + string make_constant_name(const string& str); const char* cpp_type_name(java_type_t type); |