diff options
237 files changed, 5439 insertions, 3760 deletions
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/api/current.txt b/api/current.txt index 1c1d1427a4db..b4db1f7bc10a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6833,13 +6833,6 @@ package android.app.admin { method public final android.os.IBinder onBind(android.content.Intent); } - public class DevicePolicyKeyguardService extends android.app.Service { - ctor public DevicePolicyKeyguardService(); - method @Nullable public void dismiss(); - method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage onCreateKeyguardSurface(@NonNull android.os.IBinder); - } - public class DevicePolicyManager { method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int); method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String); @@ -7055,7 +7048,6 @@ package android.app.admin { method public boolean setResetPasswordToken(android.content.ComponentName, byte[]); method public void setRestrictionsProvider(@NonNull android.content.ComponentName, @Nullable android.content.ComponentName); method public void setScreenCaptureDisabled(@NonNull android.content.ComponentName, boolean); - method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); method public void setSecureSetting(@NonNull android.content.ComponentName, String, String); method public void setSecurityLoggingEnabled(@NonNull android.content.ComponentName, boolean); method public void setShortSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence); @@ -7082,7 +7074,6 @@ package android.app.admin { field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN"; field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE"; field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED"; - field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE"; field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE"; field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE"; field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED"; @@ -30284,6 +30275,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/api/system-current.txt b/api/system-current.txt index c664bed47c1e..c5d319cc1d00 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -848,6 +848,13 @@ package android.app { package android.app.admin { + public class DevicePolicyKeyguardService extends android.app.Service { + ctor public DevicePolicyKeyguardService(); + method @Nullable public void dismiss(); + method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); + method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage onCreateKeyguardSurface(@NonNull android.os.IBinder); + } + public class DevicePolicyManager { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner(); @@ -872,8 +879,10 @@ package android.app.admin { method @Deprecated @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); + method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED"; field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED"; + field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE"; field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION"; field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE"; field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; diff --git a/cmds/hid/README.md b/cmds/hid/README.md index 7e22d08eeaeb..620336f30327 100644 --- a/cmds/hid/README.md +++ b/cmds/hid/README.md @@ -38,17 +38,21 @@ legal JSON format, as this would imply multiple root elements). Register a new uhid device | Field | Type | Description | -|:-------------:|:-------------:|:--------------------------| +|:-------------:|:-------------:|:-------------------------- | | id | integer | Device id | | command | string | Must be set to "register" | | name | string | Device name | | vid | 16-bit integer| Vendor id | | pid | 16-bit integer| Product id | +| bus | string | Bus that device should use | | descriptor | byte array | USB HID report descriptor | Device ID is used for matching the subsequent commands to a specific device to avoid ambiguity when multiple devices are registered. +Device bus is used to determine how the uhid device is connected to the host. +The options are "usb" and "bluetooth". + USB HID report descriptor should be generated according the the USB HID spec and can be checked by reverse parsing using a variety of tools, for example [usbdescreqparser][5]. @@ -61,6 +65,7 @@ Example: "name": "Odie (Test)", "vid": 0x18d1, "pid": 0x2c40, + "bus": "usb", "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00, 0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a, @@ -142,4 +147,4 @@ for debugging purposes. [3]: ../../../../cts/tests/tests/hardware/res/raw/ [4]: https://developer.android.com/training/game-controllers/controller-input.html#button [5]: http://eleccelerator.com/usbdescreqparser/ -[6]: https://developer.android.com/training/game-controllers/controller-input.html
\ No newline at end of file +[6]: https://developer.android.com/training/game-controllers/controller-input.html diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 33ffba1b117a..d3d7e1d483e8 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -335,6 +335,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/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/atoms.proto b/cmds/statsd/src/atoms.proto index 57d4d78cc06d..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"]; diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index a3701a77f27a..79a7e8d318e2 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -41,15 +41,50 @@ 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; StatsPullerManager::StatsPullerManager() : kAllPullAtomInfo({ // TrainInfo. - {{.atomTag = util::TRAIN_INFO, .uid = -1}, new TrainInfoPuller()}, + {{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, 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..ab0cceeb112e 100644 --- a/cmds/statsd/src/external/StatsPullerManager.h +++ b/cmds/statsd/src/external/StatsPullerManager.h @@ -101,11 +101,11 @@ public: // If the metric wants to make any change to the data, like timestamps, they // should make a copy as this data may be shared with multiple metrics. virtual bool Pull(int tagId, const ConfigKey& configKey, - vector<std::shared_ptr<LogEvent>>* data, bool useUids = false); + vector<std::shared_ptr<LogEvent>>* data, bool useUids = true); // Same as above, but directly specify the allowed uids to pull from. virtual bool Pull(int tagId, const vector<int32_t>& uids, - vector<std::shared_ptr<LogEvent>>* data, bool useUids = false); + vector<std::shared_ptr<LogEvent>>* data, bool useUids = true); // Clear pull data cache immediately. int ForceClearPullerCache(); @@ -118,9 +118,9 @@ public: void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs, const int64_t timeoutNs, const vector<int32_t>& additiveFields, const shared_ptr<IPullAtomCallback>& callback, - bool useUid = false); + bool useUid = true); - void UnregisterPullAtomCallback(const int uid, const int32_t atomTag); + void UnregisterPullAtomCallback(const int uid, const int32_t atomTag, bool useUids = true); std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo; @@ -152,7 +152,7 @@ private: std::map<ConfigKey, wp<PullUidProvider>> mPullUidProviders; bool PullLocked(int tagId, const ConfigKey& configKey, vector<std::shared_ptr<LogEvent>>* data, - bool useUids = false); + bool useUids = true); bool PullLocked(int tagId, const vector<int32_t>& uids, vector<std::shared_ptr<LogEvent>>* data, bool useUids); @@ -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/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 c4d1c512d0a9..2fcb13b709f9 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -512,6 +512,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, @@ -523,7 +550,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/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/Service.java b/core/java/android/app/Service.java index dc8269f900b7..b96b54ad8d21 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -34,6 +34,7 @@ import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import android.view.contentcapture.ContentCaptureManager; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -306,7 +307,8 @@ import java.lang.annotation.RetentionPolicy; * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.java * bind} */ -public abstract class Service extends ContextWrapper implements ComponentCallbacks2 { +public abstract class Service extends ContextWrapper implements ComponentCallbacks2, + ContentCaptureManager.ContentCaptureClient { private static final String TAG = "Service"; /** @@ -817,6 +819,14 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac writer.println("nothing to dump"); } + @Override + protected void attachBaseContext(Context newBase) { + super.attachBaseContext(newBase); + if (newBase != null) { + newBase.setContentCaptureOptions(getContentCaptureOptions()); + } + } + // ------------------ Internal API ------------------ /** @@ -835,6 +845,8 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac mActivityManager = (IActivityManager)activityManager; mStartCompatibility = getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.ECLAIR; + + setContentCaptureOptions(application.getContentCaptureOptions()); } /** @@ -849,6 +861,18 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac return mClassName; } + /** @hide */ + @Override + public final ContentCaptureManager.ContentCaptureClient getContentCaptureClient() { + return this; + } + + /** @hide */ + @Override + public final ComponentName contentCaptureClientGetComponentName() { + return new ComponentName(this, mClassName); + } + // set by the thread after the constructor and before onCreate(Bundle icicle) is called. @UnsupportedAppUsage private ActivityThread mThread = null; 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/app/admin/DevicePolicyKeyguardService.java b/core/java/android/app/admin/DevicePolicyKeyguardService.java index 5b7e387e6538..db833ec478bd 100644 --- a/core/java/android/app/admin/DevicePolicyKeyguardService.java +++ b/core/java/android/app/admin/DevicePolicyKeyguardService.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; import android.os.IBinder; @@ -28,14 +29,16 @@ import android.view.SurfaceControlViewHost; /** * Client interface for providing the SystemUI with secondary lockscreen information. * - * <p>An implementation must be provided by the Profile Owner when - * {@link DevicePolicyManager#setSecondaryLockscreenEnabled} is set to true and the service must be - * declared in the manifest as handling the action + * <p>An implementation must be provided by the default configured supervision app that is set as + * Profile Owner or Device Owner when {@link DevicePolicyManager#setSecondaryLockscreenEnabled} is + * set to true and the service must be declared in the manifest as handling the action * {@link DevicePolicyManager#ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE}, otherwise the keyguard * will fail to bind to the service and continue to unlock. * * @see DevicePolicyManager#setSecondaryLockscreenEnabled + * @hide */ +@SystemApi public class DevicePolicyKeyguardService extends Service { private static final String TAG = "DevicePolicyKeyguardService"; private IKeyguardCallback mCallback; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 10309a9b4a03..faf9ec61ffde 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2395,9 +2395,11 @@ public class DevicePolicyManager { public static final int MAX_PASSWORD_LENGTH = 16; /** - * Service Action: Service implemented by a device owner or profile owner to provide a - * secondary lockscreen. + * Service Action: Service implemented by a device owner or profile owner supervision app to + * provide a secondary lockscreen. + * @hide */ + @SystemApi public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE"; @@ -7001,6 +7003,22 @@ public class DevicePolicyManager { } /** + * Returns the configured supervision app if it exists and is the device owner or policy owner. + * @hide + */ + public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent( + @NonNull UserHandle user) { + if (mService != null) { + try { + return mService.getProfileOwnerOrDeviceOwnerSupervisionComponent(user); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return null; + } + + /** * @hide * @return the human readable name of the organisation associated with this DPM or {@code null} * if one is not set. @@ -8637,11 +8655,16 @@ public class DevicePolicyManager { * <p>Relevant interactions on the secondary lockscreen should be communicated back to the * keyguard via {@link IKeyguardCallback}, such as when the screen is ready to be dismissed. * + * <p>This API, and associated APIs, can only be called by the default supervision app when it + * is set as the device owner or profile owner. + * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param enabled Whether or not the lockscreen needs to be shown. * @throws SecurityException if {@code admin} is not a device or profile owner. * @see #isSecondaryLockscreenEnabled + * @hide **/ + @SystemApi public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) { throwIfParentInstance("setSecondaryLockscreenEnabled"); if (mService != null) { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index fc1eb0a7b9c1..591a3f68eed0 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -155,6 +155,7 @@ interface IDevicePolicyManager { boolean setProfileOwner(in ComponentName who, String ownerName, int userHandle); ComponentName getProfileOwnerAsUser(int userHandle); ComponentName getProfileOwner(int userHandle); + ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(in UserHandle userHandle); String getProfileOwnerName(int userHandle); void setProfileEnabled(in ComponentName who); void setProfileName(in ComponentName who, String profileName); 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/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING index daf9a1491cf9..9ebc9969a730 100644 --- a/core/java/android/content/res/TEST_MAPPING +++ b/core/java/android/content/res/TEST_MAPPING @@ -1,7 +1,7 @@ { "presubmit": [ { - "name": "FrameworksResourceLoaderTests" + "name": "CtsResourcesLoaderTests" } ] } 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/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/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/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 6784cf7407fa..dbbe4b61c81c 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -170,10 +170,15 @@ public final class ImeFocusController { } if (DEBUG) Log.d(TAG, "onViewFocusChanged, view=" + view + ", mServedView=" + mServedView); + // We don't need to track the next served view when the view lost focus here because: + // 1) The current view focus may be cleared temporary when in touch mode, closing input + // at this moment isn't the right way. + // 2) We only care about the served view change when it focused, since changing input + // connection when the focus target changed is reasonable. + // 3) Setting the next served view as null when no more served view should be handled in + // other special events (e.g. view detached from window or the window dismissed). if (hasFocus) { mNextServedView = view; - } else if (view == mServedView) { - mNextServedView = null; } mViewRootImpl.dispatchCheckFocus(); } 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/Toast.java b/core/java/android/widget/Toast.java index 4f14539dd976..08b32930971a 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -117,7 +117,6 @@ public class Toast { private final Binder mToken; private final Context mContext; private final Handler mHandler; - private final ToastPresenter mPresenter; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) final TN mTN; @UnsupportedAppUsage @@ -165,8 +164,8 @@ public class Toast { looper = getLooper(looper); mHandler = new Handler(looper); mCallbacks = new ArrayList<>(); - mPresenter = new ToastPresenter(context, AccessibilityManager.getInstance(context)); - mTN = new TN(mPresenter, context.getPackageName(), mToken, mCallbacks, looper); + mTN = new TN(context, context.getPackageName(), mToken, + mCallbacks, looper); mTN.mY = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.toast_y_offset); mTN.mGravity = context.getResources().getInteger( @@ -496,7 +495,7 @@ public class Toast { return result; } else { Toast result = new Toast(context, looper); - View v = result.mPresenter.getTextToastView(text); + View v = ToastPresenter.getTextToastView(context, text); result.mNextView = v; result.mDuration = duration; @@ -565,13 +564,14 @@ public class Toast { if (sService != null) { return sService; } - sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); + sService = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); return sService; } private static class TN extends ITransientNotification.Stub { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) - private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); + private final WindowManager.LayoutParams mParams; private static final int SHOW = 0; private static final int HIDE = 1; @@ -608,9 +608,13 @@ public class Toast { * The parameter {@code callbacks} is not copied and is accessed with itself as its own * lock. */ - TN(ToastPresenter presenter, String packageName, Binder token, List<Callback> callbacks, + TN(Context context, String packageName, Binder token, List<Callback> callbacks, @Nullable Looper looper) { - mPresenter = presenter; + WindowManager windowManager = context.getSystemService(WindowManager.class); + AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context); + mPresenter = new ToastPresenter(context, windowManager, accessibilityManager, + getService(), packageName); + mParams = mPresenter.getLayoutParams(); mPackageName = packageName; mToken = token; mCallbacks = callbacks; @@ -645,8 +649,6 @@ public class Toast { } } }; - - presenter.startLayoutParams(mParams, packageName); } private List<Callback> getCallbacks() { @@ -691,31 +693,9 @@ public class Toast { // remove the old view if necessary handleHide(); mView = mNextView; - Context context = mView.getContext().getApplicationContext(); - if (context == null) { - context = mView.getContext(); - } - mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - mPresenter.adjustLayoutParams(mParams, windowToken, mDuration, mGravity, mX, mY, - mHorizontalMargin, mVerticalMargin); - if (mView.getParent() != null) { - if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); - mWM.removeView(mView); - } - if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this); - // Since the notification manager service cancels the token right - // after it notifies us to cancel the toast there is an inherent - // race and we may attempt to add a window after the token has been - // invalidated. Let us hedge against that. - try { - mWM.addView(mView, mParams); - mPresenter.trySendAccessibilityEvent(mView, mPackageName); - for (Callback callback : getCallbacks()) { - callback.onToastShown(); - } - } catch (WindowManager.BadTokenException e) { - /* ignore */ - } + mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY, + mHorizontalMargin, mVerticalMargin, + new CallbackBinder(getCallbacks(), mHandler)); } } @@ -723,25 +703,9 @@ public class Toast { public void handleHide() { if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); if (mView != null) { - // note: checking parent() just to make sure the view has - // been added... i have seen cases where we get here when - // the view isn't yet added, so let's try not to crash. - if (mView.getParent() != null) { - if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); - mWM.removeViewImmediate(mView); - } - - - // Now that we've removed the view it's safe for the server to release - // the resources. - try { - getService().finishToken(mPackageName, mToken); - } catch (RemoteException e) { - } - - for (Callback callback : getCallbacks()) { - callback.onToastHidden(); - } + checkState(mView == mPresenter.getView(), + "Trying to hide toast view different than the last one displayed"); + mPresenter.hide(new CallbackBinder(getCallbacks(), mHandler)); mView = null; } } diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java index 0447b6bb9f11..e9d4aa668891 100644 --- a/core/java/android/widget/ToastPresenter.java +++ b/core/java/android/widget/ToastPresenter.java @@ -16,11 +16,18 @@ package android.widget; +import static com.android.internal.util.Preconditions.checkState; + +import android.annotation.Nullable; +import android.app.INotificationManager; +import android.app.ITransientNotificationCallback; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PixelFormat; import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -37,41 +44,94 @@ import com.android.internal.util.ArrayUtils; * @hide */ public class ToastPresenter { + private static final String TAG = "ToastPresenter"; + private static final String WINDOW_TITLE = "Toast"; private static final long SHORT_DURATION_TIMEOUT = 4000; private static final long LONG_DURATION_TIMEOUT = 7000; + /** + * Returns the default text toast view for message {@code text}. + */ + public static View getTextToastView(Context context, CharSequence text) { + View view = LayoutInflater.from(context).inflate( + R.layout.transient_notification, null); + TextView textView = view.findViewById(com.android.internal.R.id.message); + textView.setText(text); + return view; + } + private final Context mContext; private final Resources mResources; + private final WindowManager mWindowManager; private final AccessibilityManager mAccessibilityManager; + private final INotificationManager mNotificationManager; + private final String mPackageName; + private final WindowManager.LayoutParams mParams; + @Nullable private View mView; + @Nullable private IBinder mToken; - public ToastPresenter(Context context, AccessibilityManager accessibilityManager) { + public ToastPresenter(Context context, WindowManager windowManager, + AccessibilityManager accessibilityManager, + INotificationManager notificationManager, String packageName) { mContext = context; mResources = context.getResources(); + mWindowManager = windowManager; mAccessibilityManager = accessibilityManager; + mNotificationManager = notificationManager; + mPackageName = packageName; + mParams = createLayoutParams(); + } + + public String getPackageName() { + return mPackageName; + } + + public WindowManager.LayoutParams getLayoutParams() { + return mParams; + } + + /** + * Returns the {@link View} being shown at the moment or {@code null} if no toast is being + * displayed. + */ + @Nullable + public View getView() { + return mView; } /** - * Initializes {@code params} with default values for toasts. + * Returns the {@link IBinder} token used to display the toast or {@code null} if there is no + * toast being shown at the moment. */ - public void startLayoutParams(WindowManager.LayoutParams params, String packageName) { + @Nullable + public IBinder getToken() { + return mToken; + } + + /** + * Creates {@link WindowManager.LayoutParams} with default values for toasts. + */ + private WindowManager.LayoutParams createLayoutParams() { + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.format = PixelFormat.TRANSLUCENT; params.windowAnimations = R.style.Animation_Toast; params.type = WindowManager.LayoutParams.TYPE_TOAST; params.setFitInsetsIgnoringVisibility(true); - params.setTitle("Toast"); + params.setTitle(WINDOW_TITLE); params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; - setShowForAllUsersIfApplicable(params, packageName); + setShowForAllUsersIfApplicable(params, mPackageName); + return params; } /** * Customizes {@code params} according to other parameters, ready to be passed to {@link * WindowManager#addView(View, ViewGroup.LayoutParams)}. */ - public void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken, + private void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken, int duration, int gravity, int xOffset, int yOffset, float horizontalMargin, float verticalMargin) { Configuration config = mResources.getConfiguration(); @@ -97,7 +157,7 @@ public class ToastPresenter { * Sets {@link WindowManager.LayoutParams#SYSTEM_FLAG_SHOW_FOR_ALL_USERS} flag if {@code * packageName} is a cross-user package. * - * Implementation note: + * <p>Implementation note: * This code is safe to be executed in SystemUI and the app's process: * <li>SystemUI: It's running on a trusted domain so apps can't tamper with it. SystemUI * has the permission INTERNAL_SYSTEM_WINDOW needed by the flag, so SystemUI can add @@ -120,14 +180,66 @@ public class ToastPresenter { } /** - * Returns the default text toast view for message {@code text}. + * Shows the toast in {@code view} with the parameters passed and callback {@code callback}. */ - public View getTextToastView(CharSequence text) { - View view = LayoutInflater.from(mContext).inflate( - R.layout.transient_notification, null); - TextView textView = view.findViewById(com.android.internal.R.id.message); - textView.setText(text); - return view; + public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity, + int xOffset, int yOffset, float horizontalMargin, float verticalMargin, + @Nullable ITransientNotificationCallback callback) { + checkState(mView == null, "Only one toast at a time is allowed, call hide() first."); + mView = view; + mToken = token; + + adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset, + horizontalMargin, verticalMargin); + if (mView.getParent() != null) { + mWindowManager.removeView(mView); + } + try { + mWindowManager.addView(mView, mParams); + } catch (WindowManager.BadTokenException e) { + // Since the notification manager service cancels the token right after it notifies us + // to cancel the toast there is an inherent race and we may attempt to add a window + // after the token has been invalidated. Let us hedge against that. + Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e); + return; + } + trySendAccessibilityEvent(mView, mPackageName); + if (callback != null) { + try { + callback.onToastShown(); + } catch (RemoteException e) { + Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e); + } + } + } + + /** + * Hides toast that was shown using {@link #show(View, IBinder, IBinder, int, + * int, int, int, float, float, ITransientNotificationCallback)}. + * + * <p>This method has to be called on the same thread on which {@link #show(View, IBinder, + * IBinder, int, int, int, int, float, float, ITransientNotificationCallback)} was called. + */ + public void hide(@Nullable ITransientNotificationCallback callback) { + checkState(mView != null, "No toast to hide."); + + if (mView.getParent() != null) { + mWindowManager.removeViewImmediate(mView); + } + try { + mNotificationManager.finishToken(mPackageName, mToken); + } catch (RemoteException e) { + Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e); + } + if (callback != null) { + try { + callback.onToastHidden(); + } catch (RemoteException e) { + Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e); + } + } + mView = null; + mToken = null; } /** diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index dca682e4ee29..c82ab6c79e9d 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 = { @@ -226,7 +235,7 @@ public class ChooserActivity extends ResolverActivity implements private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED, - false); + true); private Bundle mReplacementExtras; private IntentSender mChosenComponentSender; @@ -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/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index 0ea855a6b7a9..0d90bbff4c73 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -64,7 +64,7 @@ public class ChooserListAdapter extends ResolverListAdapter { private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED, - false); + true); private boolean mEnableStackedApps = true; 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/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java index 2779be6f9753..575a5320bbd3 100644 --- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java @@ -69,6 +69,7 @@ public class DividerSnapAlgorithm { private final ArrayList<SnapTarget> mTargets = new ArrayList<>(); private final Rect mInsets = new Rect(); private final int mSnapMode; + private final boolean mFreeSnapMode; private final int mMinimalSizeResizableTask; private final int mTaskHeightInMinimizedMode; private final float mFixedRatio; @@ -125,6 +126,8 @@ public class DividerSnapAlgorithm { mInsets.set(insets); mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED : res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode); + mFreeSnapMode = res.getBoolean( + com.android.internal.R.bool.config_dockedStackDividerFreeSnapMode); mFixedRatio = res.getFraction( com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1); mMinimalSizeResizableTask = res.getDimensionPixelSize( @@ -247,7 +250,20 @@ public class DividerSnapAlgorithm { } } + private boolean shouldApplyFreeSnapMode(int position) { + if (!mFreeSnapMode) { + return false; + } + if (!isFirstSplitTargetAvailable() || !isLastSplitTargetAvailable()) { + return false; + } + return mFirstSplitTarget.position < position && position < mLastSplitTarget.position; + } + private SnapTarget snap(int position, boolean hardDismiss) { + if (shouldApplyFreeSnapMode(position)) { + return new SnapTarget(position, position, SnapTarget.FLAG_NONE); + } int minIndex = -1; float minDistance = Float.MAX_VALUE; int size = mTargets.size(); 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 ab68c440483e..523c7493420b 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -331,7 +331,8 @@ public class ConversationLayout extends FrameLayout @RemotableViewMethod public void setIsImportantConversation(boolean isImportantConversation) { mImportantConversation = isImportantConversation; - mImportanceRingView.setVisibility(isImportantConversation ? VISIBLE : GONE); + mImportanceRingView.setVisibility(isImportantConversation + && mIcon.getVisibility() != GONE ? VISIBLE : GONE); } public boolean isImportantConversation() { diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b1bba53bd7ab..340dd4d7d89a 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3346,6 +3346,10 @@ This should only be set when the device has gestural navigation enabled by default. --> <bool name="config_showGesturalNavigationHints">false</bool> + <!-- Controls the free snap mode for the docked stack divider. In this mode, the divider can be + snapped to any position between the first target and the last target. --> + <bool name="config_dockedStackDividerFreeSnapMode">false</bool> + <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows. These values are in DPs and will be converted to pixel sizes internally. --> <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ec8058235912..11dda41d0b57 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1667,6 +1667,7 @@ <java-symbol type="bool" name="config_perDisplayFocusEnabled" /> <java-symbol type="bool" name="config_showNavigationBar" /> <java-symbol type="bool" name="config_supportAutoRotation" /> + <java-symbol type="bool" name="config_dockedStackDividerFreeSnapMode" /> <java-symbol type="dimen" name="docked_stack_divider_thickness" /> <java-symbol type="dimen" name="docked_stack_divider_insets" /> <java-symbol type="dimen" name="docked_stack_minimize_thickness" /> diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp deleted file mode 100644 index 2b14bca1f7c1..000000000000 --- a/core/tests/ResourceLoaderTests/Android.bp +++ /dev/null @@ -1,64 +0,0 @@ -// -// 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. -// - -android_test { - name: "FrameworksResourceLoaderTests", - srcs: [ - "src/**/*.kt" - ], - libs: [ - "android.test.runner", - "android.test.base" - ], - static_libs: [ - "FrameworksResourceLoaderTests_Providers", - "androidx.test.espresso.core", - "androidx.test.ext.junit", - "androidx.test.runner", - "androidx.test.rules", - "mockito-target-minus-junit4", - "truth-prebuilt" - ], - resource_dirs: ["res", "resources/provider_stable/res"], - platform_apis: true, - test_suites: ["device-tests"], - aaptflags: ["-0 .txt"], - data: [ - ":FrameworksResourceLoaderTests_ProviderOne_Split", - ":FrameworksResourceLoaderTests_ProviderTwo_Split", - ":FrameworksResourceLoaderTests_ProviderThree_Split", - ":FrameworksResourceLoaderTests_ProviderFour_Split" - ] -} - -java_genrule { - name: "FrameworksResourceLoaderTests_Providers", - tools: ["soong_zip"], - srcs : [ - ":FrameworksResourceLoaderTests_ProviderOne", - ":FrameworksResourceLoaderTests_ProviderOne_ARSC", - ":FrameworksResourceLoaderTests_ProviderTwo", - ":FrameworksResourceLoaderTests_ProviderTwo_ARSC", - ":FrameworksResourceLoaderTests_ProviderThree", - ":FrameworksResourceLoaderTests_ProviderThree_ARSC", - ":FrameworksResourceLoaderTests_ProviderFour", - ":FrameworksResourceLoaderTests_ProviderFour_ARSC" - ], - out: ["FrameworksResourceLoaderTests_Providers.jar"], - cmd: "mkdir -p $(genDir)/assets/ && cp $(in) $(genDir)/assets/ && " + - "$(location soong_zip) -o $(out) " + - "-L 0 -C $(genDir) -D $(genDir)/assets/" -}
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/AndroidManifest.xml b/core/tests/ResourceLoaderTests/AndroidManifest.xml deleted file mode 100644 index 00b4ccbd8030..000000000000 --- a/core/tests/ResourceLoaderTests/AndroidManifest.xml +++ /dev/null @@ -1,42 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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 - --> - -<!-- Split loading is tested separately, so this must be marked isolated --> -<manifest - xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.res.loader.test" - android:isolatedSplits="true" - > - - <uses-sdk android:minSdkVersion="29"/> - - <application> - <uses-library android:name="android.test.runner"/> - - <activity - android:name=".TestActivity" - android:configChanges="orientation" - /> - </application> - - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:label="ResourceLoaderTests" - android:targetPackage="android.content.res.loader.test" - /> - -</manifest> diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml deleted file mode 100644 index 800e7a7124ac..000000000000 --- a/core/tests/ResourceLoaderTests/AndroidTest.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<configuration description="Test module config for ResourceLoaderTests"> - <option name="test-tag" value="ResourceLoaderTests" /> - - <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> - <option name="cleanup-apks" value="true" /> - <!-- The following value cannot be multi-line as whitespace is parsed by the installer --> - <option name="split-apk-file-names" - value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTests_ProviderOne_Split.apk,FrameworksResourceLoaderTests_ProviderTwo_Split.apk,FrameworksResourceLoaderTests_ProviderThree_Split.apk,FrameworksResourceLoaderTests_ProviderFour_Split.apk" /> - </target_preparer> - - <test class="com.android.tradefed.testtype.AndroidJUnitTest"> - <option name="package" value="android.content.res.loader.test" /> - </test> -</configuration> diff --git a/core/tests/ResourceLoaderTests/assets/asset.txt b/core/tests/ResourceLoaderTests/assets/asset.txt deleted file mode 100644 index 271704bdc1b5..000000000000 --- a/core/tests/ResourceLoaderTests/assets/asset.txt +++ /dev/null @@ -1 +0,0 @@ -In assets directory
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/assets/base_asset.txt b/core/tests/ResourceLoaderTests/assets/base_asset.txt deleted file mode 100644 index 8e62cc346238..000000000000 --- a/core/tests/ResourceLoaderTests/assets/base_asset.txt +++ /dev/null @@ -1 +0,0 @@ -Base
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar Binary files differdeleted file mode 100644 index a12e33a34aee..000000000000 --- a/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar Binary files differdeleted file mode 100644 index 182cbabadfe6..000000000000 --- a/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar Binary files differdeleted file mode 100644 index e6b5f15b8a57..000000000000 --- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar Binary files differdeleted file mode 100644 index e9c743c60289..000000000000 --- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar Binary files differdeleted file mode 100644 index cd0536042662..000000000000 --- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar Binary files differdeleted file mode 100644 index dc8aa90385fd..000000000000 --- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar Binary files differdeleted file mode 100644 index 8a672bac4685..000000000000 --- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar Binary files differdeleted file mode 100644 index 56f3d1e385e4..000000000000 --- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar Binary files differdeleted file mode 100644 index 663d3128dd54..000000000000 --- a/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar Binary files differdeleted file mode 100644 index 5f6e4b8cc988..000000000000 --- a/core/tests/ResourceLoaderTests/lib/kotlin-test.jar +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_png.png Binary files differdeleted file mode 100644 index 8102d1539d53..000000000000 --- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_png.png +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_xml.xml deleted file mode 100644 index d1211c50a203..000000000000 --- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_xml.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<color - xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#B2D2F2" - /> diff --git a/core/tests/ResourceLoaderTests/res/layout/layout.xml b/core/tests/ResourceLoaderTests/res/layout/layout.xml deleted file mode 100644 index 05499ed35e50..000000000000 --- a/core/tests/ResourceLoaderTests/res/layout/layout.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<MysteryLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - diff --git a/core/tests/ResourceLoaderTests/res/values/values.xml b/core/tests/ResourceLoaderTests/res/values/values.xml deleted file mode 100644 index ad785322fcc9..000000000000 --- a/core/tests/ResourceLoaderTests/res/values/values.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<resources> - <dimen name="test">0dp</dimen> - <string name="test">Not overlaid</string> -</resources> diff --git a/core/tests/ResourceLoaderTests/resources/Android.bp b/core/tests/ResourceLoaderTests/resources/Android.bp deleted file mode 100644 index 18ef64b70927..000000000000 --- a/core/tests/ResourceLoaderTests/resources/Android.bp +++ /dev/null @@ -1,115 +0,0 @@ -// -// 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. -// - -android_test_helper_app { - name: "FrameworksResourceLoaderTests_ProviderOne", - manifest: "AndroidManifestApp.xml", - asset_dirs: ["provider1/assets"], - resource_dirs: ["provider1/res", "provider_stable/res"], - aaptflags: ["-0 .txt"] -} - -android_test_helper_app { - name: "FrameworksResourceLoaderTests_ProviderTwo", - manifest: "AndroidManifestApp.xml", - asset_dirs: ["provider2/assets"], - resource_dirs: ["provider2/res", "provider_stable/res"], - aaptflags: ["-0 .txt"] -} - -android_test_helper_app { - name: "FrameworksResourceLoaderTests_ProviderThree", - manifest: "AndroidManifestApp.xml", - asset_dirs: ["provider3/assets"], - resource_dirs: ["provider3/res", "provider_stable/res"], - aaptflags: ["-0 .txt"] -} - -android_test_helper_app { - name: "FrameworksResourceLoaderTests_ProviderFour", - manifest: "AndroidManifestApp.xml", - asset_dirs: ["provider4/assets"], - resource_dirs: ["provider4/res", "provider_stable/res"], - aaptflags: ["-0 .txt"] -} - -// Resources.arsc(s) - -genrule { - name: "FrameworksResourceLoaderTests_ProviderOne_ARSC", - srcs: [":FrameworksResourceLoaderTests_ProviderOne"], - cmd: "unzip $(in) resources.arsc -d $(genDir) && " - + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderOne.arsc", - out: ["FrameworksResourceLoaderTests_ProviderOne.arsc"] -} - -genrule { - name: "FrameworksResourceLoaderTests_ProviderTwo_ARSC", - srcs: [":FrameworksResourceLoaderTests_ProviderTwo"], - cmd: "unzip $(in) resources.arsc -d $(genDir) && " - + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderTwo.arsc", - out: ["FrameworksResourceLoaderTests_ProviderTwo.arsc"] -} - -genrule { - name: "FrameworksResourceLoaderTests_ProviderThree_ARSC", - srcs: [":FrameworksResourceLoaderTests_ProviderThree"], - cmd: "unzip $(in) resources.arsc -d $(genDir) && " - + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderThree.arsc", - out: ["FrameworksResourceLoaderTests_ProviderThree.arsc"] -} - -genrule { - name: "FrameworksResourceLoaderTests_ProviderFour_ARSC", - srcs: [":FrameworksResourceLoaderTests_ProviderFour"], - cmd: "unzip $(in) resources.arsc -d $(genDir) && " - + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderFour.arsc", - out: ["FrameworksResourceLoaderTests_ProviderFour.arsc"] -} - -// Split APKs - -android_test_helper_app { - name: "FrameworksResourceLoaderTests_ProviderOne_Split", - manifest: "AndroidManifestSplit1.xml", - asset_dirs: ["provider1/assets"], - resource_dirs: ["provider1/res", "provider_stable/res"], - aaptflags: ["-0 .txt"] -} - -android_test_helper_app { - name: "FrameworksResourceLoaderTests_ProviderTwo_Split", - manifest: "AndroidManifestSplit2.xml", - asset_dirs: ["provider2/assets"], - resource_dirs: ["provider2/res", "provider_stable/res"], - aaptflags: ["-0 .txt"] -} - -android_test_helper_app { - name: "FrameworksResourceLoaderTests_ProviderThree_Split", - manifest: "AndroidManifestSplit3.xml", - asset_dirs: ["provider3/assets"], - resource_dirs: ["provider3/res", "provider_stable/res"], - aaptflags: ["-0 .txt"] -} - -android_test_helper_app { - name: "FrameworksResourceLoaderTests_ProviderFour_Split", - manifest: "AndroidManifestSplit4.xml", - asset_dirs: ["provider4/assets"], - resource_dirs: ["provider4/res", "provider_stable/res"], - aaptflags: ["-0 .txt"] -}
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml deleted file mode 100644 index c8a3590aaa62..000000000000 --- a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?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="android.content.res.loader.test"> - - <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" /> - <application/> -</manifest> diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml deleted file mode 100644 index d5fa83f59546..000000000000 --- a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?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. - --> - -<!-- Mocks the framework package name so that AAPT2 assigns the correct package --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android"> - - <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" /> - <application/> -</manifest> diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit1.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit1.xml deleted file mode 100644 index 5cd4227286a2..000000000000 --- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit1.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<manifest - xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.res.loader.test" - split="FrameworksResourceLoaderTests_ProviderOne_Split" - android:isFeatureSplit="true" - > - - <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" /> - <application android:hasCode="false" /> - -</manifest> diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit2.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit2.xml deleted file mode 100644 index b5180e66b3a1..000000000000 --- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit2.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?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="android.content.res.loader.test" - split="FrameworksResourceLoaderTests_ProviderTwo_Split" - android:isFeatureSplit="true" - > - - <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" /> - <application android:hasCode="false" /> - -</manifest> diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit3.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit3.xml deleted file mode 100644 index 8ddb89280d60..000000000000 --- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit3.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?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="android.content.res.loader.test" - split="FrameworksResourceLoaderTests_ProviderThree_Split" - android:isFeatureSplit="true" - > - - <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" /> - <application android:hasCode="false" /> - -</manifest> diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit4.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit4.xml deleted file mode 100644 index b6bf552c9892..000000000000 --- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit4.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?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="android.content.res.loader.test" - split="FrameworksResourceLoaderTests_ProviderFour_Split" - android:isFeatureSplit="true" - > - - <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" /> - <application android:hasCode="false" /> - -</manifest> diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/drawable-mdpi/ic_delete.png b/core/tests/ResourceLoaderTests/resources/framework/res/drawable-mdpi/ic_delete.png Binary files differdeleted file mode 100644 index f3e53d7596c1..000000000000 --- a/core/tests/ResourceLoaderTests/resources/framework/res/drawable-mdpi/ic_delete.png +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/layout/activity_list_item.xml b/core/tests/ResourceLoaderTests/resources/framework/res/layout/activity_list_item.xml deleted file mode 100644 index d59059b453d6..000000000000 --- a/core/tests/ResourceLoaderTests/resources/framework/res/layout/activity_list_item.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/values/public.xml b/core/tests/ResourceLoaderTests/resources/framework/res/values/public.xml deleted file mode 100644 index 2e501826e00a..000000000000 --- a/core/tests/ResourceLoaderTests/resources/framework/res/values/public.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?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 type="drawable" name="ic_delete" id="0x0108001d" /> - <public type="layout" name="activity_list_item" id="0x01090000" /> - <public type="string" name="cancel" id="0x01040000" /> -</resources>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml deleted file mode 100644 index 5f6e90cf9e0d..000000000000 --- a/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?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> - <string name="cancel">SomeRidiculouslyUnlikelyString</string> -</resources> diff --git a/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt deleted file mode 100644 index 6dcd8e419a8c..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt +++ /dev/null @@ -1 +0,0 @@ -One
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt deleted file mode 100644 index 0e41ffa475af..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt +++ /dev/null @@ -1 +0,0 @@ -LoaderOne
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_png.png Binary files differdeleted file mode 100644 index 4eb8ca3537ea..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_png.png +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_xml.xml deleted file mode 100644 index 57a8cf1b86de..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_xml.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<color - xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#000001" - /> diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/layout/layout.xml deleted file mode 100644 index ede3838be8de..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider1/res/layout/layout.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<RelativeLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/values/values.xml deleted file mode 100644 index 5ef75d5426a0..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider1/res/values/values.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?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> - <dimen name="test">100dp</dimen> - <string name="test">One</string> - - <string name="additional">One</string> - <public type="string" name="additional" id="0x7f0400fe" /> -</resources>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt deleted file mode 100644 index 5673baa5b53d..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt +++ /dev/null @@ -1 +0,0 @@ -Two
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt deleted file mode 100644 index bca782ec1b2b..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt +++ /dev/null @@ -1 +0,0 @@ -LoaderTwo
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_png.png Binary files differdeleted file mode 100644 index 671d6d00be31..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_png.png +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_xml.xml deleted file mode 100644 index 333fe346998c..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_xml.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<color - xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#000002" - /> diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/layout/layout.xml deleted file mode 100644 index d8bff90d56d8..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider2/res/layout/layout.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml deleted file mode 100644 index 387c51905d8a..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?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> - <dimen name="test">200dp</dimen> - <string name="test">Two</string> - - <string name="additional">Two</string> - <public type="string" name="additional" id="0x7f0400fe" /> -</resources>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt deleted file mode 100644 index 368c34d3ba04..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt +++ /dev/null @@ -1 +0,0 @@ -Three
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt deleted file mode 100644 index bae8ef79a2ce..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt +++ /dev/null @@ -1 +0,0 @@ -LoaderThree
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_png.png Binary files differdeleted file mode 100644 index 5231d175569e..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_png.png +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_xml.xml deleted file mode 100644 index 41095d4a158b..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_xml.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?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. - --> - -<color - xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#000003" - /> diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/layout/layout.xml deleted file mode 100644 index d58d3db12ad4..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider3/res/layout/layout.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?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. - --> - -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml deleted file mode 100644 index ab75bfac29c6..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?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> - <dimen name="test">300dp</dimen> - <string name="test">Three</string> - - <string name="additional">Three</string> - <public type="string" name="additional" id="0x7f0400fe" /> -</resources>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt deleted file mode 100644 index ad70cdd4ab64..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt +++ /dev/null @@ -1 +0,0 @@ -Four
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt deleted file mode 100644 index b75d9963575b..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt +++ /dev/null @@ -1 +0,0 @@ -LoaderFour
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_png.png Binary files differdeleted file mode 100644 index e9a4cfcef316..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_png.png +++ /dev/null diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_xml.xml deleted file mode 100644 index 0623245c6152..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_xml.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?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. - --> - -<color - xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#000004" - /> diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/layout/layout.xml deleted file mode 100644 index ab9e26529fe7..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider4/res/layout/layout.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?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. - --> - -<TableLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml deleted file mode 100644 index 896993e9d9a6..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?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> - <dimen name="test">400dp</dimen> - <string name="test">Four</string> - - <string name="additional">Four</string> - <public type="string" name="additional" id="0x7f0400fe" /> -</resources>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider_additional/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider_additional/res/values/values.xml deleted file mode 100644 index 29918d7105ef..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider_additional/res/values/values.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?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 type="string" name="additional" id="0x7f0400fe" /> -</resources> diff --git a/core/tests/ResourceLoaderTests/resources/provider_stable/res/values/public.xml b/core/tests/ResourceLoaderTests/resources/provider_stable/res/values/public.xml deleted file mode 100644 index 269c40fc2a38..000000000000 --- a/core/tests/ResourceLoaderTests/resources/provider_stable/res/values/public.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?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 type="dimen" name="test" id="0x7f010000" /> - <public type="drawable" name="drawable_png" id="0x7f020000" /> - <public type="drawable" name="drawable_xml" id="0x7f020001" /> - <public type="layout" name="layout" id="0x7f030000" /> - <public type="string" name="test" id="0x7f040000" /> -</resources>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt deleted file mode 100644 index ec6a605340ae..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt +++ /dev/null @@ -1,298 +0,0 @@ -/* - * 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 - */ - -package android.content.res.loader.test - -import android.content.Context -import android.content.res.AssetFileDescriptor -import android.content.res.Configuration -import android.content.res.Resources -import android.content.res.loader.AssetsProvider -import android.content.res.loader.ResourcesProvider -import android.os.ParcelFileDescriptor -import android.system.Os -import android.util.ArrayMap -import androidx.test.InstrumentationRegistry -import org.json.JSONObject -import org.junit.After -import org.junit.Before -import java.io.Closeable -import java.io.FileOutputStream -import java.io.File -import java.io.FileDescriptor -import java.util.zip.ZipInputStream - -abstract class ResourceLoaderTestBase { - protected val PROVIDER_ONE: String = "FrameworksResourceLoaderTests_ProviderOne" - protected val PROVIDER_TWO: String = "FrameworksResourceLoaderTests_ProviderTwo" - protected val PROVIDER_THREE: String = "FrameworksResourceLoaderTests_ProviderThree" - protected val PROVIDER_FOUR: String = "FrameworksResourceLoaderTests_ProviderFour" - protected val PROVIDER_EMPTY: String = "empty" - - companion object { - /** Converts the map to a stable JSON string representation. */ - fun mapToString(m: Map<String, String>): String { - return JSONObject(ArrayMap<String, String>().apply { putAll(m) }).toString() - } - - /** Creates a lambda that runs multiple resources queries and concatenates the results. */ - fun query(queries: Map<String, (Resources) -> String>): Resources.() -> String { - return { - val resultMap = ArrayMap<String, String>() - queries.forEach { q -> - resultMap[q.key] = try { - q.value.invoke(this) - } catch (e: Exception) { - e.javaClass.simpleName - } - } - mapToString(resultMap) - } - } - } - - // Data type of the current test iteration - open lateinit var dataType: DataType - - protected lateinit var context: Context - protected lateinit var resources: Resources - - // Track opened streams and ResourcesProviders to close them after testing - private val openedObjects = mutableListOf<Closeable>() - - @Before - fun setUpBase() { - context = InstrumentationRegistry.getTargetContext() - .createConfigurationContext(Configuration()) - resources = context.resources - } - - @After - fun removeAllLoaders() { - resources.clearLoaders() - context.applicationContext.resources.clearLoaders() - openedObjects.forEach { - try { - it.close() - } catch (ignored: Exception) { - } - } - } - - protected fun String.openProvider(dataType: DataType, - assetsProvider: MemoryAssetsProvider?): ResourcesProvider { - if (assetsProvider != null) { - openedObjects += assetsProvider - } - return when (dataType) { - DataType.APK_DISK_FD -> { - val file = context.copiedAssetFile("$this.apk") - ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd), - assetsProvider).apply { - file.close() - } - } - DataType.APK_DISK_FD_OFFSETS -> { - val asset = context.assets.openFd("$this.apk") - ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset, - asset.length, assetsProvider).apply { - asset.close() - } - } - DataType.ARSC_DISK_FD -> { - val file = context.copiedAssetFile("$this.arsc") - ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd), - assetsProvider).apply { - file.close() - } - } - DataType.ARSC_DISK_FD_OFFSETS -> { - val asset = context.assets.openFd("$this.arsc") - ResourcesProvider.loadFromTable(asset.parcelFileDescriptor, asset.startOffset, - asset.length, assetsProvider).apply { - asset.close() - } - } - DataType.APK_RAM_OFFSETS -> { - val asset = context.assets.openFd("$this.apk") - val leadingGarbageSize = 100L - val trailingGarbageSize = 55L - val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(), - trailingGarbageSize.toInt()) - ResourcesProvider.loadFromApk(fd, leadingGarbageSize, asset.declaredLength, - assetsProvider).apply { - asset.close() - fd.close() - } - } - DataType.APK_RAM_FD -> { - val asset = context.assets.openFd("$this.apk") - var fd = loadAssetIntoMemory(asset) - ResourcesProvider.loadFromApk(fd, assetsProvider).apply { - asset.close() - fd.close() - } - } - DataType.ARSC_RAM_MEMORY -> { - val asset = context.assets.openFd("$this.arsc") - var fd = loadAssetIntoMemory(asset) - ResourcesProvider.loadFromTable(fd, assetsProvider).apply { - asset.close() - fd.close() - } - } - DataType.ARSC_RAM_MEMORY_OFFSETS -> { - val asset = context.assets.openFd("$this.arsc") - val leadingGarbageSize = 100L - val trailingGarbageSize = 55L - val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(), - trailingGarbageSize.toInt()) - ResourcesProvider.loadFromTable(fd, leadingGarbageSize, asset.declaredLength, - assetsProvider).apply { - asset.close() - fd.close() - } - } - DataType.EMPTY -> { - if (equals(PROVIDER_EMPTY)) { - ResourcesProvider.empty(EmptyAssetsProvider()) - } else { - if (assetsProvider == null) ResourcesProvider.empty(ZipAssetsProvider(this)) - else ResourcesProvider.empty(assetsProvider) - } - } - DataType.DIRECTORY -> { - ResourcesProvider.loadFromDirectory(zipToDir("$this.apk").absolutePath, - assetsProvider) - } - DataType.SPLIT -> { - ResourcesProvider.loadFromSplit(context, "${this}_Split") - } - } - } - - class EmptyAssetsProvider : AssetsProvider - - /** An AssetsProvider that reads from a zip asset. */ - inner class ZipAssetsProvider(val providerName: String) : AssetsProvider { - val root: File = zipToDir("$providerName.apk") - - override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? { - val f = File(root, path) - return if (f.exists()) AssetFileDescriptor( - ParcelFileDescriptor.open(File(root, path), - ParcelFileDescriptor.MODE_READ_ONLY), 0, - AssetFileDescriptor.UNKNOWN_LENGTH) else null - } - } - - /** AssetsProvider for testing that returns file descriptors to files in RAM. */ - class MemoryAssetsProvider : AssetsProvider, Closeable { - var loadAssetResults = HashMap<String, FileDescriptor>() - - fun addLoadAssetFdResult(path: String, value: String) = apply { - val fd = Os.memfd_create(path, 0) - val valueBytes = value.toByteArray() - Os.write(fd, valueBytes, 0, valueBytes.size) - loadAssetResults[path] = fd - } - - override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? { - return if (loadAssetResults.containsKey(path)) AssetFileDescriptor( - ParcelFileDescriptor.dup(loadAssetResults[path]), 0, - AssetFileDescriptor.UNKNOWN_LENGTH) else null - } - - override fun close() { - for (f in loadAssetResults.values) { - Os.close(f) - } - } - } - - /** Extracts an archive-based asset into a directory on disk. */ - private fun zipToDir(name: String): File { - val root = File(context.filesDir, name.split('.')[0]) - if (root.exists()) { - return root - } - - root.mkdir() - ZipInputStream(context.assets.open(name)).use { zis -> - while (true) { - val entry = zis.nextEntry ?: break - val file = File(root, entry.name) - if (entry.isDirectory) { - continue - } - - file.parentFile.mkdirs() - file.outputStream().use { output -> - var b = zis.read() - while (b != -1) { - output.write(b) - b = zis.read() - } - } - } - } - return root - } - - /** Loads the asset into a temporary file stored in RAM. */ - private fun loadAssetIntoMemory( - asset: AssetFileDescriptor, - leadingGarbageSize: Int = 0, - trailingGarbageSize: Int = 0 - ): ParcelFileDescriptor { - val originalFd = Os.memfd_create(asset.toString(), 0 /* flags */) - val fd = ParcelFileDescriptor.dup(originalFd) - Os.close(originalFd) - - val input = asset.createInputStream() - FileOutputStream(fd.fileDescriptor).use { output -> - // Add garbage before the APK data - for (i in 0 until leadingGarbageSize) { - output.write(Math.random().toInt()) - } - - for (i in 0 until asset.length.toInt()) { - output.write(input.read()) - } - - // Add garbage after the APK data - for (i in 0 until trailingGarbageSize) { - output.write(Math.random().toInt()) - } - } - - return fd - } - - enum class DataType { - APK_DISK_FD, - APK_DISK_FD_OFFSETS, - APK_RAM_FD, - APK_RAM_OFFSETS, - ARSC_DISK_FD, - ARSC_DISK_FD_OFFSETS, - ARSC_RAM_MEMORY, - ARSC_RAM_MEMORY_OFFSETS, - EMPTY, - DIRECTORY, - SPLIT - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt deleted file mode 100644 index 5aa8814c7481..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt +++ /dev/null @@ -1,815 +0,0 @@ -/* - * 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 - */ - -package android.content.res.loader.test - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.content.res.AssetManager -import android.content.res.Configuration -import android.content.res.Resources -import android.content.res.loader.ResourcesLoader -import android.graphics.Color -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.ColorDrawable -import android.os.IBinder -import androidx.test.rule.ActivityTestRule -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotEquals -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import java.util.Collections - -/** - * Tests generic ResourceLoader behavior. Intentionally abstract in its test methodology because - * the behavior being verified isn't specific to any resource type. As long as it can pass an - * equals check. - * - * Currently tests strings and dimens since String and any Number seemed most relevant to verify. - */ -@RunWith(Parameterized::class) -class ResourceLoaderValuesTest : ResourceLoaderTestBase() { - - @get:Rule - private val mTestActivityRule = ActivityTestRule<TestActivity>(TestActivity::class.java) - - companion object { - @Parameterized.Parameters(name = "{1} {0}") - @JvmStatic - fun parameters(): Array<Any> { - val parameters = mutableListOf<Parameter>() - - // Test resolution of resources encoded within the resources.arsc. - parameters += Parameter( - "tableBased", - query(mapOf( - "getOverlaid" to { res -> - res.getString(R.string.test) - }, - "getAdditional" to { res -> - res.getString(0x7f0400fe /* R.string.additional */) - }, - "getIdentifier" to { res -> - res.getString(res.getIdentifier("test", "string", - "android.content.res.loader.test")) - }, - "getIdentifierAdditional" to { res -> - res.getString(res.getIdentifier("additional", "string", - "android.content.res.loader.test")) - } - )), - mapOf("getOverlaid" to "Not overlaid", - "getAdditional" to "NotFoundException", - "getIdentifier" to "Not overlaid", - "getIdentifierAdditional" to "NotFoundException"), - - mapOf("getOverlaid" to "One", - "getAdditional" to "One", - "getIdentifier" to "One", - "getIdentifierAdditional" to "One"), - - mapOf("getOverlaid" to "Two", - "getAdditional" to "Two", - "getIdentifier" to "Two", - "getIdentifierAdditional" to "Two"), - - mapOf("getOverlaid" to "Three", - "getAdditional" to "Three", - "getIdentifier" to "Three", - "getIdentifierAdditional" to "Three"), - - mapOf("getOverlaid" to "Four", - "getAdditional" to "Four", - "getIdentifier" to "Four", - "getIdentifierAdditional" to "Four"), - listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD, - DataType.APK_RAM_OFFSETS, DataType.ARSC_DISK_FD, - DataType.ARSC_DISK_FD_OFFSETS, DataType.ARSC_RAM_MEMORY, - DataType.ARSC_RAM_MEMORY_OFFSETS, DataType.SPLIT, DataType.DIRECTORY) - ) - - // Test resolution of file-based resources and assets with no assets provider. - parameters += Parameter( - "tableFileBased", - query(mapOf( - // Drawable xml in res directory - "drawableXml" to { res -> - (res.getDrawable(R.drawable.drawable_xml) as ColorDrawable) - .color.toString() - }, - // Asset as compiled XML layout in res directory - "layout" to { res -> - res.getLayout(R.layout.layout).advanceToRoot().name - }, - // Bitmap drawable in res directory - "drawablePng" to { res -> - (res.getDrawable(R.drawable.drawable_png) as BitmapDrawable) - .bitmap.getColor(0, 0).toArgb().toString() - } - )), - mapOf("drawableXml" to Color.parseColor("#B2D2F2").toString(), - "layout" to "MysteryLayout", - "drawablePng" to Color.parseColor("#FF00FF").toString()), - - mapOf("drawableXml" to Color.parseColor("#000001").toString(), - "layout" to "RelativeLayout", - "drawablePng" to Color.RED.toString()), - - mapOf("drawableXml" to Color.parseColor("#000002").toString(), - "layout" to "LinearLayout", - "drawablePng" to Color.GREEN.toString()), - - mapOf("drawableXml" to Color.parseColor("#000003").toString(), - "layout" to "FrameLayout", - "drawablePng" to Color.BLUE.toString()), - - mapOf("drawableXml" to Color.parseColor("#000004").toString(), - "layout" to "TableLayout", - "drawablePng" to Color.WHITE.toString()), - listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD, - DataType.APK_RAM_OFFSETS, DataType.SPLIT, DataType.DIRECTORY) - ) - - // Test resolution of assets. - parameters += Parameter( - "fileBased", - query(mapOf( - // File in the assets directory - "openAsset" to { res -> - res.assets.open("asset.txt").reader().readText() - }, - // From assets directory returning file descriptor - "openAssetFd" to { res -> - res.assets.openFd("asset.txt").readText() - }, - // Asset as compiled XML layout in res directory - "layout" to { res -> - res.assets.openXmlResourceParser("res/layout/layout.xml") - .advanceToRoot().name - } - )), - mapOf("openAsset" to "In assets directory", - "openAssetFd" to "In assets directory", - "layout" to "MysteryLayout"), - - mapOf("openAsset" to "One", - "openAssetFd" to "One", - "layout" to "RelativeLayout"), - - mapOf("openAsset" to "Two", - "openAssetFd" to "Two", - "layout" to "LinearLayout"), - - mapOf("openAsset" to "Three", - "openAssetFd" to "Three", - "layout" to "FrameLayout"), - - mapOf("openAsset" to "Four", - "openAssetFd" to "Four", - "layout" to "TableLayout"), - listOf(DataType.EMPTY) - ) - - // Test assets from apk and provider - parameters += Parameter( - "fileBasedApkAssetsProvider", - query(mapOf( - // File in the assets directory - "openAsset" to { res -> - res.assets.open("asset.txt").reader().readText() - }, - // From assets directory returning file descriptor - "openAssetFd" to { res -> - res.assets.openFd("asset.txt").readText() - } - )), - mapOf("openAsset" to "In assets directory", - "openAssetFd" to "In assets directory"), - - mapOf("openAsset" to "AssetsOne", - "openAssetFd" to "AssetsOne"), - { MemoryAssetsProvider().addLoadAssetFdResult("assets/asset.txt", - "AssetsOne") }, - - mapOf("openAsset" to "Two", - "openAssetFd" to "Two"), - null /* assetProviderTwo */, - - mapOf("openAsset" to "AssetsThree", - "openAssetFd" to "AssetsThree"), - { MemoryAssetsProvider().addLoadAssetFdResult("assets/asset.txt", - "AssetsThree") }, - - mapOf("openAsset" to "Four", - "openAssetFd" to "Four"), - null /* assetProviderFour */, - listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD, - DataType.APK_RAM_OFFSETS, DataType.DIRECTORY) - - ) - - // TODO(151949807): Increase testing for cookie based APIs and for what happens when - // some providers do not overlay base resources - - return parameters.flatMap { parameter -> - parameter.dataTypes.map { dataType -> - arrayOf(dataType, parameter) - } - }.toTypedArray() - } - } - - @Suppress("LateinitVarOverridesLateinitVar") - @field:Parameterized.Parameter(0) - override lateinit var dataType: DataType - - @field:Parameterized.Parameter(1) - lateinit var parameter: Parameter - - private val valueOriginal by lazy { mapToString(parameter.valueOriginal) } - private val valueOne by lazy { mapToString(parameter.valueOne) } - private val valueTwo by lazy { mapToString(parameter.valueTwo) } - private val valueThree by lazy { mapToString(parameter.valueThree) } - private val valueFour by lazy { mapToString(parameter.valueFour) } - - private fun openOne() = PROVIDER_ONE.openProvider(dataType, - parameter.assetProviderOne?.invoke()) - private fun openTwo() = PROVIDER_TWO.openProvider(dataType, - parameter.assetProviderTwo?.invoke()) - private fun openThree() = PROVIDER_THREE.openProvider(dataType, - parameter.assetProviderThree?.invoke()) - private fun openFour() = PROVIDER_FOUR.openProvider(dataType, - parameter.assetProviderFour?.invoke()) - private fun openEmpty() = PROVIDER_EMPTY.openProvider(DataType.EMPTY, null) - - // Class method for syntax highlighting purposes - private fun getValue(c: Context = context) = parameter.getValue(c.resources) - private fun getValue(r: Resources) = parameter.getValue(r) - - @Test - fun assertValueUniqueness() { - // Ensure the parameters are valid in case of coding errors - val original = getValue() - assertEquals(valueOriginal, original) - assertNotEquals(valueOne, original) - assertNotEquals(valueTwo, original) - assertNotEquals(valueThree, original) - assertNotEquals(valueFour, original) - assertNotEquals(valueTwo, valueOne) - assertNotEquals(valueThree, valueOne) - assertNotEquals(valueFour, valueOne) - assertNotEquals(valueThree, valueTwo) - assertNotEquals(valueFour, valueTwo) - assertNotEquals(valueFour, valueThree) - } - - @Test - fun addProvidersRepeatedly() { - val testOne = openOne() - val testTwo = openTwo() - val loader = ResourcesLoader() - - resources.addLoaders(loader) - loader.addProvider(testOne) - assertEquals(valueOne, getValue()) - - loader.addProvider(testTwo) - assertEquals(valueTwo, getValue()) - - loader.removeProvider(testOne) - assertEquals(valueTwo, getValue()) - - loader.removeProvider(testTwo) - assertEquals(valueOriginal, getValue()) - } - - @Test - fun addLoadersRepeatedly() { - val testOne = openOne() - val testTwo = openTwo() - val loader1 = ResourcesLoader() - val loader2 = ResourcesLoader() - - resources.addLoaders(loader1) - loader1.addProvider(testOne) - assertEquals(valueOne, getValue()) - - resources.addLoaders(loader2) - loader2.addProvider(testTwo) - assertEquals(valueTwo, getValue()) - - resources.removeLoaders(loader1) - assertEquals(valueTwo, getValue()) - - resources.removeLoaders(loader2) - assertEquals(valueOriginal, getValue()) - } - - @Test - fun setMultipleProviders() { - val testOne = openOne() - val testTwo = openTwo() - val loader = ResourcesLoader() - - resources.addLoaders(loader) - loader.providers = listOf(testOne, testTwo) - assertEquals(valueTwo, getValue()) - - loader.removeProvider(testTwo) - assertEquals(valueOne, getValue()) - - loader.providers = Collections.emptyList() - assertEquals(valueOriginal, getValue()) - } - - @Test - fun addMultipleLoaders() { - val loader1 = ResourcesLoader() - loader1.addProvider(openOne()) - val loader2 = ResourcesLoader() - loader2.addProvider(openTwo()) - - resources.addLoaders(loader1, loader2) - assertEquals(valueTwo, getValue()) - - resources.removeLoaders(loader2) - assertEquals(valueOne, getValue()) - - resources.removeLoaders(loader1) - assertEquals(valueOriginal, getValue()) - } - - @Test - fun emptyProvider() { - val testOne = openOne() - val testTwo = openTwo() - val testEmpty = openEmpty() - val loader = ResourcesLoader() - - resources.addLoaders(loader) - loader.providers = listOf(testOne, testEmpty, testTwo) - assertEquals(valueTwo, getValue()) - - loader.removeProvider(testTwo) - assertEquals(valueOne, getValue()) - - loader.removeProvider(testOne) - assertEquals(valueOriginal, getValue()) - - loader.providers = Collections.emptyList() - assertEquals(valueOriginal, getValue()) - } - - @Test(expected = UnsupportedOperationException::class) - fun getProvidersDoesNotLeakMutability() { - val testOne = openOne() - val loader = ResourcesLoader() - val providers = loader.providers - providers += testOne - } - - @Test(expected = UnsupportedOperationException::class) - fun getLoadersDoesNotLeakMutability() { - val loaders = resources.loaders - loaders += ResourcesLoader() - } - - @Test - fun alreadyAddedProviderNoOps() { - val testOne = openOne() - val testTwo = openTwo() - val loader = ResourcesLoader() - - resources.addLoaders(loader) - loader.addProvider(testOne) - loader.addProvider(testTwo) - loader.addProvider(testOne) - - assertEquals(2, loader.providers.size) - assertEquals(loader.providers[0], testOne) - assertEquals(loader.providers[1], testTwo) - } - - @Test - fun alreadyAddedLoaderNoOps() { - val loader1 = ResourcesLoader() - loader1.addProvider(openOne()) - val loader2 = ResourcesLoader() - loader2.addProvider(openTwo()) - - resources.addLoaders(loader1) - resources.addLoaders(loader2) - resources.addLoaders(loader1) - - assertEquals(2, resources.loaders.size) - assertEquals(resources.loaders[0], loader1) - assertEquals(resources.loaders[1], loader2) - } - - @Test - fun repeatedRemoveProviderNoOps() { - val testOne = openOne() - val testTwo = openTwo() - val loader = ResourcesLoader() - - resources.addLoaders(loader) - loader.addProvider(testOne) - loader.addProvider(testTwo) - - loader.removeProvider(testOne) - loader.removeProvider(testOne) - - assertEquals(1, loader.providers.size) - assertEquals(loader.providers[0], testTwo) - } - - @Test - fun repeatedRemoveLoaderNoOps() { - val loader1 = ResourcesLoader() - loader1.addProvider(openOne()) - val loader2 = ResourcesLoader() - loader2.addProvider(openTwo()) - - resources.addLoaders(loader1, loader2) - resources.removeLoaders(loader1) - resources.removeLoaders(loader1) - - assertEquals(1, resources.loaders.size) - assertEquals(resources.loaders[0], loader2) - - resources.removeLoaders(loader2, loader2) - - assertEquals(0, resources.loaders.size) - } - - @Test - fun repeatedSetProvider() { - val testOne = openOne() - val testTwo = openTwo() - val loader = ResourcesLoader() - - resources.addLoaders(loader) - loader.providers = listOf(testOne, testTwo) - loader.providers = listOf(testOne, testTwo) - - assertEquals(2, loader.providers.size) - assertEquals(loader.providers[0], testOne) - assertEquals(loader.providers[1], testTwo) - } - - @Test - fun repeatedAddMultipleLoaders() { - val loader1 = ResourcesLoader() - loader1.addProvider(openOne()) - val loader2 = ResourcesLoader() - loader2.addProvider(openTwo()) - - resources.addLoaders(loader1, loader2) - resources.addLoaders(loader1, loader2) - - assertEquals(2, resources.loaders.size) - assertEquals(resources.loaders[0], loader1) - assertEquals(resources.loaders[1], loader2) - } - - @Test - fun reorderProviders() { - val testOne = openOne() - val testTwo = openTwo() - val loader = ResourcesLoader() - - resources.addLoaders(loader) - loader.addProvider(testOne) - loader.addProvider(testTwo) - assertEquals(valueTwo, getValue()) - - loader.removeProvider(testOne) - assertEquals(valueTwo, getValue()) - - loader.addProvider(testOne) - assertEquals(valueOne, getValue()) - - loader.removeProvider(testTwo) - assertEquals(valueOne, getValue()) - - loader.removeProvider(testOne) - assertEquals(valueOriginal, getValue()) - } - - @Test - fun reorderLoaders() { - val testOne = openOne() - val testTwo = openTwo() - val loader1 = ResourcesLoader() - loader1.addProvider(testOne) - val loader2 = ResourcesLoader() - loader2.addProvider(testTwo) - - resources.addLoaders(loader1) - resources.addLoaders(loader2) - assertEquals(valueTwo, getValue()) - - resources.removeLoaders(loader1) - assertEquals(valueTwo, getValue()) - - resources.addLoaders(loader1) - assertEquals(valueOne, getValue()) - - resources.removeLoaders(loader2) - assertEquals(valueOne, getValue()) - - resources.removeLoaders(loader1) - assertEquals(valueOriginal, getValue()) - } - - @Test - fun reorderMultipleLoadersAndProviders() { - val testOne = openOne() - val testTwo = openTwo() - val testThree = openThree() - val testFour = openFour() - - val loader1 = ResourcesLoader() - loader1.providers = listOf(testOne, testTwo) - - val loader2 = ResourcesLoader() - loader2.providers = listOf(testThree, testFour) - - resources.addLoaders(loader1, loader2) - assertEquals(valueFour, getValue()) - - resources.removeLoaders(loader1) - resources.addLoaders(loader1) - assertEquals(valueTwo, getValue()) - - loader1.removeProvider(testTwo) - assertEquals(valueOne, getValue()) - - loader1.removeProvider(testOne) - assertEquals(valueFour, getValue()) - - loader2.removeProvider(testFour) - assertEquals(valueThree, getValue()) - } - - private fun createContext(context: Context, id: Int): Context { - val overrideConfig = Configuration() - overrideConfig.orientation = Int.MAX_VALUE - id - return context.createConfigurationContext(overrideConfig) - } - - @Test - fun copyContextLoaders() { - val loader1 = ResourcesLoader() - loader1.addProvider(openOne()) - val loader2 = ResourcesLoader() - loader2.addProvider(openTwo()) - - resources.addLoaders(loader1) - assertEquals(valueOne, getValue()) - - // The child context should include the loaders of the original context. - val childContext = createContext(context, 0) - assertEquals(valueOne, getValue(childContext)) - - // Changing the loaders of the child context should not affect the original context. - childContext.resources.addLoaders(loader2) - assertEquals(valueOne, getValue()) - assertEquals(valueTwo, getValue(childContext)) - - // Changing the loaders of the original context should not affect the child context. - resources.removeLoaders(loader1) - assertEquals(valueOriginal, getValue()) - assertEquals(valueTwo, getValue(childContext)) - - // A new context created from the original after an update to the original's loaders should - // have the updated loaders. - val originalPrime = createContext(context, 2) - assertEquals(valueOriginal, getValue(originalPrime)) - - // A new context created from the child context after an update to the child's loaders - // should have the updated loaders. - val childPrime = createContext(childContext, 1) - assertEquals(valueTwo, getValue(childPrime)) - } - - @Test - fun loaderUpdatesAffectContexts() { - val testOne = openOne() - val testTwo = openTwo() - val loader = ResourcesLoader() - - resources.addLoaders(loader) - loader.addProvider(testOne) - assertEquals(valueOne, getValue()) - - val childContext = createContext(context, 0) - assertEquals(valueOne, getValue(childContext)) - - // Adding a provider to a loader affects all contexts that use the loader. - loader.addProvider(testTwo) - assertEquals(valueTwo, getValue()) - assertEquals(valueTwo, getValue(childContext)) - - // Changes to the loaders for a context do not affect providers. - resources.clearLoaders() - assertEquals(valueOriginal, getValue()) - assertEquals(valueTwo, getValue(childContext)) - - val childContext2 = createContext(context, 1) - assertEquals(valueOriginal, getValue()) - assertEquals(valueOriginal, getValue(childContext2)) - - childContext2.resources.addLoaders(loader) - assertEquals(valueOriginal, getValue()) - assertEquals(valueTwo, getValue(childContext)) - assertEquals(valueTwo, getValue(childContext2)) - } - - @Test - fun appLoadersIncludedInActivityContexts() { - val loader = ResourcesLoader() - loader.addProvider(openOne()) - - val applicationContext = context.applicationContext - applicationContext.resources.addLoaders(loader) - assertEquals(valueOne, getValue(applicationContext)) - - val activity = mTestActivityRule.launchActivity(Intent()) - assertEquals(valueOne, getValue(activity)) - - applicationContext.resources.clearLoaders() - } - - @Test - fun loadersApplicationInfoChanged() { - val loader1 = ResourcesLoader() - loader1.addProvider(openOne()) - val loader2 = ResourcesLoader() - loader2.addProvider(openTwo()) - - val applicationContext = context.applicationContext - applicationContext.resources.addLoaders(loader1) - assertEquals(valueOne, getValue(applicationContext)) - - var token: IBinder? = null - val activity = mTestActivityRule.launchActivity(Intent()) - mTestActivityRule.runOnUiThread(Runnable { - token = activity.activityToken - val at = activity.activityThread - - // The activity should have the loaders from the application. - assertEquals(valueOne, getValue(applicationContext)) - assertEquals(valueOne, getValue(activity)) - - activity.resources.addLoaders(loader2) - assertEquals(valueOne, getValue(applicationContext)) - assertEquals(valueTwo, getValue(activity)) - - // Relaunches the activity. - at.handleApplicationInfoChanged(activity.applicationInfo) - }) - - mTestActivityRule.runOnUiThread(Runnable { - val activityThread = activity.activityThread - val newActivity = activityThread.getActivity(token) - - // The loader added to the activity loaders should not be persisted. - assertEquals(valueOne, getValue(applicationContext)) - assertEquals(valueOne, getValue(newActivity)) - }) - - applicationContext.resources.clearLoaders() - } - - @Test - fun multipleLoadersHaveSameProviders() { - val provider1 = openOne() - val loader1 = ResourcesLoader() - loader1.addProvider(provider1) - val loader2 = ResourcesLoader() - loader2.addProvider(provider1) - loader2.addProvider(openTwo()) - - resources.addLoaders(loader1, loader2) - assertEquals(valueTwo, getValue()) - - resources.removeLoaders(loader1) - resources.addLoaders(loader1) - assertEquals(valueOne, getValue()) - - assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader }) - } - - @Test(expected = IllegalStateException::class) - fun cannotUseClosedProvider() { - val provider = openOne() - provider.close() - val loader = ResourcesLoader() - loader.addProvider(provider) - } - - @Test(expected = IllegalStateException::class) - fun cannotCloseUsedProvider() { - val provider = openOne() - val loader = ResourcesLoader() - loader.addProvider(provider) - provider.close() - } - - @Test - fun addLoadersRepeatedlyCustomResources() { - val res = Resources(AssetManager::class.java.newInstance(), resources.displayMetrics, - resources.configuration!!) - val originalValue = getValue(res) - val testOne = openOne() - val testTwo = openTwo() - val loader1 = ResourcesLoader() - val loader2 = ResourcesLoader() - - res.addLoaders(loader1) - loader1.addProvider(testOne) - assertEquals(valueOne, getValue(res)) - - res.addLoaders(loader2) - loader2.addProvider(testTwo) - assertEquals(valueTwo, getValue(res)) - - res.removeLoaders(loader1) - res.addLoaders(loader1) - assertEquals(valueOne, getValue(res)) - - res.removeLoaders(loader1) - assertEquals(valueTwo, getValue(res)) - - res.removeLoaders(loader2) - assertEquals(originalValue, getValue(res)) - } - - @Test - fun setMultipleProvidersCustomResources() { - val res = Resources(AssetManager::class.java.newInstance(), resources.displayMetrics, - resources.configuration!!) - val originalValue = getValue(res) - val testOne = openOne() - val testTwo = openTwo() - val loader = ResourcesLoader() - - res.addLoaders(loader) - loader.providers = listOf(testOne, testTwo) - assertEquals(valueTwo, getValue(res)) - - loader.removeProvider(testTwo) - assertEquals(valueOne, getValue(res)) - - loader.providers = Collections.emptyList() - assertEquals(originalValue, getValue(res)) - } - - data class Parameter( - val testPrefix: String, - val getValue: Resources.() -> String, - val valueOriginal: Map<String, String>, - val valueOne: Map<String, String>, - val assetProviderOne: (() -> MemoryAssetsProvider)? = null, - val valueTwo: Map<String, String>, - val assetProviderTwo: (() -> MemoryAssetsProvider)? = null, - val valueThree: Map<String, String>, - val assetProviderThree: (() -> MemoryAssetsProvider)? = null, - val valueFour: Map<String, String>, - val assetProviderFour: (() -> MemoryAssetsProvider)? = null, - val dataTypes: List<DataType> - ) { - constructor( - testPrefix: String, - getValue: Resources.() -> String, - valueOriginal: Map<String, String>, - valueOne: Map<String, String>, - valueTwo: Map<String, String>, - valueThree: Map<String, String>, - valueFour: Map<String, String>, - dataTypes: List<DataType> - ): this(testPrefix, getValue, valueOriginal, valueOne, - null, valueTwo, null, valueThree, null, valueFour, null, dataTypes) - - override fun toString() = testPrefix - } -} - -class TestActivity : Activity()
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt deleted file mode 100644 index 526160d04000..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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. - */ - -package android.content.res.loader.test - -import android.content.Context -import android.content.res.AssetFileDescriptor -import android.content.res.Resources -import android.os.ParcelFileDescriptor -import android.util.TypedValue -import org.mockito.Answers -import org.mockito.stubbing.Answer -import org.xmlpull.v1.XmlPullParser -import java.io.File - -object Utils { - val ANSWER_THROWS = Answer<Any> { - when (val name = it.method.name) { - "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it) - else -> throw UnsupportedOperationException("$name with " + - "${it.arguments?.joinToString()} should not be called") - } - } -} - -fun Int.dpToPx(resources: Resources) = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - this.toFloat(), - resources.displayMetrics -).toInt() - -fun AssetFileDescriptor.readText() = createInputStream().reader().readText() - -fun XmlPullParser.advanceToRoot() = apply { - while (next() != XmlPullParser.START_TAG) { - // Empty - } -} - -fun Context.copiedAssetFile(fileName: String): ParcelFileDescriptor { - return resources.assets.open(fileName).use { input -> - // AssetManager doesn't expose a direct file descriptor to the asset, so copy it to - // an individual file so one can be created manually. - val copiedFile = File(filesDir, fileName) - copiedFile.outputStream().use { output -> - input.copyTo(output) - } - ParcelFileDescriptor.open(copiedFile, ParcelFileDescriptor.MODE_READ_WRITE) - } -} 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..74ca2036d764 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -67,6 +67,7 @@ import android.graphics.drawable.Icon; import android.metrics.LogMaker; import android.net.Uri; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.service.chooser.ChooserTarget; import androidx.test.platform.app.InstrumentationRegistry; @@ -75,8 +76,10 @@ import androidx.test.rule.ActivityTestRule; import com.android.internal.R; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import com.android.internal.app.chooser.DisplayResolveInfo; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 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; @@ -140,6 +143,10 @@ public class ChooserActivityTest { public void cleanOverrideData() { sOverrides.reset(); sOverrides.createPackageManager = mPackageManagerOverride; + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED, + Boolean.toString(false), + true /* makeDefault*/); } @Test @@ -988,7 +995,7 @@ public class ChooserActivityTest { serviceTargets, TARGET_TYPE_CHOOSER_TARGET, directShareToShortcutInfos, - null) + List.of()) ); // Thread.sleep shouldn't be a thing in an integration test but it's @@ -1060,7 +1067,7 @@ public class ChooserActivityTest { serviceTargets, TARGET_TYPE_CHOOSER_TARGET, directShareToShortcutInfos, - null) + List.of()) ); // Thread.sleep shouldn't be a thing in an integration test but it's // necessary here because of the way the code is structured @@ -1148,7 +1155,7 @@ public class ChooserActivityTest { serviceTargets, TARGET_TYPE_CHOOSER_TARGET, directShareToShortcutInfos, - null) + List.of()) ); // Thread.sleep shouldn't be a thing in an integration test but it's // necessary here because of the way the code is structured @@ -1430,6 +1437,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/libs/androidfw/TEST_MAPPING b/libs/androidfw/TEST_MAPPING index d1a6a5c18299..777aa0b429e5 100644 --- a/libs/androidfw/TEST_MAPPING +++ b/libs/androidfw/TEST_MAPPING @@ -5,7 +5,7 @@ "host": true }, { - "name": "FrameworksResourceLoaderTests" + "name": "CtsResourcesLoaderTests" } ] }
\ No newline at end of file diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index afd82aca07c5..43cc4f244f71 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -50,10 +50,8 @@ ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChu } SkAlphaType ImageDecoder::getOutAlphaType() const { - // While an SkBitmap may want to use kOpaque_SkAlphaType for a performance - // optimization, this class just outputs raw pixels. Using either - // premultiplication choice has no effect on decoding an opaque encoded image. - return mUnpremultipliedRequired ? kUnpremul_SkAlphaType : kPremul_SkAlphaType; + return opaque() ? kOpaque_SkAlphaType + : mUnpremultipliedRequired ? kUnpremul_SkAlphaType : kPremul_SkAlphaType; } bool ImageDecoder::setTargetSize(int width, int height) { @@ -82,8 +80,7 @@ bool ImageDecoder::setTargetSize(int width, int height) { SkISize targetSize = { width, height }, decodeSize = targetSize; int sampleSize = mCodec->computeSampleSize(&decodeSize); - if (decodeSize != targetSize && mUnpremultipliedRequired - && !mCodec->getInfo().isOpaque()) { + if (decodeSize != targetSize && mUnpremultipliedRequired && !opaque()) { return false; } diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp index b6b378539bd0..41d939bd6373 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -305,9 +305,6 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong } SkImageInfo bitmapInfo = decoder->getOutputInfo(); - if (decoder->opaque()) { - bitmapInfo = bitmapInfo.makeAlphaType(kOpaque_SkAlphaType); - } if (asAlphaMask && colorType == kGray_8_SkColorType) { bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType); } 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/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index f5b08061f35d..0ab62c14ab9f 100644 --- a/media/java/android/media/AudioDeviceAttributes.java +++ b/media/java/android/media/AudioDeviceAttributes.java @@ -99,11 +99,11 @@ public final class AudioDeviceAttributes implements Parcelable { if (role != ROLE_OUTPUT && role != ROLE_INPUT) { throw new IllegalArgumentException("Invalid role " + role); } - if (role == ROLE_OUTPUT && !AudioDeviceInfo.isValidAudioDeviceTypeOut(type)) { - throw new IllegalArgumentException("Invalid output device type " + type); + if (role == ROLE_OUTPUT) { + AudioDeviceInfo.enforceValidAudioDeviceTypeOut(type); } - if (role == ROLE_INPUT && !AudioDeviceInfo.isValidAudioDeviceTypeIn(type)) { - throw new IllegalArgumentException("Invalid input device type " + type); + if (role == ROLE_INPUT) { + AudioDeviceInfo.enforceValidAudioDeviceTypeIn(type); } mRole = role; diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index db2a1e8b6e7c..6b0e17d84357 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -280,6 +280,28 @@ public final class AudioDeviceInfo { } } + /** + * @hide + * Throws IAE on an invalid output device type + * @param type + */ + public static void enforceValidAudioDeviceTypeOut(int type) { + if (!isValidAudioDeviceTypeOut(type)) { + throw new IllegalArgumentException("Illegal output device type " + type); + } + } + + /** + * @hide + * Throws IAE on an invalid input device type + * @param type + */ + public static void enforceValidAudioDeviceTypeIn(int type) { + if (!isValidAudioDeviceTypeIn(type)) { + throw new IllegalArgumentException("Illegal input device type " + type); + } + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 7408987e465e..8477aa3ca26e 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4571,6 +4571,150 @@ public class AudioManager { } } + /** + * @hide + * Volume behavior for an audio device where a software attenuation is applied + * @see #setDeviceVolumeBehavior(int, String, int) + */ + public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; + /** + * @hide + * Volume behavior for an audio device where the volume is always set to provide no attenuation + * nor gain (e.g. unit gain). + * @see #setDeviceVolumeBehavior(int, String, int) + */ + public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; + /** + * @hide + * Volume behavior for an audio device where the volume is either set to muted, or to provide + * no attenuation nor gain (e.g. unit gain). + * @see #setDeviceVolumeBehavior(int, String, int) + */ + public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; + /** + * @hide + * Volume behavior for an audio device where no software attenuation is applied, and + * the volume is kept synchronized between the host and the device itself through a + * device-specific protocol such as BT AVRCP. + * @see #setDeviceVolumeBehavior(int, String, int) + */ + public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; + /** + * @hide + * Volume behavior for an audio device where no software attenuation is applied, and + * the volume is kept synchronized between the host and the device itself through a + * device-specific protocol (such as for hearing aids), based on the audio mode (e.g. + * normal vs in phone call). + * @see #setMode(int) + * @see #setDeviceVolumeBehavior(int, String, int) + */ + public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; + + /** @hide */ + @IntDef({ + DEVICE_VOLUME_BEHAVIOR_VARIABLE, + DEVICE_VOLUME_BEHAVIOR_FULL, + DEVICE_VOLUME_BEHAVIOR_FIXED, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceVolumeBehavior {} + + /** + * @hide + * Throws IAE on an invalid volume behavior value + * @param volumeBehavior behavior value to check + */ + public static void enforceValidVolumeBehavior(int volumeBehavior) { + switch (volumeBehavior) { + case DEVICE_VOLUME_BEHAVIOR_VARIABLE: + case DEVICE_VOLUME_BEHAVIOR_FULL: + case DEVICE_VOLUME_BEHAVIOR_FIXED: + case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: + case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: + return; + default: + throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior); + } + } + + /** + * @hide + * Sets the volume behavior for an audio output device. + * @param deviceType the type of audio device to be affected. Currently only supports + * {@link AudioDeviceInfo#TYPE_HDMI}, {@link AudioDeviceInfo#TYPE_HDMI_ARC}, + * {@link AudioDeviceInfo#TYPE_LINE_DIGITAL} and {@link AudioDeviceInfo#TYPE_AUX_LINE} + * @param deviceAddress the address of the device, if any + * @param deviceVolumeBehavior one of the device behaviors + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setDeviceVolumeBehavior(int deviceType, @Nullable String deviceAddress, + @DeviceVolumeBehavior int deviceVolumeBehavior) { + setDeviceVolumeBehavior(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, + deviceType, deviceAddress), deviceVolumeBehavior); + } + + /** + * @hide + * Sets the volume behavior for an audio output device. + * @param device the device to be affected. Currently only supports devices of type + * {@link AudioDeviceInfo#TYPE_HDMI}, {@link AudioDeviceInfo#TYPE_HDMI_ARC}, + * {@link AudioDeviceInfo#TYPE_LINE_DIGITAL} and {@link AudioDeviceInfo#TYPE_AUX_LINE} + * @param deviceVolumeBehavior one of the device behaviors + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, + @DeviceVolumeBehavior int deviceVolumeBehavior) { + // verify arguments (validity of device type is enforced in server) + Objects.requireNonNull(device); + enforceValidVolumeBehavior(deviceVolumeBehavior); + // communicate with service + final IAudioService service = getService(); + try { + service.setDeviceVolumeBehavior(device, deviceVolumeBehavior, + mApplicationContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Returns the volume device behavior for the given device type and address + * @param deviceType an audio output device type, as defined in {@link AudioDeviceInfo} + * @param deviceAddress the address of the audio device, if any. + * @return the volume behavior for the device + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public @DeviceVolumeBehavior int getDeviceVolumeBehavior(int deviceType, + @Nullable String deviceAddress) { + // verify arguments + AudioDeviceInfo.enforceValidAudioDeviceTypeOut(deviceType); + return getDeviceVolumeBehavior(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, + deviceType, deviceAddress)); + } + + /** + * @hide + * Returns the volume device behavior for the given audio device + * @param device the audio device + * @return the volume behavior for the device + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public @DeviceVolumeBehavior int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) + { + // verify arguments (validity of device type is enforced in server) + Objects.requireNonNull(device); + // communicate with service + final IAudioService service = getService(); + try { + return service.getDeviceVolumeBehavior(device); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Indicate wired accessory connection state change. * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx) diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 453a5d8a5b7e..bb10e1fe2f2c 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -294,6 +294,11 @@ interface IAudioService { oneway void setRttEnabled(in boolean rttEnabled); + void setDeviceVolumeBehavior(in AudioDeviceAttributes device, + in int deviceVolumeBehavior, in String pkgName); + + int getDeviceVolumeBehavior(in AudioDeviceAttributes device); + // WARNING: read warning at top of file, new methods that need to be used by native // code via IAudioManager.h need to be added to the top section. } 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/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java index f6679c0e681f..9d98479dfeff 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java @@ -219,6 +219,9 @@ public class NotificationPanelViewController extends OverlayViewController { mNavBarNotificationTouchListener = (v, event) -> { + if (!isInflated()) { + return true; + } boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event); if (consumed) { return true; diff --git a/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml b/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml index 2e9381aac806..5f84587a71a4 100644 --- a/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml +++ b/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml @@ -14,9 +14,7 @@ ~ limitations under the License. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.apk.cts.shim" - android:versionCode="2" - android:versionName="2.0" > + package="com.android.cts.ctsshim"> - <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="P" /> + <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="P.123" /> </manifest>
\ No newline at end of file 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/drawable/control_background_ripple.xml b/packages/SystemUI/res/drawable/control_background_ripple.xml index 37914e272811..27e3da9fc462 100644 --- a/packages/SystemUI/res/drawable/control_background_ripple.xml +++ b/packages/SystemUI/res/drawable/control_background_ripple.xml @@ -17,7 +17,10 @@ <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:attr/colorControlHighlight"> <item android:id="@android:id/mask"> - <color android:color="@android:color/white" /> + <shape android:shape="rectangle"> + <solid android:color="@android:color/white" /> + <corners android:radius="@dimen/control_corner_radius" /> + </shape> </item> <item android:drawable="@drawable/control_background" /> </ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index b83e500fbaeb..5119b59424d4 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -109,6 +109,10 @@ android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:button="@drawable/controls_btn_star" + android:background="@android:color/transparent" + android:clickable="false" + android:selectable="false" + android:importantForAccessibility="no" android:layout_marginTop="4dp" android:layout_marginStart="4dp" app:layout_constraintStart_toEndOf="@id/subtitle" 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/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 7cbc840afed4..57e3f14d7aed 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -971,13 +971,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean changed = false; if (enabled && (oldIntent == null)) { - ComponentName poComponent = mDevicePolicyManager.getProfileOwnerAsUser(userId); - if (poComponent == null) { - Log.e(TAG, "No profile owner found for User " + userId); + ComponentName supervisorComponent = + mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent( + UserHandle.of(userId)); + if (supervisorComponent == null) { + Log.e(TAG, "No Profile Owner or Device Owner supervision app found for User " + + userId); } else { Intent intent = new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE) - .setPackage(poComponent.getPackageName()); + .setPackage(supervisorComponent.getPackageName()); ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, 0); if (resolveInfo != null && resolveInfo.serviceInfo != null) { Intent launchIntent = 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/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt index e89c66e0137c..74b94e76dfc1 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt @@ -166,7 +166,7 @@ open class BroadcastDispatcher @Inject constructor ( @VisibleForTesting protected open fun createUBRForUser(userId: Int) = - UserBroadcastDispatcher(context, userId, mainHandler, bgLooper) + UserBroadcastDispatcher(context, userId, bgLooper) override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("Broadcast dispatcher:") diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt index 0c631aacab82..4e84f06f51a7 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt @@ -27,7 +27,6 @@ import android.os.UserHandle import android.util.ArrayMap import android.util.ArraySet import android.util.Log -import androidx.annotation.MainThread import androidx.annotation.VisibleForTesting import com.android.internal.util.Preconditions import com.android.systemui.Dumpable @@ -46,11 +45,13 @@ private const val DEBUG = false * * Created by [BroadcastDispatcher] as needed by users. The value of [userId] can be * [UserHandle.USER_ALL]. + * + * Each instance of this class will register itself exactly once with [Context]. Updates to the + * [IntentFilter] will be done in the background thread. */ class UserBroadcastDispatcher( private val context: Context, private val userId: Int, - private val mainHandler: Handler, private val bgLooper: Looper ) : BroadcastReceiver(), Dumpable { @@ -168,7 +169,7 @@ class UserBroadcastDispatcher( // Only call this from a BG thread private fun createFilterAndRegisterReceiverBG() { val intentFilter = createFilter() - mainHandler.post(RegisterReceiverRunnable(intentFilter)) + bgHandler.post(RegisterReceiverRunnable(intentFilter)) } override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { @@ -207,10 +208,7 @@ class UserBroadcastDispatcher( /* * Registers and unregisters the BroadcastReceiver - * - * Must be called from Main Thread */ - @MainThread override fun run() { if (registered.get()) { context.unregisterReceiver(this@UserBroadcastDispatcher) diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 1c69594469c1..be9cd5f01c86 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -549,6 +549,7 @@ public class BubbleData { Log.e(TAG, "Attempt to expand stack without selected bubble!"); return; } + mSelectedBubble.markUpdatedAt(mTimeSource.currentTimeMillis()); mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis()); mStateChange.orderChanged |= repackAll(); } else if (!mBubbles.isEmpty()) { @@ -662,7 +663,7 @@ public class BubbleData { /** * This applies a full sort and group pass to all existing bubbles. The bubbles are grouped - * by groupId. Each group is then sorted by the max(lastUpdated) time of it's bubbles. Bubbles + * by groupId. Each group is then sorted by the max(lastUpdated) time of its bubbles. Bubbles * within each group are then sorted by lastUpdated descending. * * @return true if the position of any bubbles changed as a result diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 764fda05354c..1291dd98932e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -151,10 +151,10 @@ private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChang subtitle.text = data.control.subtitle favorite.isChecked = data.favorite removed.text = if (data.removed) "Removed" else "" - favorite.setOnClickListener { + itemView.setOnClickListener { + favorite.isChecked = !favorite.isChecked favoriteCallback(data.control.controlId, favorite.isChecked) } - itemView.setOnClickListener { favorite.performClick() } applyRenderInfo(renderInfo) } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java index 5911805f9f6b..a7c40435b3ca 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java @@ -37,6 +37,8 @@ import android.content.res.Resources; import android.hardware.SensorPrivacyManager; import android.media.AudioManager; import android.net.ConnectivityManager; +import android.net.NetworkScoreManager; +import android.net.wifi.WifiManager; import android.os.BatteryStats; import android.os.Handler; import android.os.PowerManager; @@ -193,6 +195,12 @@ public class SystemServicesModule { return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL); } + @Provides + @Singleton + static NetworkScoreManager provideNetworkScoreManager(Context context) { + return context.getSystemService(NetworkScoreManager.class); + } + @Singleton @Provides static NotificationManager provideNotificationManager(Context context) { @@ -273,6 +281,12 @@ public class SystemServicesModule { return (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE); } + @Provides + @Singleton + static WifiManager provideWifiManager(Context context) { + return context.getSystemService(WifiManager.class); + } + @Singleton @Provides static WindowManager provideWindowManager(Context context) { 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/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 7974281b956f..8397c65dbdb0 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -162,7 +162,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } /** - * Synchronizes the current bounds with the pinned stack. + * Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations. */ void synchronizePinnedStackBounds() { cancelAnimations(); @@ -178,6 +178,21 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } /** + * Synchronizes the current bounds with either the pinned stack, or the ongoing animation. This + * is done to prepare for a touch gesture. + */ + void synchronizePinnedStackBoundsForTouchGesture() { + if (mAnimatingToBounds.isEmpty()) { + // If we're not animating anywhere, sync normally. + synchronizePinnedStackBounds(); + } else { + // If we're animating, set the current bounds to the animated bounds. That way, the + // touch gesture will begin at the most recent animated location of the bounds. + mBounds.set(mAnimatedBounds); + } + } + + /** * Tries to move the pinned stack to the given {@param bounds}. */ void movePip(Rect toBounds) { @@ -295,13 +310,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, final float estimatedFlingYEndValue = PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY); - setAnimatingToBounds(new Rect( - (int) xEndValue, - (int) estimatedFlingYEndValue, - (int) xEndValue + mBounds.width(), - (int) estimatedFlingYEndValue + mBounds.height())); - - startBoundsAnimation(); + startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */); } /** @@ -322,9 +331,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mAnimatedBoundsPhysicsAnimator .spring(FloatProperties.RECT_X, bounds.left, springConfig) .spring(FloatProperties.RECT_Y, bounds.top, springConfig); - startBoundsAnimation(); - - setAnimatingToBounds(bounds); + startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */); } /** @@ -349,7 +356,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, (target, values) -> updateAction.run()); } - startBoundsAnimation(); + startBoundsAnimator(dismissEndPoint.x /* toX */, dismissEndPoint.y /* toY */); } /** @@ -418,11 +425,23 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * This will also add end actions to the bounds animator that cancel the TimeAnimator and update * the 'real' bounds to equal the final animated bounds. */ - private void startBoundsAnimation() { + private void startBoundsAnimator(float toX, float toY) { cancelAnimations(); + // Set animatingToBounds directly to avoid allocating a new Rect, but then call + // setAnimatingToBounds to run the normal logic for changing animatingToBounds. + mAnimatingToBounds.set( + (int) toX, + (int) toY, + (int) toX + mBounds.width(), + (int) toY + mBounds.height()); + setAnimatingToBounds(mAnimatingToBounds); + mAnimatedBoundsPhysicsAnimator - .withEndActions(() -> mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds)) + .withEndActions(() -> { + mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds); + mAnimatingToBounds.setEmpty(); + }) .addUpdateListener(mResizePipUpdateListener) .start(); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index dfd5d2fecd38..7cc2759ad59a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -384,7 +384,7 @@ public class PipTouchHandler { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { - mMotionHelper.synchronizePinnedStackBounds(); + mMotionHelper.synchronizePinnedStackBoundsForTouchGesture(); mGesture.onDown(mTouchState); break; } 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/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java index f9b14737332b..ebdcc0006dce 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java @@ -25,6 +25,7 @@ import android.os.Looper; import android.os.Message; import android.provider.Settings; import android.telephony.SubscriptionManager; +import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.TextView; @@ -260,7 +261,9 @@ public class QSCarrierGroupController { mCarrierGroups[i].setVisibility(View.GONE); } mNoSimTextView.setText(info.carrierText); - mNoSimTextView.setVisibility(View.VISIBLE); + if (!TextUtils.isEmpty(info.carrierText)) { + mNoSimTextView.setVisibility(View.VISIBLE); + } } handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread. } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index e21861ab426e..3879c164a84c 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -1085,14 +1085,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, crop.offsetTo(-(otherTaskRect.left - otherRect.left), -(otherTaskRect.top - otherRect.top)); t.setWindowCrop(mTiles.mSecondarySurface, crop); - // Reposition home and recents surfaces or they would be positioned relatively to its - // parent (split-screen secondary task) position. - for (int i = mTiles.mHomeAndRecentsSurfaces.size() - 1; i >= 0; --i) { - t.setPosition(mTiles.mHomeAndRecentsSurfaces.get(i), - mTiles.mHomeBounds.left - otherTaskRect.left, - mTiles.mHomeBounds.top - otherTaskRect.top); - t.setWindowCrop(mTiles.mHomeAndRecentsSurfaces.get(i), null); - } final SurfaceControl dividerCtrl = getWindowSurfaceControl(); if (dividerCtrl != null) { if (isHorizontalDivision()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index d7322a04ba49..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 @@ -68,6 +69,7 @@ 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 @@ -83,6 +85,26 @@ class NotificationShadeDepthController @Inject constructor( } /** + * 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. */ private var wakeAndUnlockBlurRadius = 0 @@ -99,9 +121,19 @@ class NotificationShadeDepthController @Inject constructor( val updateBlurCallback = Choreographer.FrameCallback { updateScheduled = false - var shadeRadius = max(shadeSpring.radius, wakeAndUnlockBlurRadius) - shadeRadius = (shadeRadius * (1f - brightnessMirrorSpring.ratio)).toInt() - val blur = max(shadeRadius, 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, @@ -187,7 +219,6 @@ class NotificationShadeDepthController @Inject constructor( if (statusBarStateController.state == StatusBarState.SHADE) { newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion) } - shadeSpring.animateTo(newBlur) } @@ -212,6 +243,9 @@ class NotificationShadeDepthController @Inject constructor( it.println("globalActionsRadius: ${globalActionsSpring.radius}") it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}") it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius") + it.println("notificationLaunchAnimationProgress: " + + "${notificationLaunchAnimationParams?.linearProgress}") + it.println("ignoreShadeBlurUntilHidden: $ignoreShadeBlurUntilHidden") } } 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/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt new file mode 100644 index 000000000000..57f8a6a3abef --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt @@ -0,0 +1,81 @@ +/* + * 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.systemui.statusbar.notification.collection + +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection + +/** + * Stores the state that [ShadeListBuilder] assigns to this [ListEntry] + */ +data class ListAttachState private constructor( + /** + * Null if not attached to the current shade list. If top-level, then the shade list root. If + * part of a group, then that group's GroupEntry. + */ + var parent: GroupEntry?, + + /** + * The section that this ListEntry was sorted into. If the child of the group, this will be the + * parent's section. Null if not attached to the list. + */ + var section: NotifSection?, + var sectionIndex: Int, + + /** + * If a [NotifFilter] is excluding this entry from the list, then that filter. Always null for + * [GroupEntry]s. + */ + var excludingFilter: NotifFilter?, + + /** + * The [NotifPromoter] promoting this entry to top-level, if any. Always null for [GroupEntry]s. + */ + var promoter: NotifPromoter? +) { + + /** Copies the state of another instance. */ + fun clone(other: ListAttachState) { + parent = other.parent + section = other.section + sectionIndex = other.sectionIndex + excludingFilter = other.excludingFilter + promoter = other.promoter + } + + /** Resets back to a "clean" state (the same as created by the factory method) */ + fun reset() { + parent = null + section = null + sectionIndex = -1 + excludingFilter = null + promoter = null + } + + companion object { + @JvmStatic + fun create(): ListAttachState { + return ListAttachState( + null, + null, + -1, + null, + null) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java index b5c81b2adfa0..0caf0dc4a3a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java @@ -102,11 +102,11 @@ public class ListDumper { .append(")"); } - if (entry.mNotifSection != null) { + if (entry.getNotifSection() != null) { sb.append(" sectionIndex=") .append(entry.getSection()) .append(" sectionName=") - .append(entry.mNotifSection.getName()); + .append(entry.getNotifSection().getName()); } if (includeRecordKeeping) { @@ -133,15 +133,15 @@ public class ListDumper { .append(" "); } - if (notifEntry.mExcludingFilter != null) { + if (notifEntry.getExcludingFilter() != null) { rksb.append("filter=") - .append(notifEntry.mExcludingFilter) + .append(notifEntry.getExcludingFilter().getName()) .append(" "); } - if (notifEntry.mNotifPromoter != null) { + if (notifEntry.getNotifPromoter() != null) { rksb.append("promoter=") - .append(notifEntry.mNotifPromoter) + .append(notifEntry.getNotifPromoter().getName()) .append(" "); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index b048d032feaf..837374fe2201 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -16,7 +16,8 @@ package com.android.systemui.statusbar.notification.collection; -import android.annotation.Nullable; + +import androidx.annotation.Nullable; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; @@ -27,13 +28,11 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga public abstract class ListEntry { private final String mKey; - @Nullable private GroupEntry mParent; - @Nullable private GroupEntry mPreviousParent; - @Nullable NotifSection mNotifSection; - - private int mSection = -1; int mFirstAddedIteration = -1; + private final ListAttachState mPreviousAttachState = ListAttachState.create(); + private final ListAttachState mAttachState = ListAttachState.create(); + ListEntry(String key) { mKey = key; } @@ -51,27 +50,40 @@ public abstract class ListEntry { public abstract @Nullable NotificationEntry getRepresentativeEntry(); @Nullable public GroupEntry getParent() { - return mParent; + return mAttachState.getParent(); } void setParent(@Nullable GroupEntry parent) { - mParent = parent; + mAttachState.setParent(parent); } @Nullable public GroupEntry getPreviousParent() { - return mPreviousParent; - } - - void setPreviousParent(@Nullable GroupEntry previousParent) { - mPreviousParent = previousParent; + return mPreviousAttachState.getParent(); } /** The section this notification was assigned to (0 to N-1, where N is number of sections). */ public int getSection() { - return mSection; + return mAttachState.getSectionIndex(); + } + + @Nullable public NotifSection getNotifSection() { + return mAttachState.getSection(); } - void setSection(int section) { - mSection = section; + ListAttachState getAttachState() { + return mAttachState; + } + + ListAttachState getPreviousAttachState() { + return mPreviousAttachState; + } + + /** + * Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a + * fresh attach state (all entries will be null/default-initialized). + */ + void beginNewAttachState() { + mPreviousAttachState.clone(mAttachState); + mAttachState.reset(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index c9cc67009399..9c2cac71925e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -504,6 +504,11 @@ public class NotifCollection implements Dumpable { extender)); } + mLogger.logLifetimeExtensionEnded( + entry.getKey(), + extender, + entry.mLifetimeExtenders.size()); + if (!isLifetimeExtended(entry)) { if (tryRemoveNotification(entry)) { dispatchEventsAndRebuildList(); @@ -529,6 +534,7 @@ public class NotifCollection implements Dumpable { mAmDispatchingToOtherCode = true; for (NotifLifetimeExtender extender : mLifetimeExtenders) { if (extender.shouldExtendLifetime(entry, entry.mCancellationReason)) { + mLogger.logLifetimeExtended(entry.getKey(), extender); entry.mLifetimeExtenders.add(extender); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 808e1b38eb43..0377c0900d63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -105,18 +105,6 @@ public final class NotificationEntry extends ListEntry { /** List of dismiss interceptors that are intercepting the dismissal of this notification. */ final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>(); - /** If this notification was filtered out, then the filter that did the filtering. */ - @Nullable NotifFilter mExcludingFilter; - - /** - * The NotifFilter, if any, that was active on this notification during the previous run of - * the list builder. - */ - @Nullable NotifFilter mPreviousExcludingFilter; - - /** If this was a group child that was promoted to the top level, then who did the promoting. */ - @Nullable NotifPromoter mNotifPromoter; - /** * If this notification was cancelled by system server, then the reason that was supplied. * Uncancelled notifications always have REASON_NOT_CANCELED. Note that lifetime-extended @@ -283,6 +271,14 @@ public final class NotificationEntry extends ListEntry { mDismissState = requireNonNull(dismissState); } + @Nullable public NotifFilter getExcludingFilter() { + return getAttachState().getExcludingFilter(); + } + + @Nullable public NotifPromoter getNotifPromoter() { + return getAttachState().getPromoter(); + } + /* * Convenience getters for SBN and Ranking members */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 19f7cefe76a0..0a3b02c1a767 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -58,6 +58,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import javax.inject.Inject; import javax.inject.Singleton; @@ -273,8 +274,6 @@ public class ShadeListBuilder implements Dumpable { * if we detect that behavior, we should crash instantly. */ private void buildList() { - mLogger.logStartBuildList(mIterationCount); - mPipelineState.requireIsBefore(STATE_BUILD_STARTED); mPipelineState.setState(STATE_BUILD_STARTED); @@ -316,21 +315,23 @@ public class ShadeListBuilder implements Dumpable { // Step 7: Lock in our group structure and log anything that's changed since the last run mPipelineState.incrementTo(STATE_FINALIZING); - logFilterChanges(); - logParentingChanges(); + logChanges(); freeEmptyGroups(); // Step 8: Dispatch the new list, first to any listeners and then to the view layer - if (mIterationCount % 10 == 0) { - mLogger.logFinalList(mNotifList); - } dispatchOnBeforeRenderList(mReadOnlyNotifList); if (mOnRenderListListener != null) { mOnRenderListListener.onRenderList(mReadOnlyNotifList); } // Step 9: We're done! - mLogger.logEndBuildList(mIterationCount); + mLogger.logEndBuildList( + mIterationCount, + mReadOnlyNotifList.size(), + countChildren(mReadOnlyNotifList)); + if (mIterationCount % 10 == 0) { + mLogger.logFinalList(mNotifList); + } mPipelineState.setState(STATE_IDLE); mIterationCount++; } @@ -354,18 +355,13 @@ public class ShadeListBuilder implements Dumpable { private void resetNotifs() { for (GroupEntry group : mGroups.values()) { - group.setPreviousParent(group.getParent()); - group.setParent(null); + group.beginNewAttachState(); group.clearChildren(); group.setSummary(null); } for (NotificationEntry entry : mAllEntries) { - entry.setPreviousParent(entry.getParent()); - entry.setParent(null); - - entry.mPreviousExcludingFilter = entry.mExcludingFilter; - entry.mExcludingFilter = null; + entry.beginNewAttachState(); if (entry.mFirstAddedIteration == -1) { entry.mFirstAddedIteration = mIterationCount; @@ -439,6 +435,7 @@ public class ShadeListBuilder implements Dumpable { group.setSummary(entry); } else { mLogger.logDuplicateSummary( + mIterationCount, group.getKey(), existingSummary.getKey(), entry.getKey()); @@ -460,7 +457,7 @@ public class ShadeListBuilder implements Dumpable { final String topLevelKey = entry.getKey(); if (mGroups.containsKey(topLevelKey)) { - mLogger.logDuplicateTopLevelKey(topLevelKey); + mLogger.logDuplicateTopLevelKey(mIterationCount, topLevelKey); } else { entry.setParent(ROOT_ENTRY); out.add(entry); @@ -591,10 +588,10 @@ public class ShadeListBuilder implements Dumpable { * filtered out during any of the filtering steps. */ private void annulAddition(ListEntry entry) { - // TODO: We should null out the entry's section and promoter here. However, if we do that, - // future runs will think that the section changed. We need a mPreviousNotifSection, - // similar to what we do for parents. entry.setParent(null); + entry.getAttachState().setSectionIndex(-1); + entry.getAttachState().setSection(null); + entry.getAttachState().setPromoter(null); if (entry.mFirstAddedIteration == mIterationCount) { entry.mFirstAddedIteration = -1; } @@ -607,8 +604,8 @@ public class ShadeListBuilder implements Dumpable { if (entry instanceof GroupEntry) { GroupEntry parent = (GroupEntry) entry; for (NotificationEntry child : parent.getChildren()) { - child.mNotifSection = sectionWithIndex.first; - child.setSection(sectionWithIndex.second); + child.getAttachState().setSection(sectionWithIndex.first); + child.getAttachState().setSectionIndex(sectionWithIndex.second); } parent.sortChildren(sChildComparator); } @@ -622,36 +619,52 @@ public class ShadeListBuilder implements Dumpable { mGroups.values().removeIf(ge -> ge.getSummary() == null && ge.getChildren().isEmpty()); } - private void logFilterChanges() { + private void logChanges() { for (NotificationEntry entry : mAllEntries) { - if (entry.mExcludingFilter != entry.mPreviousExcludingFilter) { - mLogger.logFilterChanged( - entry.getKey(), - entry.mPreviousExcludingFilter, - entry.mExcludingFilter); - } + logAttachStateChanges(entry); + } + for (GroupEntry group : mGroups.values()) { + logAttachStateChanges(group); } } - private void logParentingChanges() { - for (NotificationEntry entry : mAllEntries) { - if (entry.getParent() != entry.getPreviousParent()) { - mLogger.logParentChanged( - entry.getKey(), - entry.getPreviousParent() == null - ? null : entry.getPreviousParent().getKey(), - entry.getParent() == null - ? null : entry.getParent().getKey()); + private void logAttachStateChanges(ListEntry entry) { + + final ListAttachState curr = entry.getAttachState(); + final ListAttachState prev = entry.getPreviousAttachState(); + + if (!Objects.equals(curr, prev)) { + mLogger.logEntryAttachStateChanged( + mIterationCount, + entry.getKey(), + prev.getParent(), + curr.getParent()); + + if (curr.getParent() != prev.getParent()) { + mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent()); } - } - for (GroupEntry group : mGroups.values()) { - if (group.getParent() != group.getPreviousParent()) { - mLogger.logParentChanged( - group.getKey(), - group.getPreviousParent() == null - ? null : group.getPreviousParent().getKey(), - group.getParent() == null - ? null : group.getParent().getKey()); + + if (curr.getExcludingFilter() != prev.getExcludingFilter()) { + mLogger.logFilterChanged( + mIterationCount, prev.getExcludingFilter(), curr.getExcludingFilter()); + } + + // When something gets detached, its promoter and section are always set to null, so + // don't bother logging those changes. + final boolean wasDetached = curr.getParent() == null && prev.getParent() != null; + + if (!wasDetached && curr.getPromoter() != prev.getPromoter()) { + mLogger.logPromoterChanged( + mIterationCount, prev.getPromoter(), curr.getPromoter()); + } + + if (!wasDetached && curr.getSection() != prev.getSection()) { + mLogger.logSectionChanged( + mIterationCount, + prev.getSection(), + prev.getSectionIndex(), + curr.getSection(), + curr.getSectionIndex()); } } } @@ -698,8 +711,9 @@ public class ShadeListBuilder implements Dumpable { }; private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) { - entry.mExcludingFilter = findRejectingFilter(entry, now, filters); - return entry.mExcludingFilter != null; + final NotifFilter filter = findRejectingFilter(entry, now, filters); + entry.getAttachState().setExcludingFilter(filter); + return filter != null; } @Nullable private static NotifFilter findRejectingFilter(NotificationEntry entry, long now, @@ -717,15 +731,7 @@ public class ShadeListBuilder implements Dumpable { private boolean applyTopLevelPromoters(NotificationEntry entry) { NotifPromoter promoter = findPromoter(entry); - - if (promoter != entry.mNotifPromoter) { - mLogger.logPromoterChanged( - entry.getKey(), - entry.mNotifPromoter != null ? entry.mNotifPromoter.getName() : null, - promoter != null ? promoter.getName() : null); - entry.mNotifPromoter = promoter; - } - + entry.getAttachState().setPromoter(promoter); return promoter != null; } @@ -744,17 +750,8 @@ public class ShadeListBuilder implements Dumpable { final NotifSection section = sectionWithIndex.first; final Integer sectionIndex = sectionWithIndex.second; - if (section != entry.mNotifSection) { - mLogger.logSectionChanged( - entry.getKey(), - entry.mNotifSection != null ? entry.mNotifSection.getName() : null, - entry.getSection(), - section.getName(), - sectionIndex); - - entry.mNotifSection = section; - entry.setSection(sectionIndex); - } + entry.getAttachState().setSection(section); + entry.getAttachState().setSectionIndex(sectionIndex); return sectionWithIndex; } @@ -776,6 +773,17 @@ public class ShadeListBuilder implements Dumpable { } } + private static int countChildren(List<ListEntry> entries) { + int count = 0; + for (int i = 0; i < entries.size(); i++) { + final ListEntry entry = entries.get(i); + if (entry instanceof GroupEntry) { + count += ((GroupEntry) entry).getChildren().size(); + } + } + return count; + } + private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) { for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) { mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index e946cf16b3f6..aa107824729e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -24,6 +24,8 @@ import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection import javax.inject.Inject class ShadeListBuilderLogger @Inject constructor( @@ -36,19 +38,13 @@ class ShadeListBuilderLogger @Inject constructor( }) } - fun logStartBuildList(iterationCount: Int) { + fun logEndBuildList(iterationCount: Int, topLevelEntries: Int, numChildren: Int) { buffer.log(TAG, INFO, { - int1 = iterationCount + long1 = iterationCount.toLong() + int1 = topLevelEntries + int2 = numChildren }, { - "Starting to build shade list (run #$int1)" - }) - } - - fun logEndBuildList(iterationCount: Int) { - buffer.log(TAG, INFO, { - int1 = iterationCount - }, { - "Finished building shade list (run #$int1)" + "(Build $long1) Build complete ($int1 top-level entries, $int2 children)" }) } @@ -97,90 +93,115 @@ class ShadeListBuilderLogger @Inject constructor( }) } - fun logDuplicateSummary(groupKey: String, existingKey: String, newKey: String) { + fun logDuplicateSummary(buildId: Int, groupKey: String, existingKey: String, newKey: String) { buffer.log(TAG, WARNING, { + int1 = buildId str1 = groupKey str2 = existingKey str3 = newKey }, { - """Duplicate summary for group "$str1": "$str2" vs. "$str3"""" + """(Build $int1) Duplicate summary for group "$str1": "$str2" vs. "$str3"""" }) } - fun logDuplicateTopLevelKey(topLevelKey: String) { + fun logDuplicateTopLevelKey(buildId: Int, topLevelKey: String) { buffer.log(TAG, WARNING, { + int1 = buildId str1 = topLevelKey }, { - "Duplicate top-level key: $str1" + "(Build $int1) Duplicate top-level key: $str1" }) } - fun logParentChanged(key: String, prevParent: String?, newParent: String?) { + fun logEntryAttachStateChanged( + buildId: Int, + key: String, + prevParent: GroupEntry?, + newParent: GroupEntry? + ) { buffer.log(TAG, INFO, { + int1 = buildId str1 = key - str2 = prevParent - str3 = newParent + str2 = prevParent?.key + str3 = newParent?.key }, { - "Parent change for $str1: $str2 -> $str3" + if (str2 == null && str3 != null) { + "(Build $int1) ATTACHED {$str1}" + } else if (str2 != null && str3 == null) { + "(Build $int1) DETACHED {$str1}" + } else { + "(Build $int1) MODIFIED {$str1}" + } + }) + } + + fun logParentChanged(buildId: Int, prevParent: GroupEntry?, newParent: GroupEntry?) { + buffer.log(TAG, INFO, { + int1 = buildId + str1 = prevParent?.key + str2 = newParent?.key + }, { + if (str1 == null && str2 != null) { + "(Build $int1) Parent is {$str2}" + } else if (str1 != null && str2 == null) { + "(Build $int1) Parent was {$str1}" + } else { + "(Build $int1) Reparent: {$str2} -> {$str3}" + } }) } fun logFilterChanged( - key: String, + buildId: Int, prevFilter: NotifFilter?, newFilter: NotifFilter? ) { buffer.log(TAG, INFO, { - str1 = key - str2 = prevFilter?.name - str3 = newFilter?.name + int1 = buildId + str1 = prevFilter?.name + str2 = newFilter?.name }, { - "Filter changed for $str1: $str2 -> $str3" + "(Build $int1) Filter changed: $str1 -> $str2" }) } fun logPromoterChanged( - key: String, - prevPromoter: String?, - newPromoter: String? + buildId: Int, + prevPromoter: NotifPromoter?, + newPromoter: NotifPromoter? ) { buffer.log(TAG, INFO, { - str1 = key - str2 = prevPromoter - str3 = newPromoter + int1 = buildId + str1 = prevPromoter?.name + str2 = newPromoter?.name }, { - "Promoter changed for $str1: $str2 -> $str3" + "(Build $int1) Promoter changed: $str1 -> $str2" }) } fun logSectionChanged( - key: String, - prevSection: String?, + buildId: Int, + prevSection: NotifSection?, prevIndex: Int, - section: String, - index: Int + newSection: NotifSection?, + newIndex: Int ) { buffer.log(TAG, INFO, { - str1 = key - str2 = section - int1 = index - str3 = prevSection - int2 = prevIndex + long1 = buildId.toLong() + str1 = prevSection?.name + int1 = prevIndex + str2 = newSection?.name + int2 = newIndex }, { - if (str3 == null) { - "Section assigned for $str1: '$str2' (#$int1)" + if (str1 == null) { + "(Build $long1) Section assigned: '$str2' (#$int2)" } else { - "Section changed for $str1: '$str3' (#$int2) -> '$str2' (#$int1)" + "(Build $long1) Section changed: '$str1' (#$int1) -> '$str2' (#$int2)" } }) } fun logFinalList(entries: List<ListEntry>) { - buffer.log(TAG, DEBUG, { - int1 = entries.size - }, { - "List is finalized ($int1 top-level entries):" - }) if (entries.isEmpty()) { buffer.log(TAG, DEBUG, {}, { "(empty list)" }) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index ef302f682df8..c69882d5b83c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -111,6 +111,29 @@ class NotifCollectionLogger @Inject constructor( "RemoteException while attempting to clear all notifications:\n$str1" }) } + + fun logLifetimeExtended(key: String, extender: NotifLifetimeExtender) { + buffer.log(TAG, INFO, { + str1 = key + str2 = extender.name + }, { + "LIFETIME EXTENDED: $str1 by $str2" + }) + } + + fun logLifetimeExtensionEnded( + key: String, + extender: NotifLifetimeExtender, + totalExtenders: Int + ) { + buffer.log(TAG, INFO, { + str1 = key + str2 = extender.name + int1 = totalExtenders + }, { + "LIFETIME EXTENSION ENDED for $str1 by '$str2'; $int1 remaining extensions" + }) + } } private const val TAG = "NotifCollection" 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 19b5f5c79ea2..255c2ea808e0 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 @@ -89,6 +89,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationCounters; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.stack.AmbientState; @@ -148,6 +149,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private KeyguardBypassController mBypassController; private LayoutListener mLayoutListener; private RowContentBindStage mRowContentBindStage; + private PeopleNotificationIdentifier mPeopleNotificationIdentifier; private int mIconTransformContentShift; private int mMaxHeadsUpHeightBeforeN; private int mMaxHeadsUpHeightBeforeP; @@ -1145,7 +1147,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void onPluginDisconnected(NotificationMenuRowPlugin plugin) { boolean existed = mMenuRow.getMenuView() != null; - mMenuRow = new NotificationMenuRow(mContext); + mMenuRow = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); if (existed) { createMenu(); } @@ -1582,7 +1584,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); - mMenuRow = new NotificationMenuRow(mContext); mImageResolver = new NotificationInlineImageResolver(context, new NotificationInlineImageCache()); initDimens(); @@ -1603,9 +1604,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView NotificationMediaManager notificationMediaManager, OnAppOpsClickListener onAppOpsClickListener, FalsingManager falsingManager, - StatusBarStateController statusBarStateController) { + StatusBarStateController statusBarStateController, + PeopleNotificationIdentifier peopleNotificationIdentifier) { mAppName = appName; - if (mMenuRow != null && mMenuRow.getMenuView() != null) { + if (mMenuRow == null) { + mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier); + } + if (mMenuRow.getMenuView() != null) { mMenuRow.setAppName(mAppName); } mLogger = logger; @@ -1620,6 +1625,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView setAppOpsOnClickListener(onAppOpsClickListener); mFalsingManager = falsingManager; mStatusbarStateController = statusBarStateController; + mPeopleNotificationIdentifier = peopleNotificationIdentifier; } private void initDimens() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 39fab439ad07..8b3d06b97882 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -28,6 +28,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.dagger.AppName; import com.android.systemui.statusbar.notification.row.dagger.DismissRunnable; import com.android.systemui.statusbar.notification.row.dagger.NotificationKey; @@ -68,6 +69,7 @@ public class ExpandableNotificationRowController { private Runnable mOnDismissRunnable; private final FalsingManager mFalsingManager; private final boolean mAllowLongPress; + private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; @Inject public ExpandableNotificationRowController(ExpandableNotificationRow view, @@ -83,7 +85,8 @@ public class ExpandableNotificationRowController { NotificationRowContentBinder.InflationCallback inflationCallback, NotificationGutsManager notificationGutsManager, @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, - @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager) { + @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager, + PeopleNotificationIdentifier peopleNotificationIdentifier) { mView = view; mActivatableNotificationViewController = activatableNotificationViewController; mMediaManager = mediaManager; @@ -104,6 +107,7 @@ public class ExpandableNotificationRowController { mOnAppOpsClickListener = mNotificationGutsManager::openGuts; mAllowLongPress = allowLongPress; mFalsingManager = falsingManager; + mPeopleNotificationIdentifier = peopleNotificationIdentifier; } /** @@ -123,7 +127,8 @@ public class ExpandableNotificationRowController { mMediaManager, mOnAppOpsClickListener, mFalsingManager, - mStatusBarStateController + mStatusBarStateController, + mPeopleNotificationIdentifier ); mView.setOnDismissRunnable(mOnDismissRunnable); mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index 212cba6a95e5..83a6eb297ab3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -45,8 +45,9 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.AlphaOptimizedImageView; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent; -import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import java.util.ArrayList; @@ -114,12 +115,16 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private boolean mIsUserTouching; - public NotificationMenuRow(Context context) { + private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; + + public NotificationMenuRow(Context context, + PeopleNotificationIdentifier peopleNotificationIdentifier) { mContext = context; mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear); mHandler = new Handler(Looper.getMainLooper()); mLeftMenuItems = new ArrayList<>(); mRightMenuItems = new ArrayList<>(); + mPeopleNotificationIdentifier = peopleNotificationIdentifier; } @Override @@ -260,7 +265,10 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mSnoozeItem = createSnoozeItem(mContext); } mAppOpsItem = createAppOpsItem(mContext); - if (mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_PEOPLE) { + NotificationEntry entry = mParent.getEntry(); + int personNotifType = mPeopleNotificationIdentifier + .getPeopleNotificationType(entry.getSbn(), entry.getRanking()); + if (personNotifType != PeopleNotificationIdentifier.TYPE_NON_PERSON) { mInfoItem = createConversationItem(mContext); } else { mInfoItem = createInfoItem(mContext); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index b46ca401ae05..8636cb29811c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -104,6 +104,8 @@ public class KeyguardStatusBarView extends RelativeLayout private DisplayCutout mDisplayCutout; private int mRoundedCornerPadding = 0; + // right and left padding applied to this view to account for cutouts and rounded corners + private Pair<Integer, Integer> mPadding = new Pair(0, 0); public KeyguardStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); @@ -158,10 +160,13 @@ public class KeyguardStatusBarView extends RelativeLayout getResources().getDimensionPixelSize( com.android.internal.R.dimen.text_size_small_material)); lp = (MarginLayoutParams) mCarrierLabel.getLayoutParams(); - lp.setMarginStart( - getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin)); - mCarrierLabel.setLayoutParams(lp); + int marginStart = calculateMargin( + getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin), + mPadding.first); + lp.setMarginStart(marginStart); + + mCarrierLabel.setLayoutParams(lp); updateKeyguardStatusBarHeight(); } @@ -220,6 +225,7 @@ public class KeyguardStatusBarView extends RelativeLayout : 0; int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin : baseMarginEnd; + marginEnd = calculateMargin(marginEnd, mPadding.second); if (marginEnd != lp.getMarginEnd()) { lp.setMarginEnd(marginEnd); mSystemIconsContainer.setLayoutParams(lp); @@ -252,10 +258,10 @@ public class KeyguardStatusBarView extends RelativeLayout private void updatePadding(Pair<Integer, Integer> cornerCutoutMargins) { final int waterfallTop = mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top; - Pair<Integer, Integer> padding = + mPadding = StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner( mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding); - setPadding(padding.first, waterfallTop, padding.second, 0); + setPadding(mPadding.first, waterfallTop, mPadding.second, 0); } private boolean updateLayoutParamsNoCutout() { @@ -502,6 +508,17 @@ public class KeyguardStatusBarView extends RelativeLayout } } + /** + * Calculates the margin that isn't already accounted for in the view's padding. + */ + private int calculateMargin(int margin, int padding) { + if (padding >= margin) { + return 0; + } else { + return margin - padding; + } + } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("KeyguardStatusBarView:"); pw.println(" mBatteryCharging: " + mBatteryCharging); 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 ae2c78b121ba..fa55b74606c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1235,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. @@ -3338,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/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 99709402573c..6e5f8a0ae5e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -33,6 +33,7 @@ import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Bundle; @@ -173,10 +174,13 @@ public class NetworkControllerImpl extends BroadcastReceiver @Inject public NetworkControllerImpl(Context context, @Background Looper bgLooper, DeviceProvisionedController deviceProvisionedController, - BroadcastDispatcher broadcastDispatcher) { - this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE), - (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE), - (WifiManager) context.getSystemService(Context.WIFI_SERVICE), + BroadcastDispatcher broadcastDispatcher, ConnectivityManager connectivityManager, + TelephonyManager telephonyManager, WifiManager wifiManager, + NetworkScoreManager networkScoreManager) { + this(context, connectivityManager, + telephonyManager, + wifiManager, + networkScoreManager, SubscriptionManager.from(context), Config.readConfig(context), bgLooper, new CallbackHandler(), new AccessPointControllerImpl(context), @@ -190,6 +194,7 @@ public class NetworkControllerImpl extends BroadcastReceiver @VisibleForTesting NetworkControllerImpl(Context context, ConnectivityManager connectivityManager, TelephonyManager telephonyManager, WifiManager wifiManager, + NetworkScoreManager networkScoreManager, SubscriptionManager subManager, Config config, Looper bgLooper, CallbackHandler callbackHandler, AccessPointControllerImpl accessPointController, @@ -229,7 +234,7 @@ public class NetworkControllerImpl extends BroadcastReceiver } }); mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature, - mCallbackHandler, this, mWifiManager); + mCallbackHandler, this, mWifiManager, mConnectivityManager, networkScoreManager); mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index c2fc18fe21a1..b258fd47871a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -42,13 +42,10 @@ public class WifiSignalController extends public WifiSignalController(Context context, boolean hasMobileDataFeature, CallbackHandler callbackHandler, NetworkControllerImpl networkController, - WifiManager wifiManager) { + WifiManager wifiManager, ConnectivityManager connectivityManager, + NetworkScoreManager networkScoreManager) { super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI, callbackHandler, networkController); - NetworkScoreManager networkScoreManager = - context.getSystemService(NetworkScoreManager.class); - ConnectivityManager connectivityManager = - context.getSystemService(ConnectivityManager.class); mWifiTracker = new WifiStatusTracker(mContext, wifiManager, networkScoreManager, connectivityManager, this::handleStatusUpdated); mWifiTracker.setListening(true); diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java index 0242e8349364..9ccb9bf5b62b 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -21,15 +21,13 @@ import android.annotation.Nullable; import android.app.INotificationManager; import android.app.ITransientNotificationCallback; import android.content.Context; +import android.content.res.Resources; import android.os.IBinder; -import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import android.view.View; -import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; -import android.widget.Toast; import android.widget.ToastPresenter; import com.android.internal.R; @@ -49,18 +47,14 @@ import javax.inject.Singleton; public class ToastUI extends SystemUI implements CommandQueue.Callbacks { private static final String TAG = "ToastUI"; - /** - * Values taken from {@link Toast}. - */ - private static final long DURATION_SHORT = 4000; - private static final long DURATION_LONG = 7000; - private final CommandQueue mCommandQueue; private final WindowManager mWindowManager; private final INotificationManager mNotificationManager; private final AccessibilityManager mAccessibilityManager; - private final ToastPresenter mPresenter; - private ToastEntry mCurrentToast; + private final int mGravity; + private final int mY; + @Nullable private ToastPresenter mPresenter; + @Nullable private ITransientNotificationCallback mCallback; @Inject public ToastUI(Context context, CommandQueue commandQueue) { @@ -79,7 +73,9 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { mWindowManager = windowManager; mNotificationManager = notificationManager; mAccessibilityManager = accessibilityManager; - mPresenter = new ToastPresenter(context, accessibilityManager); + Resources resources = mContext.getResources(); + mGravity = resources.getInteger(R.integer.config_toastDefaultGravity); + mY = resources.getDimensionPixelSize(R.dimen.toast_y_offset); } @Override @@ -91,33 +87,21 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { @MainThread public void showToast(String packageName, IBinder token, CharSequence text, IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { - if (mCurrentToast != null) { + if (mPresenter != null) { hideCurrentToast(); } - View view = mPresenter.getTextToastView(text); - LayoutParams params = getLayoutParams(packageName, windowToken, duration); - mCurrentToast = new ToastEntry(packageName, token, view, windowToken, callback); - try { - mWindowManager.addView(view, params); - } catch (WindowManager.BadTokenException e) { - Log.w(TAG, "Error while attempting to show toast from " + packageName, e); - return; - } - mPresenter.trySendAccessibilityEvent(view, packageName); - if (callback != null) { - try { - callback.onToastShown(); - } catch (RemoteException e) { - Log.w(TAG, "Error calling back " + packageName + " to notify onToastShow()", e); - } - } + View view = ToastPresenter.getTextToastView(mContext, text); + mCallback = callback; + mPresenter = new ToastPresenter(mContext, mWindowManager, mAccessibilityManager, + mNotificationManager, packageName); + mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback); } @Override @MainThread public void hideToast(String packageName, IBinder token) { - if (mCurrentToast == null || !Objects.equals(mCurrentToast.packageName, packageName) - || !Objects.equals(mCurrentToast.token, token)) { + if (mPresenter == null || !Objects.equals(mPresenter.getPackageName(), packageName) + || !Objects.equals(mPresenter.getToken(), token)) { Log.w(TAG, "Attempt to hide non-current toast from package " + packageName); return; } @@ -126,51 +110,7 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { @MainThread private void hideCurrentToast() { - if (mCurrentToast.view.getParent() != null) { - mWindowManager.removeViewImmediate(mCurrentToast.view); - } - String packageName = mCurrentToast.packageName; - try { - mNotificationManager.finishToken(packageName, mCurrentToast.windowToken); - } catch (RemoteException e) { - Log.w(TAG, "Error finishing toast window token from package " + packageName, e); - } - if (mCurrentToast.callback != null) { - try { - mCurrentToast.callback.onToastHidden(); - } catch (RemoteException e) { - Log.w(TAG, "Error calling back " + packageName + " to notify onToastHide()", e); - } - } - mCurrentToast = null; - } - - private LayoutParams getLayoutParams(String packageName, IBinder windowToken, int duration) { - WindowManager.LayoutParams params = new WindowManager.LayoutParams(); - mPresenter.startLayoutParams(params, packageName); - int gravity = mContext.getResources().getInteger( - com.android.internal.R.integer.config_toastDefaultGravity); - int yOffset = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset); - mPresenter.adjustLayoutParams(params, windowToken, duration, gravity, 0, yOffset, 0, 0); - return params; - } - - private static class ToastEntry { - public final String packageName; - public final IBinder token; - public final View view; - public final IBinder windowToken; - - @Nullable - public final ITransientNotificationCallback callback; - - private ToastEntry(String packageName, IBinder token, View view, IBinder windowToken, - @Nullable ITransientNotificationCallback callback) { - this.packageName = packageName; - this.token = token; - this.view = view; - this.windowToken = windowToken; - this.callback = callback; - } + mPresenter.hide(mCallback); + mPresenter = null; } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index eecde7218d28..73f9d8ad6953 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -590,7 +590,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mPackageManager.resolveService(any(Intent.class), eq(0))).thenReturn(resolveInfo); when(mDevicePolicyManager.isSecondaryLockscreenEnabled(eq(UserHandle.of(user)))) .thenReturn(true, false); - when(mDevicePolicyManager.getProfileOwnerAsUser(user)) + when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent( + UserHandle.of(user))) .thenReturn(new ComponentName(packageName, cls)); // Initially null. diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt index 7821ae29592e..847e442f1a49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt @@ -91,7 +91,7 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { fakeExecutor = FakeExecutor(FakeSystemClock()) userBroadcastDispatcher = UserBroadcastDispatcher( - mockContext, USER_ID, handler, testableLooper.looper) + mockContext, USER_ID, testableLooper.looper) userBroadcastDispatcher.pendingResult = mPendingResult } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index c86b5f76fc05..7df39838b167 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -599,13 +599,13 @@ public class BubbleDataTest extends SysuiTestCase { sendUpdatedEntryAtTime(mEntryA1, 1000); sendUpdatedEntryAtTime(mEntryA2, 2000); sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1] - changeExpandedStateAtTime(true, 4000L); + changeExpandedStateAtTime(true, 4000L); // B1 marked updated at 4000L mBubbleData.setListener(mListener); // Test sendUpdatedEntryAtTime(mEntryC1, 4000); verifyUpdateReceived(); - assertOrderChangedTo(mBubbleC1, mBubbleB1, mBubbleA2, mBubbleA1); + assertOrderChangedTo(mBubbleB1, mBubbleC1, mBubbleA2, mBubbleA1); } /** @@ -789,8 +789,7 @@ public class BubbleDataTest extends SysuiTestCase { * When the stack transitions to the collapsed state, the selected bubble is brought to the top. * Bubbles within the same group should move up with it. * <p> - * When the stack transitions back to the expanded state, the previous ordering is restored, as - * long as no changes have been made (adds, removes or updates) while in the collapsed state. + * When the stack transitions back to the expanded state, this new order is kept as is. */ @Test public void test_expansionChanges() { @@ -813,20 +812,12 @@ public class BubbleDataTest extends SysuiTestCase { // stack is expanded. When next collapsed, sorting will be applied and saved, just prior // to moving the selected bubble to the top (first). // - // In this case, the expected re-expand state will be: [B1, B2, A2*, A1] - // - // That state is restored as long as no changes occur (add/remove/update) while in - // the collapsed state. + // In this case, the expected re-expand state will be: [A2, A1, B1, B2] // // collapse -> selected bubble (A2) moves first. changeExpandedStateAtTime(false, 8000L); verifyUpdateReceived(); assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2); - - // expand -> "original" order/grouping restored - changeExpandedStateAtTime(true, 10000L); - verifyUpdateReceived(); - assertOrderChangedTo(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java index fa02231426ac..8a412bff03ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.carrier; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -219,4 +220,18 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mock(NetworkController.IconState.class), 0, 0, true, true, "", "", "", true, 0, true); } + + @Test + public void testNoEmptyVisibleView_airplaneMode() { + CarrierTextController.CarrierTextCallbackInfo + info = new CarrierTextController.CarrierTextCallbackInfo( + "", + new CharSequence[]{""}, + true, + new int[]{0}, + true /* airplaneMode */); + mCallback.updateCarrierInfo(info); + mTestableLooper.processAllMessages(); + assertEquals(View.GONE, mQSCarrierGroup.getNoSimTextView().getVisibility()); + } } 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 1e3636b4ed63..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,6 +44,7 @@ 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 @@ -128,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()) @@ -159,6 +178,24 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { 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/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java index f4fbd7b7d8a8..43cf83f380f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java @@ -57,7 +57,7 @@ public class NotificationEntryBuilder { /* ListEntry properties */ entry.setParent(mParent); - entry.setSection(mSection); + entry.getAttachState().setSectionIndex(mSection); return entry; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index d7c72799e180..3adc3d0455f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -417,8 +417,8 @@ public class ShadeListBuilderTest extends SysuiTestCase { ); // THEN each filtered notif records the NotifFilter that did it - assertEquals(preGroupFilter, mEntrySet.get(1).mExcludingFilter); - assertEquals(preGroupFilter, mEntrySet.get(3).mExcludingFilter); + assertEquals(preGroupFilter, mEntrySet.get(1).getExcludingFilter()); + assertEquals(preGroupFilter, mEntrySet.get(3).getExcludingFilter()); } @Test @@ -447,8 +447,8 @@ public class ShadeListBuilderTest extends SysuiTestCase { ); // THEN each filtered notif records the filter that did it - assertEquals(filter1, mEntrySet.get(1).mExcludingFilter); - assertEquals(filter1, mEntrySet.get(3).mExcludingFilter); + assertEquals(filter1, mEntrySet.get(1).getExcludingFilter()); + assertEquals(filter1, mEntrySet.get(3).getExcludingFilter()); } @Test @@ -471,7 +471,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { ); // THEN each filtered notif records the filter that did it - assertEquals(filter1, mEntrySet.get(0).mExcludingFilter); + assertEquals(filter1, mEntrySet.get(0).getExcludingFilter()); } @Test @@ -502,8 +502,8 @@ public class ShadeListBuilderTest extends SysuiTestCase { ); // THEN each filtered notif records the filter that did it - assertEquals(filter1, mEntrySet.get(1).mExcludingFilter); - assertEquals(filter2, mEntrySet.get(2).mExcludingFilter); + assertEquals(filter1, mEntrySet.get(1).getExcludingFilter()); + assertEquals(filter2, mEntrySet.get(2).getExcludingFilter()); } @Test @@ -541,8 +541,8 @@ public class ShadeListBuilderTest extends SysuiTestCase { ); // THEN each promoted notif records the promoter that did it - assertEquals(promoter, mEntrySet.get(2).mNotifPromoter); - assertEquals(promoter, mEntrySet.get(3).mNotifPromoter); + assertEquals(promoter, mEntrySet.get(2).getNotifPromoter()); + assertEquals(promoter, mEntrySet.get(3).getNotifPromoter()); } @Test @@ -572,8 +572,8 @@ public class ShadeListBuilderTest extends SysuiTestCase { verify(promoter2).shouldPromoteToTopLevel(mEntrySet.get(3)); // THEN each promoter is recorded on each notif it promoted - assertEquals(promoter1, mEntrySet.get(2).mNotifPromoter); - assertEquals(promoter2, mEntrySet.get(3).mNotifPromoter); + assertEquals(promoter1, mEntrySet.get(2).getNotifPromoter()); + assertEquals(promoter2, mEntrySet.get(3).getNotifPromoter()); } @Test @@ -650,34 +650,34 @@ public class ShadeListBuilderTest extends SysuiTestCase { verify(pkg5Section).isInSection(mEntrySet.get(9)); // THEN the correct section is assigned for entries in pkg1Section - assertEquals(pkg1Section, mEntrySet.get(2).mNotifSection); + assertEquals(pkg1Section, mEntrySet.get(2).getNotifSection()); assertEquals(0, mEntrySet.get(2).getSection()); - assertEquals(pkg1Section, mEntrySet.get(7).mNotifSection); + assertEquals(pkg1Section, mEntrySet.get(7).getNotifSection()); assertEquals(0, mEntrySet.get(7).getSection()); // THEN the correct section is assigned for entries in pkg2Section - assertEquals(pkg2Section, mEntrySet.get(1).mNotifSection); + assertEquals(pkg2Section, mEntrySet.get(1).getNotifSection()); assertEquals(1, mEntrySet.get(1).getSection()); - assertEquals(pkg2Section, mEntrySet.get(8).mNotifSection); + assertEquals(pkg2Section, mEntrySet.get(8).getNotifSection()); assertEquals(1, mEntrySet.get(8).getSection()); - assertEquals(pkg2Section, mBuiltList.get(3).mNotifSection); + assertEquals(pkg2Section, mBuiltList.get(3).getNotifSection()); assertEquals(1, mBuiltList.get(3).getSection()); // THEN no section was assigned to entries in pkg4Section (since they were filtered) - assertEquals(null, mEntrySet.get(0).mNotifSection); + assertEquals(null, mEntrySet.get(0).getNotifSection()); assertEquals(-1, mEntrySet.get(0).getSection()); - assertEquals(null, mEntrySet.get(10).mNotifSection); + assertEquals(null, mEntrySet.get(10).getNotifSection()); assertEquals(-1, mEntrySet.get(10).getSection()); // THEN the correct section is assigned for entries in pkg5Section - assertEquals(pkg5Section, mEntrySet.get(9).mNotifSection); + assertEquals(pkg5Section, mEntrySet.get(9).getNotifSection()); assertEquals(3, mEntrySet.get(9).getSection()); // THEN the children entries are assigned the same section as its parent - assertEquals(mBuiltList.get(3).mNotifSection, child(5).entry.mNotifSection); + assertEquals(mBuiltList.get(3).getNotifSection(), child(5).entry.getNotifSection()); assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection()); - assertEquals(mBuiltList.get(3).mNotifSection, child(6).entry.mNotifSection); + assertEquals(mBuiltList.get(3).getNotifSection(), child(6).entry.getNotifSection()); assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection()); } @@ -700,7 +700,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { // THEN the entry that didn't have an explicit section gets assigned the DefaultSection assertEquals(1, notif(0).entry.getSection()); - assertNotNull(notif(0).entry.mNotifSection); + assertNotNull(notif(0).entry.getNotifSection()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java index bdd7a2e06428..a5d8a84e3e08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java @@ -131,6 +131,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { @Mock private ActivatableNotificationViewController mActivatableNotificationViewController; @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder; + @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier; private StatusBarNotification mSbn; private NotificationListenerService.RankingMap mRankingMap; @@ -239,7 +240,8 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { mGutsManager, true, null, - mFalsingManager + mFalsingManager, + mPeopleNotificationIdentifier )); when(mNotificationRowComponentBuilder.activatableNotificationView(any())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 5ad88c941600..462da935e0c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -72,6 +72,7 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.phone.StatusBar; @@ -118,6 +119,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private INotificationManager mINotificationManager; @Mock private LauncherApps mLauncherApps; @Mock private ShortcutManager mShortcutManager; + @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier; @Before public void setUp() { @@ -465,7 +467,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { } private NotificationMenuRowPlugin.MenuItem createTestMenuItem(ExpandableNotificationRow row) { - NotificationMenuRowPlugin menuRow = new NotificationMenuRow(mContext); + NotificationMenuRowPlugin menuRow = + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); menuRow.createMenu(row, row.getEntry().getSbn()); NotificationMenuRowPlugin.MenuItem menuItem = menuRow.getLongpressMenuItem(mContext); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java index b33d26f3f07b..99e8c7e2df76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java @@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.utils.leaks.LeakCheckedTest; import org.junit.After; @@ -54,11 +55,13 @@ import org.mockito.Mockito; public class NotificationMenuRowTest extends LeakCheckedTest { private ExpandableNotificationRow mRow; + private PeopleNotificationIdentifier mPeopleNotificationIdentifier; @Before public void setup() { injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); mRow = mock(ExpandableNotificationRow.class); + mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class); NotificationEntry entry = new NotificationEntryBuilder().build(); when(mRow.getEntry()).thenReturn(entry); } @@ -71,7 +74,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testAttachDetach() { - NotificationMenuRowPlugin row = new NotificationMenuRow(mContext); + NotificationMenuRowPlugin row = + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); row.createMenu(mRow, null); ViewUtils.attachView(row.getMenuView()); TestableLooper.get(this).processAllMessages(); @@ -81,7 +85,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testRecreateMenu() { - NotificationMenuRowPlugin row = new NotificationMenuRow(mContext); + NotificationMenuRowPlugin row = + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); row.createMenu(mRow, null); assertTrue(row.getMenuView() != null); row.createMenu(mRow, null); @@ -90,7 +95,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testResetUncreatedMenu() { - NotificationMenuRowPlugin row = new NotificationMenuRow(mContext); + NotificationMenuRowPlugin row = + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); row.resetMenu(); } @@ -99,7 +105,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { public void testNoAppOpsInSlowSwipe() { Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0); - NotificationMenuRow row = new NotificationMenuRow(mContext); + NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); row.createMenu(mRow, null); ViewGroup container = (ViewGroup) row.getMenuView(); @@ -111,7 +117,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { public void testNoSnoozeInSlowSwipe() { Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0); - NotificationMenuRow row = new NotificationMenuRow(mContext); + NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); row.createMenu(mRow, null); ViewGroup container = (ViewGroup) row.getMenuView(); @@ -123,7 +129,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { public void testSnoozeInSlowSwipe() { Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 1); - NotificationMenuRow row = new NotificationMenuRow(mContext); + NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); row.createMenu(mRow, null); ViewGroup container = (ViewGroup) row.getMenuView(); @@ -133,7 +139,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testIsSnappedAndOnSameSide() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); when(row.isMenuVisible()).thenReturn(true); when(row.isMenuSnapped()).thenReturn(true); @@ -165,7 +172,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testGetMenuSnapTarget() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); when(row.isMenuOnLeft()).thenReturn(true); doReturn(30).when(row).getSpaceForMenu(); @@ -179,7 +187,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testIsSwipedEnoughToShowMenu() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); when(row.isMenuVisible()).thenReturn(true); when(row.isMenuOnLeft()).thenReturn(true); doReturn(40f).when(row).getMinimumSwipeDistance(); @@ -205,7 +214,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testIsWithinSnapMenuThreshold() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); doReturn(30f).when(row).getSnapBackThreshold(); doReturn(50f).when(row).getDismissThreshold(); @@ -238,7 +248,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testShouldSnapBack() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); doReturn(40f).when(row).getSnapBackThreshold(); when(row.isMenuVisible()).thenReturn(false); when(row.isMenuOnLeft()).thenReturn(true); @@ -259,7 +270,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testCanBeDismissed() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); ExpandableNotificationRow parent = mock(ExpandableNotificationRow.class); when(row.getParent()).thenReturn(parent); @@ -274,7 +286,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testIsTowardsMenu() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); when(row.isMenuVisible()).thenReturn(true); when(row.isMenuOnLeft()).thenReturn(true); @@ -294,7 +307,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void onSnapBack() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); NotificationMenuRowPlugin.OnMenuEventListener listener = mock(NotificationMenuRowPlugin .OnMenuEventListener.class); row.setMenuClickListener(listener); @@ -315,7 +329,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testOnSnap() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); when(row.isMenuOnLeft()).thenReturn(true); NotificationMenuRowPlugin.OnMenuEventListener listener = mock(NotificationMenuRowPlugin .OnMenuEventListener.class); @@ -335,7 +350,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testOnDismiss() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); doNothing().when(row).cancelDrag(); row.onSnapOpen(); @@ -351,7 +367,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testOnDown() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); doNothing().when(row).beginDrag(); row.onTouchStart(); @@ -361,7 +378,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testOnUp() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); row.onTouchStart(); assertTrue("before onTouchEnd, isUserTouching is true", row.isUserTouching()); @@ -373,7 +391,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testIsMenuVisible() { - NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); + NotificationMenuRow row = Mockito.spy( + new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); row.setMenuAlpha(0); assertFalse("when alpha is 0, menu is not visible", row.isMenuVisible()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 2134a3d5af92..0e67feb1d4ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -57,6 +57,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.icon.IconBuilder; import com.android.systemui.statusbar.notification.icon.IconManager; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; @@ -100,6 +101,7 @@ public class NotificationTestHelper { private final RowContentBindStage mBindStage; private final IconManager mIconManager; private StatusBarStateController mStatusBarStateController; + private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; public NotificationTestHelper(Context context, TestableDependency dependency) { mContext = context; @@ -138,6 +140,7 @@ public class NotificationTestHelper { ArgumentCaptor.forClass(NotifCollectionListener.class); verify(collection).addCollectionListener(collectionListenerCaptor.capture()); mBindPipelineEntryListener = collectionListenerCaptor.getValue(); + mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class); } /** @@ -407,7 +410,8 @@ public class NotificationTestHelper { mock(NotificationMediaManager.class), mock(ExpandableNotificationRow.OnAppOpsClickListener.class), mock(FalsingManager.class), - mStatusBarStateController); + mStatusBarStateController, + mPeopleNotificationIdentifier); row.setAboveShelfChangedListener(aboveShelf -> { }); mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); inflateAndWait(entry, mBindStage); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index b5f57b64cf43..962d77366875 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -39,6 +39,7 @@ import android.content.Intent; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.Handler; import android.provider.Settings; @@ -101,6 +102,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected NetworkRegistrationInfo mFakeRegInfo; protected ConnectivityManager mMockCm; protected WifiManager mMockWm; + protected NetworkScoreManager mMockNsm; protected SubscriptionManager mMockSm; protected TelephonyManager mMockTm; protected BroadcastDispatcher mMockBd; @@ -148,6 +150,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mMockSm = mock(SubscriptionManager.class); mMockCm = mock(ConnectivityManager.class); mMockBd = mock(BroadcastDispatcher.class); + mMockNsm = mock(NetworkScoreManager.class); mMockSubDefaults = mock(SubscriptionDefaults.class); mNetCapabilities = new NetworkCapabilities(); when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true); @@ -196,8 +199,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { return null; }).when(mMockProvisionController).addCallback(any()); - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, - mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, + mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mMockProvisionController, mMockBd); setupNetworkController(); @@ -244,18 +247,17 @@ public class NetworkControllerBaseTest extends SysuiTestCase { } protected NetworkControllerImpl setUpNoMobileData() { - when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false); - NetworkControllerImpl networkControllerNoMobile - = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, + when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false); + NetworkControllerImpl networkControllerNoMobile = + new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd); - setupNetworkController(); - - return networkControllerNoMobile; + setupNetworkController(); + return networkControllerNoMobile; } // 2 Bars 3G GSM. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java index 75f261953e8e..6fffcff41a4f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java @@ -102,8 +102,8 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { public void test4gDataIcon() { // Switch to showing 4g icon and re-initialize the NetworkController. mConfig.show4gForLte = true; - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, - mConfig, Looper.getMainLooper(), mCallbackHandler, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, + mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java index b922f0600427..399b5c24431b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java @@ -58,8 +58,8 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { // Turn off mobile network support. when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, - mConfig, Looper.getMainLooper(), mCallbackHandler, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, + mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd); setupNetworkController(); @@ -120,8 +120,8 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { // Turn off mobile network support. when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, - mConfig, Looper.getMainLooper(), mCallbackHandler, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, + mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd); setupNetworkController(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java index bc3a5b193c8a..65fbe79b5e9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java @@ -176,7 +176,7 @@ public class ToastUITest extends SysuiTestCase { mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1); - verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1); + verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1); } @Test @@ -218,7 +218,7 @@ public class ToastUITest extends SysuiTestCase { mToastUI.showToast(PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, null); verify(mWindowManager).removeViewImmediate(view); - verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1); + verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1); verify(mCallback).onToastHidden(); } 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/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index f6cdaebc333a..30e765f3d602 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1331,10 +1331,10 @@ public class AudioService extends IAudioService.Stub private void updateDefaultVolumes() { for (int stream = 0; stream < mStreamStates.length; stream++) { if (stream != mStreamVolumeAlias[stream]) { - AudioSystem.DEFAULT_STREAM_VOLUME[stream] = rescaleIndex( - AudioSystem.DEFAULT_STREAM_VOLUME[mStreamVolumeAlias[stream]], + AudioSystem.DEFAULT_STREAM_VOLUME[stream] = (rescaleIndex( + AudioSystem.DEFAULT_STREAM_VOLUME[mStreamVolumeAlias[stream]] * 10, mStreamVolumeAlias[stream], - stream); + stream) + 5) / 10; } } } @@ -2332,8 +2332,7 @@ public class AudioService extends IAudioService.Stub } private void enforceModifyAudioRoutingPermission() { - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING) + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission"); } @@ -4610,6 +4609,117 @@ public class AudioService extends IAudioService.Stub observeDevicesForStreams(-1); } + /** + * @see AudioManager#setDeviceVolumeBehavior(AudioDeviceAttributes, int) + * @param device the audio device to be affected + * @param deviceVolumeBehavior one of the device behaviors + */ + public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, + @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) { + // verify permissions + enforceModifyAudioRoutingPermission(); + // verify arguments + Objects.requireNonNull(device); + AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior); + if (pkgName == null) { + pkgName = ""; + } + // translate Java device type to native device type (for the devices masks for full / fixed) + final int type; + switch (device.getType()) { + case AudioDeviceInfo.TYPE_HDMI: + type = AudioSystem.DEVICE_OUT_HDMI; + break; + case AudioDeviceInfo.TYPE_HDMI_ARC: + type = AudioSystem.DEVICE_OUT_HDMI_ARC; + break; + case AudioDeviceInfo.TYPE_LINE_DIGITAL: + type = AudioSystem.DEVICE_OUT_SPDIF; + break; + case AudioDeviceInfo.TYPE_AUX_LINE: + type = AudioSystem.DEVICE_OUT_LINE; + break; + default: + // unsupported for now + throw new IllegalArgumentException("Unsupported device type " + device.getType()); + } + // update device masks based on volume behavior + switch (deviceVolumeBehavior) { + case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE: + mFullVolumeDevices.remove(type); + mFixedVolumeDevices.remove(type); + break; + case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED: + mFullVolumeDevices.remove(type); + mFixedVolumeDevices.add(type); + break; + case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL: + mFullVolumeDevices.add(type); + mFixedVolumeDevices.remove(type); + break; + case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: + case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: + throw new IllegalArgumentException("Absolute volume unsupported for now"); + } + // log event and caller + sDeviceLogger.log(new AudioEventLogger.StringEvent( + "Volume behavior " + deviceVolumeBehavior + + " for dev=0x" + Integer.toHexString(type) + " by pkg:" + pkgName)); + // make sure we have a volume entry for this device, and that volume is updated according + // to volume behavior + checkAddAllFixedVolumeDevices(type, "setDeviceVolumeBehavior:" + pkgName); + } + + /** + * @see AudioManager#getDeviceVolumeBehavior(AudioDeviceAttributes) + * @param device the audio output device type + * @return the volume behavior for the device + */ + public @AudioManager.DeviceVolumeBehavior int getDeviceVolumeBehavior( + @NonNull AudioDeviceAttributes device) { + // verify permissions + enforceModifyAudioRoutingPermission(); + // translate Java device type to native device type (for the devices masks for full / fixed) + final int type; + switch (device.getType()) { + case AudioDeviceInfo.TYPE_HEARING_AID: + type = AudioSystem.DEVICE_OUT_HEARING_AID; + break; + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + type = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; + break; + case AudioDeviceInfo.TYPE_HDMI: + type = AudioSystem.DEVICE_OUT_HDMI; + break; + case AudioDeviceInfo.TYPE_HDMI_ARC: + type = AudioSystem.DEVICE_OUT_HDMI_ARC; + break; + case AudioDeviceInfo.TYPE_LINE_DIGITAL: + type = AudioSystem.DEVICE_OUT_SPDIF; + break; + case AudioDeviceInfo.TYPE_AUX_LINE: + type = AudioSystem.DEVICE_OUT_LINE; + break; + default: + // unsupported for now + throw new IllegalArgumentException("Unsupported device type " + device.getType()); + } + if ((mFullVolumeDevices.contains(type))) { + return AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL; + } + if ((mFixedVolumeDevices.contains(type))) { + return AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED; + } + if ((mAbsVolumeMultiModeCaseDevices.contains(type))) { + return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE; + } + if (type == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP + && mDeviceBroker.isAvrcpAbsoluteVolumeSupported()) { + return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE; + } + return AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE; + } + /*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0; /*package*/ static final int CONNECTION_STATE_CONNECTED = 1; /** @@ -4779,7 +4889,9 @@ public class AudioService extends IAudioService.Stub } catch (IllegalArgumentException e) { // Volume Groups without attributes are not controllable through set/get volume // using attributes. Do not append them. - Log.d(TAG, "volume group " + avg.name() + " for internal policy needs"); + if (DEBUG_VOL) { + Log.d(TAG, "volume group " + avg.name() + " for internal policy needs"); + } continue; } sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg)); @@ -4800,7 +4912,9 @@ public class AudioService extends IAudioService.Stub } private void readVolumeGroupsSettings() { - Log.v(TAG, "readVolumeGroupsSettings"); + if (DEBUG_VOL) { + Log.v(TAG, "readVolumeGroupsSettings"); + } for (int i = 0; i < sVolumeGroupStates.size(); i++) { final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i); vgs.readSettings(); @@ -4810,7 +4924,9 @@ public class AudioService extends IAudioService.Stub // Called upon crash of AudioServer private void restoreVolumeGroups() { - Log.v(TAG, "restoreVolumeGroups"); + if (DEBUG_VOL) { + Log.v(TAG, "restoreVolumeGroups"); + } for (int i = 0; i < sVolumeGroupStates.size(); i++) { final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i); vgs.applyAllVolumes(); @@ -4846,7 +4962,9 @@ public class AudioService extends IAudioService.Stub private VolumeGroupState(AudioVolumeGroup avg) { mAudioVolumeGroup = avg; - Log.v(TAG, "VolumeGroupState for " + avg.toString()); + if (DEBUG_VOL) { + Log.v(TAG, "VolumeGroupState for " + avg.toString()); + } for (final AudioAttributes aa : avg.getAudioAttributes()) { if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) { mAudioAttributes = aa; @@ -4949,16 +5067,21 @@ public class AudioService extends IAudioService.Stub final int device = mIndexMap.keyAt(i); if (device != AudioSystem.DEVICE_OUT_DEFAULT) { index = mIndexMap.valueAt(i); - Log.v(TAG, "applyAllVolumes: restore index " + index + " for group " - + mAudioVolumeGroup.name() + " and device " - + AudioSystem.getOutputDeviceName(device)); + if (DEBUG_VOL) { + Log.v(TAG, "applyAllVolumes: restore index " + index + " for group " + + mAudioVolumeGroup.name() + " and device " + + AudioSystem.getOutputDeviceName(device)); + } setVolumeIndexInt(index, device, 0 /*flags*/); } } // apply default volume last: by convention , default device volume will be used + // by audio policy manager if no explicit volume is present for a given device type index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT); - Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group " - + mAudioVolumeGroup.name()); + if (DEBUG_VOL) { + Log.v(TAG, "applyAllVolumes: restore default device index " + index + + " for group " + mAudioVolumeGroup.name()); + } setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/); } } @@ -4967,9 +5090,11 @@ public class AudioService extends IAudioService.Stub if (mUseFixedVolume) { return; } - Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group " - + mAudioVolumeGroup.name() + " and device " - + AudioSystem.getOutputDeviceName(device)); + if (DEBUG_VOL) { + Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group " + + mAudioVolumeGroup.name() + " and device " + + AudioSystem.getOutputDeviceName(device)); + } boolean success = Settings.System.putIntForUser(mContentResolver, getSettingNameForDevice(device), getIndex(device), @@ -4999,12 +5124,12 @@ public class AudioService extends IAudioService.Stub index = Settings.System.getIntForUser( mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); if (index == -1) { - Log.e(TAG, "readSettings: No index stored for group " - + mAudioVolumeGroup.name() + ", device " + name); continue; } - Log.v(TAG, "readSettings: found stored index " + getValidIndex(index) - + " for group " + mAudioVolumeGroup.name() + ", device: " + name); + if (DEBUG_VOL) { + Log.v(TAG, "readSettings: found stored index " + getValidIndex(index) + + " for group " + mAudioVolumeGroup.name() + ", device: " + name); + } mIndexMap.put(device, getValidIndex(index)); } } 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 b28350d51e9e..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; @@ -1150,35 +1146,24 @@ class MediaRouter2ServiceImpl { 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()) { @@ -1186,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); } } diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java index 2ed6e16bd989..bfc76df90cf3 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java @@ -250,26 +250,36 @@ public class NotificationHistoryDatabase { for (int i = mHistoryFiles.size() - 1; i >= 0; i--) { final AtomicFile currentOldestFile = mHistoryFiles.get(i); - final long creationTime = Long.parseLong(currentOldestFile.getBaseFile().getName()); - if (DEBUG) { - Slog.d(TAG, "File " + currentOldestFile.getBaseFile().getName() - + " created on " + creationTime); - } - if (creationTime <= retentionBoundary.getTimeInMillis()) { + try { + final long creationTime = Long.parseLong( + currentOldestFile.getBaseFile().getName()); if (DEBUG) { - Slog.d(TAG, "Removed " + currentOldestFile.getBaseFile().getName()); + Slog.d(TAG, "File " + currentOldestFile.getBaseFile().getName() + + " created on " + creationTime); } - currentOldestFile.delete(); - // TODO: delete all relevant bitmaps, once they exist - mHistoryFiles.removeLast(); - } else { - // all remaining files are newer than the cut off; schedule jobs to delete - scheduleDeletion(currentOldestFile.getBaseFile(), creationTime, retentionDays); + if (creationTime <= retentionBoundary.getTimeInMillis()) { + deleteFile(currentOldestFile); + } else { + // all remaining files are newer than the cut off; schedule jobs to delete + scheduleDeletion( + currentOldestFile.getBaseFile(), creationTime, retentionDays); + } + } catch (NumberFormatException e) { + deleteFile(currentOldestFile); } } } } + private void deleteFile(AtomicFile file) { + if (DEBUG) { + Slog.d(TAG, "Removed " + file.getBaseFile().getName()); + } + file.delete(); + // TODO: delete all relevant bitmaps, once they exist + mHistoryFiles.removeLast(); + } + private void scheduleDeletion(File file, long creationTime, int retentionDays) { final long deletionTime = creationTime + (retentionDays * HISTORY_RETENTION_MS); scheduleDeletion(file, deletionTime); 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/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/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 682e991bdad2..7803c7370dac 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2802,6 +2802,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { false /* includingParents */); } WindowContainerTransaction wct = new WindowContainerTransaction(); + // Clear out current windowing mode before reparenting to split taks. + wct.setWindowingMode( + task.getStack().mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_UNDEFINED); wct.reparent(task.getStack().mRemoteToken.toWindowContainerToken(), primarySplitTask.mRemoteToken.toWindowContainerToken(), toTop); mWindowOrganizerController.applyTransaction(wct); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ce7e79714c39..55b7be779690 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -197,6 +197,7 @@ import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerPolicyConstants.PointerEventListener; +import android.window.ITaskOrganizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -822,8 +823,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (w.mHasSurface && isDisplayed) { final int type = w.mAttrs.type; - if (type == TYPE_SYSTEM_DIALOG || type == TYPE_SYSTEM_ERROR - || mWmService.mPolicy.isKeyguardShowing()) { + if (type == TYPE_SYSTEM_DIALOG + || type == TYPE_SYSTEM_ERROR + || (type == TYPE_NOTIFICATION_SHADE + && mWmService.mPolicy.isKeyguardShowing())) { mTmpApplySurfaceChangesTransactionState.syswin = true; } if (mTmpApplySurfaceChangesTransactionState.preferredRefreshRate == 0 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/Task.java b/services/core/java/com/android/server/wm/Task.java index 0151b82d2f02..7c6343c87232 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4068,21 +4068,7 @@ class Task extends WindowContainer<WindowContainer> { @Override boolean isOrganized() { - final Task rootTask = getRootTask(); - if (rootTask.mTaskOrganizer == null) { - // You are obviously not organized... - return false; - } - if (rootTask == this) { - // Root tasks can be organized. - return true; - } - if (rootTask.mCreatedByOrganizer && getParent() == rootTask) { - // Direct children of tasks added by the organizer can the organized. - return true; - } - - return false; + return mTaskOrganizer != null; } @Override @@ -4137,6 +4123,7 @@ class Task extends WindowContainer<WindowContainer> { } } + @VisibleForTesting boolean setTaskOrganizer(ITaskOrganizer organizer) { if (mTaskOrganizer == organizer) { return false; diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 9356ec200f20..77ef01134292 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -732,11 +732,11 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { /** * Returns an existing stack compatible with the windowing mode and activity type or creates one * if a compatible stack doesn't exist. - * @see #getOrCreateStack(int, int, boolean, Intent, Task, boolean) + * @see #getOrCreateStack(int, int, boolean, Intent, Task) */ ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop) { return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */, - null /* candidateTask */, false /* createdByOrganizer */); + null /* candidateTask */); } /** @@ -748,7 +748,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { * @see #createStack(int, int, boolean) */ ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop, - Intent intent, Task candidateTask, boolean createdByOrganizer) { + Intent intent, Task candidateTask) { if (!alwaysCreateStack(windowingMode, activityType)) { ActivityStack stack = getStack(windowingMode, activityType); if (stack != null) { @@ -757,13 +757,13 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } else if (candidateTask != null) { final ActivityStack stack = (ActivityStack) candidateTask; final int position = onTop ? POSITION_TOP : POSITION_BOTTOM; - if (isSplitScreenModeActivated()) { - final Task splitRootSecondary = getTask(t -> t.mCreatedByOrganizer && t.isRootTask() - && t.inSplitScreenSecondaryWindowingMode()); + Task launchRootTask = updateLaunchRootTask(windowingMode); + + if (launchRootTask != null) { if (stack.getParent() == null) { - splitRootSecondary.addChild(stack, position); - } else if (stack.getParent() != splitRootSecondary) { - stack.reparent(splitRootSecondary, position); + launchRootTask.addChild(stack, position); + } else if (stack.getParent() != launchRootTask) { + stack.reparent(launchRootTask, position); } } else if (stack.getDisplay() != mDisplayContent || !stack.isRootTask()) { if (stack.getParent() == null) { @@ -779,7 +779,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { return stack; } return createStack(windowingMode, activityType, onTop, null /*info*/, intent, - createdByOrganizer); + false /* createdByOrganizer */); } /** @@ -798,7 +798,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { // it's display's windowing mode. windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType); return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */, - candidateTask, false /* createdByOrganizer */); + candidateTask); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 872f2543edb8..b641e4c391ce 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -277,9 +277,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return null; } - final Task task = display.mTaskContainers.getOrCreateStack(windowingMode, - ACTIVITY_TYPE_UNDEFINED, false /* onTop */, new Intent(), - null /* candidateTask */, true /* createdByOrganizer */); + final Task task = display.mTaskContainers.createStack(windowingMode, + ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(), + true /* createdByOrganizer */); RunningTaskInfo out = task.getTaskInfo(); mLastSentTaskInfos.put(task, out); return out; 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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 1544ff127121..eed39e182a33 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -9119,6 +9119,31 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent( + @NonNull UserHandle userHandle) { + if (!mHasFeature) { + return null; + } + synchronized (getLockObject()) { + final String supervisor = mContext.getResources().getString( + com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent); + if (supervisor == null) { + return null; + } + final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor); + final ComponentName doComponent = mOwners.getDeviceOwnerComponent(); + final ComponentName poComponent = + mOwners.getProfileOwnerComponent(userHandle.getIdentifier()); + if (supervisorComponent.equals(doComponent) || supervisorComponent.equals( + poComponent)) { + return supervisorComponent; + } else { + return null; + } + } + } + + @Override public String getProfileOwnerName(int userHandle) { if (!mHasFeature) { return null; @@ -11488,6 +11513,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new SecurityException( "User " + userId + " is not allowed to call setSecondaryLockscreenEnabled"); } + // Only the default supervision app can use this API. + final String supervisor = mContext.getResources().getString( + com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent); + if (supervisor == null) { + throw new SecurityException("Unable to set secondary lockscreen setting, no " + + "default supervision component defined"); + } + final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor); + if (!who.equals(supervisorComponent)) { + throw new SecurityException( + "Admin " + who + " is not the default supervision component"); + } } @Override 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/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/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index baf551e756e8..fe47cea6b693 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -4274,6 +4274,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Profile owner can set enabled state. setAsProfileOwner(admin1); + when(mServiceContext.resources + .getString(R.string.config_defaultSupervisionProfileOwnerComponent)) + .thenReturn(admin1.flattenToString()); dpm.setSecondaryLockscreenEnabled(admin1, true); assertTrue(dpm.isSecondaryLockscreenEnabled(UserHandle.of( DpmMockContext.CALLER_USER_HANDLE))); @@ -4297,6 +4300,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Device owners can set enabled state. setupDeviceOwner(); + when(mServiceContext.resources + .getString(R.string.config_defaultSupervisionProfileOwnerComponent)) + .thenReturn(admin1.flattenToString()); dpm.setSecondaryLockscreenEnabled(admin1, true); assertTrue(dpm.isSecondaryLockscreenEnabled(UserHandle.of(UserHandle.USER_SYSTEM))); } @@ -4309,12 +4315,39 @@ public class DevicePolicyManagerTest extends DpmTestBase { DpmMockContext.CALLER_USER_HANDLE))); // Non-DO/PO cannot set enabled state. + when(mServiceContext.resources + .getString(R.string.config_defaultSupervisionProfileOwnerComponent)) + .thenReturn(admin1.flattenToString()); assertExpectException(SecurityException.class, /* messageRegex= */ null, () -> dpm.setSecondaryLockscreenEnabled(admin1, true)); assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of( DpmMockContext.CALLER_USER_HANDLE))); } + public void testSecondaryLockscreen_nonSupervisionApp() throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + + // Initial state is disabled. + assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of( + DpmMockContext.CALLER_USER_HANDLE))); + + // Caller is Profile Owner, but no supervision app is configured. + setAsProfileOwner(admin1); + assertExpectException(SecurityException.class, "no default supervision component defined", + () -> dpm.setSecondaryLockscreenEnabled(admin1, true)); + assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of( + DpmMockContext.CALLER_USER_HANDLE))); + + // Caller is Profile Owner, but is not the default configured supervision app. + when(mServiceContext.resources + .getString(R.string.config_defaultSupervisionProfileOwnerComponent)) + .thenReturn(admin2.flattenToString()); + assertExpectException(SecurityException.class, "is not the default supervision component", + () -> dpm.setSecondaryLockscreenEnabled(admin1, true)); + assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of( + DpmMockContext.CALLER_USER_HANDLE))); + } + public void testIsDeviceManaged() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); 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/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 19bf9b673f8b..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.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,35 +114,31 @@ open class AndroidPackageParsingTestBase { lateinit var newPackages: List<AndroidPackage> - var failureInBeforeClass: Throwable? = null + private val thrownInSetUp = mutableListOf<Throwable>() @Suppress("ConstantConditionIf") @JvmStatic @BeforeClass fun setUpPackages() { - failureInBeforeClass = null - try { - this.oldPackages = apks.map { + this.oldPackages = apks.mapNotNull { + tryOrNull { packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) } + } - this.newPackages = apks.map { + 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") - .absolutePath - .run(Debug::dumpHprofData) - } - } catch (t: Throwable) { - // If we crash here we cause a tool failure (because we don't run any of the tests - // in the subclasses, leading to a difference between expected and actual test - // result counts). - failureInBeforeClass = t + if (DUMP_HPROF_TO_EXTERNAL) { + System.gc() + Environment.getExternalStorageDirectory() + .resolve( + "${AndroidPackageParsingTestBase::class.java.simpleName}.hprof") + .absolutePath + .run(Debug::dumpHprofData) } } @@ -146,13 +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 + } } - @org.junit.Before + @After fun verifySetUpPackages() { - failureInBeforeClass?.let { - throw AssertionError("setUpPackages failed:", it) - } + 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/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java index 3991d8d4a47c..80b474ff7128 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java @@ -146,7 +146,29 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { assertThat(mDataBase.mHistoryFiles).containsExactlyElementsIn(expectedFiles); verify(mAlarmManager, times(6)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any()); + } + + @Test + public void testPrune_badFileName() { + GregorianCalendar cal = new GregorianCalendar(); + cal.setTimeInMillis(10); + int retainDays = 1; + + List<AtomicFile> expectedFiles = new ArrayList<>(); + + // add 5 files with a creation date of "today", but the file names are bad + for (long i = cal.getTimeInMillis(); i >= 5; i--) { + File file = mock(File.class); + when(file.getName()).thenReturn(i + ".txt"); + AtomicFile af = new AtomicFile(file); + mDataBase.mHistoryFiles.addLast(af); + } + // trim everything a day+ old + cal.add(Calendar.DATE, 1 * retainDays); + mDataBase.prune(retainDays, cal.getTimeInMillis()); + + assertThat(mDataBase.mHistoryFiles).containsExactlyElementsIn(expectedFiles); } @Test 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/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java index 551e1860d9b3..5a527a219055 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java @@ -3,8 +3,10 @@ package com.android.server.notification; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import android.app.Application; import android.content.Intent; import android.net.Uri; import android.service.notification.Condition; @@ -45,7 +47,7 @@ public class ScheduleConditionProviderTest extends UiServiceTestCase { null, // ActivityThread not actually used in Service ScheduleConditionProvider.class.getName(), null, // token not needed when not talking with the activity manager - null, + mock(Application.class), null // mocked services don't talk with the activity manager ); service.onCreate(); 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/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 78e5f2d814de..327e8b344eeb 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -52,6 +52,14 @@ public class CarrierConfigManager { public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX"; /** + * Extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate whether this is a + * rebroadcast on unlock. Defaults to {@code false} if not specified. + * @hide + */ + public static final String EXTRA_REBROADCAST_ON_UNLOCK = + "android.telephony.extra.REBROADCAST_ON_UNLOCK"; + + /** * Optional extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate the * subscription index that the broadcast is for, if a valid one is available. */ diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 4e5be5c453b7..d6cdaa6d8bc0 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -11776,6 +11776,7 @@ public class TelephonyManager { * subscription, the key is {@link SubscriptionManager#getDefaultSubscriptionId}) and the value * as the list of {@link EmergencyNumber}; empty Map if this information is not available; * or throw a SecurityException if the caller does not have the permission. + * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @NonNull @@ -11823,6 +11824,7 @@ public class TelephonyManager { * @param number - the number to look up * @return {@code true} if the given number is an emergency number based on current locale, * SIM card(s), Android database, modem, network or defaults; {@code false} otherwise. + * @throws IllegalStateException if the Telephony process is not currently available. */ public boolean isEmergencyNumber(@NonNull String number) { try { @@ -11858,7 +11860,7 @@ public class TelephonyManager { * the same digits of any current emergency number based on current locale, sim, modem and * network; {@code false} if it is not; or throw an SecurityException if the caller does not * have the required permission/privileges - * + * @throws IllegalStateException if the Telephony process is not currently available. * @hide */ @SystemApi @@ -12344,6 +12346,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/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index a616c61b34f8..6c9ffe2a7fac 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -477,4 +477,12 @@ public class StagedRollbackTest { StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); assertThat(sm.isCheckpointSupported()).isTrue(); } + + @Test + public void hasMainlineModule() throws Exception { + String pkgName = getModuleMetadataPackageName(); + boolean existed = InstrumentationRegistry.getInstrumentation().getContext() + .getPackageManager().getModuleInfo(pkgName, 0) != null; + assertThat(existed).isTrue(); + } } diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 282f012dbf6f..78775be84828 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -243,6 +243,7 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { */ @Test public void testRollbackWhitelistedApp() throws Exception { + assumeTrue(hasMainlineModule()); runPhase("testRollbackWhitelistedApp_Phase1"); getDevice().reboot(); runPhase("testRollbackWhitelistedApp_Phase2"); @@ -460,4 +461,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { return false; } } + + /** + * True if this build has mainline modules installed. + */ + private boolean hasMainlineModule() throws Exception { + try { + runPhase("hasMainlineModule"); + return true; + } catch (AssertionError ignore) { + return false; + } + } } diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index b4f0daa025af..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)); @@ -946,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), |