diff options
401 files changed, 9212 insertions, 5692 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/api/test-current.txt b/api/test-current.txt index 0ca8b2dfb0d0..4eeaaf87ea0d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1513,6 +1513,10 @@ package android.media { field public static final String SAMPLE_RATE = "android.media.audiotrack.sampleRate"; } + public final class MediaCas implements java.lang.AutoCloseable { + method public void forceResourceLost(); + } + public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint { ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(int, int, int, int, @NonNull android.util.Size); ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(@NonNull android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint, @NonNull android.util.Size); 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 a3214606c32f..d3d7e1d483e8 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -56,7 +56,6 @@ cc_defaults { "src/condition/condition_util.cpp", "src/condition/ConditionWizard.cpp", "src/condition/SimpleConditionTracker.cpp", - "src/condition/StateConditionTracker.cpp", "src/config/ConfigKey.cpp", "src/config/ConfigListener.cpp", "src/config/ConfigManager.cpp", @@ -315,7 +314,6 @@ cc_test { "tests/condition/CombinationConditionTracker_test.cpp", "tests/condition/ConditionTimer_test.cpp", "tests/condition/SimpleConditionTracker_test.cpp", - "tests/condition/StateConditionTracker_test.cpp", "tests/ConfigManager_test.cpp", "tests/e2e/Alarm_e2e_test.cpp", "tests/e2e/Anomaly_count_e2e_test.cpp", @@ -337,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/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp index 4bce89fd7f9c..482d66fc7556 100644 --- a/cmds/statsd/benchmark/metric_util.cpp +++ b/cmds/statsd/benchmark/metric_util.cpp @@ -370,13 +370,6 @@ sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const Stat return processor; } -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) { - AttributionNodeInternal attribution; - attribution.set_uid(uid); - attribution.set_tag(tag); - return attribution; -} - void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) { std::sort(events->begin(), events->end(), [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) { diff --git a/cmds/statsd/benchmark/metric_util.h b/cmds/statsd/benchmark/metric_util.h index 6199fa9dc7a1..c5fcf7c27440 100644 --- a/cmds/statsd/benchmark/metric_util.h +++ b/cmds/statsd/benchmark/metric_util.h @@ -120,9 +120,6 @@ std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs, const vector<string>& attributionTags, const string& name); -// Helper function to create an AttributionNodeInternal proto. -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag); - // Create a statsd log event processor upon the start time in seconds, config and key. sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, const ConfigKey& key); 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/condition/StateConditionTracker.cpp b/cmds/statsd/src/condition/StateConditionTracker.cpp deleted file mode 100644 index d19a1761ac00..000000000000 --- a/cmds/statsd/src/condition/StateConditionTracker.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "StateConditionTracker.h" -#include "guardrail/StatsdStats.h" - -namespace android { -namespace os { -namespace statsd { - -using std::vector; - -StateConditionTracker::StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index, - const SimplePredicate& simplePredicate, - const unordered_map<int64_t, int>& trackerNameIndexMap, - const vector<Matcher> primaryKeys) - : ConditionTracker(id, index), mConfigKey(key), mPrimaryKeys(primaryKeys) { - if (simplePredicate.has_start()) { - auto pair = trackerNameIndexMap.find(simplePredicate.start()); - if (pair == trackerNameIndexMap.end()) { - ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start()); - return; - } - mStartLogMatcherIndex = pair->second; - mTrackerIndex.insert(mStartLogMatcherIndex); - } else { - ALOGW("Condition %lld must have a start matcher", (long long)id); - return; - } - - if (simplePredicate.has_dimensions()) { - translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions); - if (mOutputDimensions.size() > 0) { - mSliced = true; - mDimensionTag = mOutputDimensions[0].mMatcher.getTag(); - } else { - ALOGW("Condition %lld has invalid dimensions", (long long)id); - return; - } - } else { - ALOGW("Condition %lld being a state tracker, but has no dimension", (long long)id); - return; - } - - if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) { - mInitialValue = ConditionState::kFalse; - } else { - mInitialValue = ConditionState::kUnknown; - } - - mNonSlicedConditionState = mInitialValue; - mInitialized = true; -} - -StateConditionTracker::~StateConditionTracker() { - VLOG("~StateConditionTracker()"); -} - -bool StateConditionTracker::init(const vector<Predicate>& allConditionConfig, - const vector<sp<ConditionTracker>>& allConditionTrackers, - const unordered_map<int64_t, int>& conditionIdIndexMap, - vector<bool>& stack) { - return mInitialized; -} - -void StateConditionTracker::dumpState() { - VLOG("StateConditionTracker %lld DUMP:", (long long)mConditionId); - for (const auto& value : mSlicedState) { - VLOG("\t%s -> %s", value.first.toString().c_str(), value.second.toString().c_str()); - } - VLOG("Last Changed to True: "); - for (const auto& value : mLastChangedToTrueDimensions) { - VLOG("%s", value.toString().c_str()); - } - VLOG("Last Changed to False: "); - for (const auto& value : mLastChangedToFalseDimensions) { - VLOG("%s", value.toString().c_str()); - } -} - -bool StateConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) { - if (mSlicedState.find(newKey) != mSlicedState.end()) { - // if the condition is not sliced or the key is not new, we are good! - return false; - } - // 1. Report the tuple count if the tuple count > soft limit - if (mSlicedState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = mSlicedState.size() + 1; - StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount); - // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. - if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("Predicate %lld dropping data for dimension key %s", - (long long)mConditionId, newKey.toString().c_str()); - return true; - } - } - return false; -} - -void StateConditionTracker::evaluateCondition(const LogEvent& event, - const vector<MatchingState>& eventMatcherValues, - const vector<sp<ConditionTracker>>& mAllConditions, - vector<ConditionState>& conditionCache, - vector<bool>& conditionChangedCache) { - mLastChangedToTrueDimensions.clear(); - mLastChangedToFalseDimensions.clear(); - if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { - // it has been evaluated. - VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]); - return; - } - - if (mStartLogMatcherIndex >= 0 && - eventMatcherValues[mStartLogMatcherIndex] != MatchingState::kMatched) { - conditionCache[mIndex] = - mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionChangedCache[mIndex] = false; - return; - } - - VLOG("StateConditionTracker evaluate event %s", event.ToString().c_str()); - - // Primary key can exclusive fields must be simple fields. so there won't be more than - // one keys matched. - HashableDimensionKey primaryKey; - HashableDimensionKey state; - if ((mPrimaryKeys.size() > 0 && !filterValues(mPrimaryKeys, event.getValues(), &primaryKey)) || - !filterValues(mOutputDimensions, event.getValues(), &state)) { - ALOGE("Failed to filter fields in the event?? panic now!"); - conditionCache[mIndex] = - mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionChangedCache[mIndex] = false; - return; - } - hitGuardRail(primaryKey); - - VLOG("StateConditionTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str()); - - auto it = mSlicedState.find(primaryKey); - if (it == mSlicedState.end()) { - mSlicedState[primaryKey] = state; - conditionCache[mIndex] = ConditionState::kTrue; - mLastChangedToTrueDimensions.insert(state); - conditionChangedCache[mIndex] = true; - } else if (!(it->second == state)) { - mLastChangedToFalseDimensions.insert(it->second); - mLastChangedToTrueDimensions.insert(state); - mSlicedState[primaryKey] = state; - conditionCache[mIndex] = ConditionState::kTrue; - conditionChangedCache[mIndex] = true; - } else { - conditionCache[mIndex] = ConditionState::kTrue; - conditionChangedCache[mIndex] = false; - } - - if (DEBUG) { - dumpState(); - } - return; -} - -void StateConditionTracker::isConditionMet( - const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, - const bool isPartialLink, - vector<ConditionState>& conditionCache) const { - if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { - // it has been evaluated. - VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]); - return; - } - - const auto pair = conditionParameters.find(mConditionId); - if (pair == conditionParameters.end()) { - if (mSlicedState.size() > 0) { - conditionCache[mIndex] = ConditionState::kTrue; - } else { - conditionCache[mIndex] = ConditionState::kUnknown; - } - return; - } - - const auto& primaryKey = pair->second; - conditionCache[mIndex] = mInitialValue; - auto it = mSlicedState.find(primaryKey); - if (it != mSlicedState.end()) { - conditionCache[mIndex] = ConditionState::kTrue; - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/condition/StateConditionTracker.h b/cmds/statsd/src/condition/StateConditionTracker.h deleted file mode 100644 index 0efe1fb3fcb2..000000000000 --- a/cmds/statsd/src/condition/StateConditionTracker.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include <gtest/gtest_prod.h> -#include "ConditionTracker.h" -#include "config/ConfigKey.h" -#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" -#include "stats_util.h" - -namespace android { -namespace os { -namespace statsd { - -class StateConditionTracker : public virtual ConditionTracker { -public: - StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index, - const SimplePredicate& simplePredicate, - const std::unordered_map<int64_t, int>& trackerNameIndexMap, - const vector<Matcher> primaryKeys); - - ~StateConditionTracker(); - - bool init(const std::vector<Predicate>& allConditionConfig, - const std::vector<sp<ConditionTracker>>& allConditionTrackers, - const std::unordered_map<int64_t, int>& conditionIdIndexMap, - std::vector<bool>& stack) override; - - void evaluateCondition(const LogEvent& event, - const std::vector<MatchingState>& eventMatcherValues, - const std::vector<sp<ConditionTracker>>& mAllConditions, - std::vector<ConditionState>& conditionCache, - std::vector<bool>& changedCache) override; - - /** - * Note: dimensionFields will be ignored in StateConditionTracker, because we demand metrics - * must take the entire dimension fields from StateConditionTracker. This is to make implementation - * simple and efficient. - * - * For example: wakelock duration by uid process states: - * dimension in condition must be {uid, process state}. - */ - void isConditionMet(const ConditionKey& conditionParameters, - const std::vector<sp<ConditionTracker>>& allConditions, - const bool isPartialLink, - std::vector<ConditionState>& conditionCache) const override; - - virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions( - const std::vector<sp<ConditionTracker>>& allConditions) const { - return &mLastChangedToTrueDimensions; - } - - virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions( - const std::vector<sp<ConditionTracker>>& allConditions) const { - return &mLastChangedToFalseDimensions; - } - - bool IsChangedDimensionTrackable() const override { return true; } - - bool IsSimpleCondition() const override { return true; } - - bool equalOutputDimensions( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensions) const override { - return equalDimensions(mOutputDimensions, dimensions); - } - - void getTrueSlicedDimensions( - const std::vector<sp<ConditionTracker>>& allConditions, - std::set<HashableDimensionKey>* dimensions) const override { - for (const auto& itr : mSlicedState) { - dimensions->insert(itr.second); - } - } - -private: - const ConfigKey mConfigKey; - - // The index of the LogEventMatcher which defines the start. - int mStartLogMatcherIndex; - - std::set<HashableDimensionKey> mLastChangedToTrueDimensions; - std::set<HashableDimensionKey> mLastChangedToFalseDimensions; - - std::vector<Matcher> mOutputDimensions; - std::vector<Matcher> mPrimaryKeys; - - ConditionState mInitialValue; - - int mDimensionTag; - - void dumpState(); - - bool hitGuardRail(const HashableDimensionKey& newKey); - - // maps from [primary_key] to [primary_key, exclusive_state]. - std::unordered_map<HashableDimensionKey, HashableDimensionKey> mSlicedState; - - FRIEND_TEST(StateConditionTrackerTest, TestStateChange); -}; - -} // namespace statsd -} // namespace os -} // namespace android 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/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index a6ae3899e4de..0ec11f926815 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -81,74 +81,6 @@ LogEvent::LogEvent(int32_t uid, int32_t pid) mLogPid(pid) { } -LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs) { - mLogdTimestampNs = wallClockTimestampNs; - mElapsedTimestampNs = elapsedTimestampNs; - mTagId = tagId; - mLogUid = 0; - mContext = create_android_logger(1937006964); // the event tag shared by all stats logs - if (mContext) { - android_log_write_int64(mContext, elapsedTimestampNs); - android_log_write_int32(mContext, tagId); - } -} - -LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map) { - mLogdTimestampNs = wallClockTimestampNs; - mElapsedTimestampNs = elapsedTimestampNs; - mTagId = util::KEY_VALUE_PAIRS_ATOM; - mLogUid = uid; - - int pos[] = {1, 1, 1}; - - mValues.push_back(FieldValue(Field(mTagId, pos, 0 /* depth */), Value(uid))); - pos[0]++; - for (const auto&itr : int_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 2; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - - for (const auto&itr : long_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 3; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - - for (const auto&itr : string_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 4; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - - for (const auto&itr : float_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 5; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - if (!mValues.empty()) { - mValues.back().mField.decorateLastPos(1); - mValues.at(mValues.size() - 2).mField.decorateLastPos(1); - } -} - LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging, bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, const std::vector<uint8_t>& experimentIds, int32_t userId) { @@ -184,17 +116,6 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status))); } -LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) { - mLogdTimestampNs = timestampNs; - mTagId = tagId; - mLogUid = uid; - mContext = create_android_logger(1937006964); // the event tag shared by all stats logs - if (mContext) { - android_log_write_int64(mContext, timestampNs); - android_log_write_int32(mContext, tagId); - } -} - LogEvent::~LogEvent() { if (mContext) { // This is for the case when LogEvent is created using the test interface @@ -203,154 +124,6 @@ LogEvent::~LogEvent() { } } -bool LogEvent::write(int32_t value) { - if (mContext) { - return android_log_write_int32(mContext, value) >= 0; - } - return false; -} - -bool LogEvent::write(uint32_t value) { - if (mContext) { - return android_log_write_int32(mContext, value) >= 0; - } - return false; -} - -bool LogEvent::write(int64_t value) { - if (mContext) { - return android_log_write_int64(mContext, value) >= 0; - } - return false; -} - -bool LogEvent::write(uint64_t value) { - if (mContext) { - return android_log_write_int64(mContext, value) >= 0; - } - return false; -} - -bool LogEvent::write(const string& value) { - if (mContext) { - return android_log_write_string8_len(mContext, value.c_str(), value.length()) >= 0; - } - return false; -} - -bool LogEvent::write(float value) { - if (mContext) { - return android_log_write_float32(mContext, value) >= 0; - } - return false; -} - -bool LogEvent::writeBytes(const string& value) { - /* if (mContext) { - return android_log_write_char_array(mContext, value.c_str(), value.length()) >= 0; - }*/ - return false; -} - -bool LogEvent::writeKeyValuePairs(int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map) { - if (mContext) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(uid); - for (const auto& itr : int_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } - - for (const auto& itr : long_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } - - for (const auto& itr : string_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second.c_str()); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } - - for (const auto& itr : float_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } - - if (android_log_write_list_end(mContext) < 0) { - return false; - } - return true; - } - return false; -} - -bool LogEvent::write(const std::vector<AttributionNodeInternal>& nodes) { - if (mContext) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - for (size_t i = 0; i < nodes.size(); ++i) { - if (!write(nodes[i])) { - return false; - } - } - if (android_log_write_list_end(mContext) < 0) { - return false; - } - return true; - } - return false; -} - -bool LogEvent::write(const AttributionNodeInternal& node) { - if (mContext) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - if (android_log_write_int32(mContext, node.uid()) < 0) { - return false; - } - if (android_log_write_string8(mContext, node.tag().c_str()) < 0) { - return false; - } - if (android_log_write_list_end(mContext) < 0) { - return false; - } - return true; - } - return false; -} - void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t value = readNextValue<int32_t>(); addToValues(pos, depth, value, last); diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 0a89be4ce335..6c6aab1c5ada 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -28,27 +28,6 @@ namespace android { namespace os { namespace statsd { -struct AttributionNodeInternal { - void set_uid(int32_t id) { - mUid = id; - } - - void set_tag(const std::string& value) { - mTag = value; - } - - int32_t uid() const { - return mUid; - } - - const std::string& tag() const { - return mTag; - } - - int32_t mUid; - std::string mTag; -}; - struct InstallTrainInfo { int64_t trainVersionCode; std::string trainName; @@ -83,28 +62,6 @@ public: */ bool parseBuffer(uint8_t* buf, size_t len); - // TODO(b/149590301): delete unused functions below once LogEvent uses the - // new socket schema within test code. Really we would like the only entry - // points into LogEvent to be the above constructor and parseBuffer functions. - - /** - * Constructs a LogEvent with synthetic data for testing. Must call init() before reading. - */ - explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs); - - // For testing. The timestamp is used as both elapsed real time and logd timestamp. - explicit LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid); - - /** - * Constructs a KeyValuePairsAtom LogEvent from value maps. - */ - explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map); - // Constructs a BinaryPushStateChanged LogEvent from API call. explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging, bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, @@ -152,25 +109,6 @@ public: std::vector<uint8_t> GetStorage(size_t key, status_t* err) const; /** - * Write test data to the LogEvent. This can only be used when the LogEvent is constructed - * using LogEvent(tagId, timestampNs). You need to call init() before you can read from it. - */ - bool write(uint32_t value); - bool write(int32_t value); - bool write(uint64_t value); - bool write(int64_t value); - bool write(const std::string& value); - bool write(float value); - bool write(const std::vector<AttributionNodeInternal>& nodes); - bool write(const AttributionNodeInternal& node); - bool writeBytes(const std::string& value); - bool writeKeyValuePairs(int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map); - - /** * Return a string representation of this event. */ std::string ToString() const; diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index e85b97514242..0de92f3d9f47 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -55,6 +55,7 @@ const int FIELD_ID_DATA = 1; const int FIELD_ID_DIMENSION_IN_WHAT = 1; const int FIELD_ID_BUCKET_INFO = 3; const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; +const int FIELD_ID_SLICE_BY_STATE = 6; // for DurationBucketInfo const int FIELD_ID_DURATION = 3; const int FIELD_ID_BUCKET_NUM = 4; @@ -115,6 +116,14 @@ DurationMetricProducer::DurationMetricProducer( } mUnSlicedPartCondition = ConditionState::kUnknown; + for (const auto& stateLink : metric.state_link()) { + Metric2State ms; + ms.stateAtomId = stateLink.state_atom_id(); + translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); + translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); + mMetric2StateLinks.push_back(ms); + } + mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions); if (mWizard != nullptr && mConditionTrackerIndex >= 0 && mMetric2ConditionLinks.size() == 1) { @@ -150,21 +159,49 @@ sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker( return anomalyTracker; } +void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, + const int32_t oldState, const int32_t newState) { + // Create a FieldValue object to hold the new state. + FieldValue value; + value.mValue.setInt(newState); + // Check if this metric has a StateMap. If so, map the new state value to + // the correct state group id. + mapStateValue(atomId, &value); + + flushIfNeededLocked(eventTimeNs); + + // Each duration tracker is mapped to a different whatKey (a set of values from the + // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the + // state change event are a subset of the tracker's whatKey field values. + // + // Ex. For a duration metric dimensioned on uid and tag: + // DurationTracker1 whatKey = uid: 1001, tag: 1 + // DurationTracker2 whatKey = uid: 1002, tag 1 + // + // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state + // change. + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) { + continue; + } + whatIt.second->onStateChanged(eventTimeNs, atomId, value); + } +} + unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( const MetricDimensionKey& eventKey) const { switch (mAggregationType) { case DurationMetric_AggregationType_SUM: return make_unique<OringDurationTracker>( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, - mTimeBaseNs, mBucketSizeNs, mConditionSliced, - mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, + mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); case DurationMetric_AggregationType_MAX_SPARSE: return make_unique<MaxDurationTracker>( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, - mTimeBaseNs, mBucketSizeNs, mConditionSliced, - mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, + mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); } } @@ -364,6 +401,13 @@ void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); } + // Then fill slice_by_state. + for (auto state : dimensionKey.getStateValuesKey().getValues()) { + uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SLICE_BY_STATE); + writeStateToProto(state, protoOutput); + protoOutput->end(stateToken); + } // Then fill bucket_info (DurationBucketInfo). for (const auto& bucket : pair.second) { uint64_t bucketInfoToken = protoOutput->start( @@ -460,7 +504,6 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey const ConditionKey& conditionKeys, bool condition, const LogEvent& event) { const auto& whatKey = eventKey.getDimensionKeyInWhat(); - auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey); if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { if (hitGuardRailLocked(eventKey)) { @@ -471,19 +514,18 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey auto it = mCurrentSlicedDurationTrackerMap.find(whatKey); if (mUseWhatDimensionAsInternalDimension) { - it->second->noteStart(whatKey, condition, - event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys); return; } if (mInternalDimensions.empty()) { - it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, - event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(), + conditionKeys); } else { HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY; filterValues(mInternalDimensions, event.getValues(), &dimensionKey); - it->second->noteStart( - dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(), + conditionKeys); } } @@ -519,6 +561,41 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat); } + // Stores atom id to primary key pairs for each state atom that the metric is + // sliced by. + std::map<int, HashableDimensionKey> statePrimaryKeys; + + // For states with primary fields, use MetricStateLinks to get the primary + // field values from the log event. These values will form a primary key + // that will be used to query StateTracker for the correct state value. + for (const auto& stateLink : mMetric2StateLinks) { + getDimensionForState(event.getValues(), stateLink, + &statePrimaryKeys[stateLink.stateAtomId]); + } + + // For each sliced state, query StateTracker for the state value using + // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY. + // + // Expected functionality: for any case where the MetricStateLinks are + // initialized incorrectly (ex. # of state links != # of primary fields, no + // links are provided for a state with primary fields, links are provided + // in the wrong order, etc.), StateTracker will simply return kStateUnknown + // when queried using an incorrect key. + HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY; + for (auto atomId : mSlicedStateAtoms) { + FieldValue value; + if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { + // found a primary key for this state, query using the key + queryStateValue(atomId, statePrimaryKeys[atomId], &value); + } else { + // if no MetricStateLinks exist for this state atom, + // query using the default dimension key (empty HashableDimensionKey) + queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); + } + mapStateValue(atomId, &value); + stateValuesKey.addValue(value); + } + // Handles Stop events. if (matcherIndex == mStopIndex) { if (mUseWhatDimensionAsInternalDimension) { @@ -559,8 +636,8 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, condition = condition && mIsActive; - handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey, - condition, event); + handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition, + event); } size_t DurationMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 06da0f64aedb..cc48f99add01 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -54,6 +54,10 @@ public: sp<AnomalyTracker> addAnomalyTracker(const Alert &alert, const sp<AlarmMonitor>& anomalyAlarmMonitor) override; + void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const int32_t oldState, + const int32_t newState) override; + protected: void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override; @@ -137,7 +141,7 @@ private: // Helper function to create a duration tracker given the metric aggregation type. std::unique_ptr<DurationTracker> createDurationTracker( - const MetricDimensionKey& eventKey) const; + const MetricDimensionKey& eventKey) const; // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index be754e29b5bd..2518d85eb6a1 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -120,12 +120,13 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo FieldValue value; if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { // found a primary key for this state, query using the key - getMappedStateValue(atomId, statePrimaryKeys[atomId], &value); + queryStateValue(atomId, statePrimaryKeys[atomId], &value); } else { // if no MetricStateLinks exist for this state atom, // query using the default dimension key (empty HashableDimensionKey) - getMappedStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); + queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); } + mapStateValue(atomId, &value); stateValuesKey.addValue(value); } @@ -264,15 +265,17 @@ void MetricProducer::writeActiveMetricToProtoOutputStream( } } -void MetricProducer::getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, - FieldValue* value) { +void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* value) { if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) { value->mValue = Value(StateTracker::kStateUnknown); value->mField.setTag(atomId); ALOGW("StateTracker not found for state atom %d", atomId); return; } +} +void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) { // check if there is a state map for this atom auto atomIt = mStateGroupMap.find(atomId); if (atomIt == mStateGroupMap.end()) { diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 4c4cd8940b24..4550e65b6438 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -187,7 +187,8 @@ public: }; void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, int oldState, int newState){}; + const HashableDimensionKey& primaryKey, const int32_t oldState, + const int32_t newState){}; // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp. // This method clears all the past buckets. @@ -379,11 +380,15 @@ protected: return (endNs - mTimeBaseNs) / mBucketSizeNs - 1; } - // Query StateManager for original state value. - // If no state map exists for this atom, return the original value. - // Otherwise, return the group_id mapped to the atom and original value. - void getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, - FieldValue* value); + // Query StateManager for original state value using the queryKey. + // The field and value are output. + void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* value); + + // If a state map exists for the given atom, replace the original state + // value with the group id mapped to the value. + // If no state map exists, keep the original state value. + void mapStateValue(const int32_t atomId, FieldValue* value); DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason); @@ -467,6 +472,11 @@ protected: FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 3fb9166bf9bf..1fd6572cc760 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -329,6 +329,11 @@ private: FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index afe93d445e1d..8d59d1362919 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -56,11 +56,19 @@ struct DurationBucket { int64_t mDuration; }; +struct DurationValues { + // Recorded duration for current partial bucket. + int64_t mDuration; + + // Sum of past partial bucket durations in current full bucket. + // Used for anomaly detection. + int64_t mDurationFullBucket; +}; + class DurationTracker { public: DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - bool nesting, + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers) @@ -73,7 +81,6 @@ public: mNested(nesting), mCurrentBucketStartTimeNs(currentBucketStartNs), mDuration(0), - mDurationFullBucket(0), mCurrentBucketNum(currentBucketNum), mStartTimeNs(startTimeNs), mConditionSliced(conditionSliced), @@ -82,8 +89,8 @@ public: virtual ~DurationTracker(){}; - virtual void noteStart(const HashableDimensionKey& key, bool condition, - const int64_t eventTime, const ConditionKey& conditionKey) = 0; + virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, + const ConditionKey& conditionKey) = 0; virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime, const bool stopAll) = 0; virtual void noteStopAll(const int64_t eventTime) = 0; @@ -91,6 +98,9 @@ public: virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0; virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0; + virtual void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) = 0; + // Flush stale buckets if needed, and return true if the tracker has no on-going duration // events, so that the owner can safely remove the tracker. virtual bool flushIfNeeded( @@ -109,9 +119,12 @@ public: // Dump internal states for debugging virtual void dumpStates(FILE* out, bool verbose) const = 0; - void setEventKey(const MetricDimensionKey& eventKey) { - mEventKey = eventKey; - } + virtual int64_t getCurrentStateKeyDuration() const = 0; + + virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0; + + // Replace old value with new value for the given state atom. + virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0; protected: int64_t getCurrentBucketEndTimeNs() const { @@ -140,10 +153,11 @@ protected: } } - void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) { + void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey, + const int64_t& bucketValue, const int64_t& bucketNum) { for (auto& anomalyTracker : mAnomalyTrackers) { if (anomalyTracker != nullptr) { - anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum); + anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum); } } } @@ -164,6 +178,10 @@ protected: return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; } + void setEventKey(const MetricDimensionKey& eventKey) { + mEventKey = eventKey; + } + // A reference to the DurationMetricProducer's config key. const ConfigKey& mConfigKey; @@ -183,7 +201,8 @@ protected: int64_t mDuration; // current recorded duration result (for partial bucket) - int64_t mDurationFullBucket; // Sum of past partial buckets in current full bucket. + // Recorded duration results for each state key in the current partial bucket. + std::unordered_map<HashableDimensionKey, DurationValues> mStateKeyDurationMap; int64_t mCurrentBucketNum; diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 2be5855e90e6..ee4e1672411f 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -26,15 +26,14 @@ namespace statsd { MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - bool nesting, + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, - currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, - conditionSliced, fullLink, anomalyTrackers) { + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, + anomalyTrackers) { } bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { @@ -91,7 +90,6 @@ void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool conditi } } - void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime, bool forceStop) { VLOG("MaxDuration: key %s stop", key.toString().c_str()); @@ -240,6 +238,11 @@ void MaxDurationTracker::onSlicedConditionMayChange(bool overallCondition, } } +void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) { + ALOGE("MaxDurationTracker does not handle sliced state changes."); +} + void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) { for (auto& pair : mInfos) { noteConditionChanged(pair.first, condition, timestamp); @@ -309,6 +312,20 @@ void MaxDurationTracker::dumpStates(FILE* out, bool verbose) const { fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); } +int64_t MaxDurationTracker::getCurrentStateKeyDuration() const { + ALOGE("MaxDurationTracker does not handle sliced state changes."); + return -1; +} + +int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const { + ALOGE("MaxDurationTracker does not handle sliced state changes."); + return -1; +} + +void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { + ALOGE("MaxDurationTracker does not handle sliced state changes."); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index efb8dc70afd1..2891c6e1138a 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -54,10 +54,19 @@ public: void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; void onConditionChanged(bool condition, const int64_t timestamp) override; + void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) override; + int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const int64_t currentTimestamp) const override; void dumpStates(FILE* out, bool verbose) const override; + int64_t getCurrentStateKeyDuration() const override; + + int64_t getCurrentStateKeyFullBucketDuration() const override; + + void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); + private: // Returns true if at least one of the mInfos is started. bool anyStarted(); diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 57f39656fdfe..19b2fe89989d 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -26,13 +26,12 @@ using std::pair; OringDurationTracker::OringDurationTracker( const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, - int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, - const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, - currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, - conditionSliced, fullLink, anomalyTrackers), + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, + int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, + bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, + anomalyTrackers), mStarted(), mPaused() { mLastStartTime = 0; @@ -90,10 +89,14 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const int64 mConditionKeyMap.erase(key); } if (mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); - VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime, - (long long)mDuration); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); + VLOG("record duration %lld, total duration %lld for state key %s", + (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); } } @@ -112,10 +115,14 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const int64 void OringDurationTracker::noteStopAll(const int64_t timestamp) { if (!mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime, - (long long)mDuration); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s", + (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } stopAnomalyAlarm(timestamp); @@ -146,21 +153,36 @@ bool OringDurationTracker::flushCurrentBucket( // Process the current bucket. if (mStarted.size() > 0) { - mDuration += (currentBucketEndTimeNs - mLastStartTime); + // Calculate the duration for the current state key. + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (currentBucketEndTimeNs - mLastStartTime); } - if (mDuration > 0) { - DurationBucket current_info; - current_info.mBucketStartNs = mCurrentBucketStartTimeNs; - current_info.mBucketEndNs = currentBucketEndTimeNs; - current_info.mDuration = mDuration; - (*output)[mEventKey].push_back(current_info); - mDurationFullBucket += mDuration; - VLOG(" duration: %lld", (long long)current_info.mDuration); - } - if (eventTimeNs > fullBucketEnd) { - // End of full bucket, can send to anomaly tracker now. - addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum); - mDurationFullBucket = 0; + // Store DurationBucket info for each whatKey, stateKey pair. + // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the + // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to + // store durations for each stateKey, so we need to flush the bucket by creating a + // DurationBucket for each stateKey. + for (auto& durationIt : mStateKeyDurationMap) { + if (durationIt.second.mDuration > 0) { + DurationBucket current_info; + current_info.mBucketStartNs = mCurrentBucketStartTimeNs; + current_info.mBucketEndNs = currentBucketEndTimeNs; + current_info.mDuration = durationIt.second.mDuration; + (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)] + .push_back(current_info); + + durationIt.second.mDurationFullBucket += durationIt.second.mDuration; + VLOG(" duration: %lld", (long long)current_info.mDuration); + } + + if (eventTimeNs > fullBucketEnd) { + // End of full bucket, can send to anomaly tracker now. + addPastBucketToAnomalyTrackers( + MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first), + getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum); + durationIt.second.mDurationFullBucket = 0; + } + durationIt.second.mDuration = 0; } if (mStarted.size() > 0) { @@ -169,20 +191,19 @@ bool OringDurationTracker::flushCurrentBucket( info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1); info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs; info.mDuration = mBucketSizeNs; + // Full duration buckets are attributed to the current stateKey. (*output)[mEventKey].push_back(info); // Safe to send these buckets to anomaly tracker since they must be full buckets. // If it's a partial bucket, numBucketsForward would be 0. - addPastBucketToAnomalyTrackers(info.mDuration, mCurrentBucketNum + i); + addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i); VLOG(" add filling bucket with duration %lld", (long long)info.mDuration); } } else { if (numBucketsForward >= 2) { - addPastBucketToAnomalyTrackers(0, mCurrentBucketNum + numBucketsForward - 1); + addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1); } } - mDuration = 0; - if (numBucketsForward > 0) { mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs; mCurrentBucketNum += numBucketsForward; @@ -229,10 +250,14 @@ void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, } if (mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime), - (long long)mDuration); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + VLOG("record duration %lld, total duration %lld for state key %s", + (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } } @@ -288,10 +313,13 @@ void OringDurationTracker::onConditionChanged(bool condition, const int64_t time } else { if (!mStarted.empty()) { VLOG("Condition false, all paused"); - mDuration += (timestamp - mLastStartTime); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); mPaused.insert(mStarted.begin(), mStarted.end()); mStarted.clear(); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } } if (mStarted.empty()) { @@ -299,6 +327,20 @@ void OringDurationTracker::onConditionChanged(bool condition, const int64_t time } } +void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) { + // If no keys are being tracked, update the current state key and return. + if (mStarted.empty()) { + updateCurrentStateKey(atomId, newState); + return; + } + // Add the current duration length to the previous state key and then update + // the last start time and current state key. + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime); + mLastStartTime = timestamp; + updateCurrentStateKey(atomId, newState); +} + int64_t OringDurationTracker::predictAnomalyTimestampNs( const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const { @@ -308,12 +350,13 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( // The timestamp of the current bucket end. const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs(); - // The past duration ns for the current bucket. - int64_t currentBucketPastNs = mDuration + mDurationFullBucket; + // The past duration ns for the current bucket of the current stateKey. + int64_t currentStateBucketPastNs = + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration(); // As we move into the future, old buckets get overwritten (so their old data is erased). // Sum of past durations. Will change as we overwrite old buckets. - int64_t pastNs = currentBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey); + int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey); // The refractory period end timestamp for dimension mEventKey. const int64_t refractoryPeriodEndNs = @@ -372,7 +415,7 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx); } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) { - pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs)); + pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs)); } } @@ -382,7 +425,34 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( void OringDurationTracker::dumpStates(FILE* out, bool verbose) const { fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size()); fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size()); - fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); + fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration()); +} + +int64_t OringDurationTracker::getCurrentStateKeyDuration() const { + auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); + if (it == mStateKeyDurationMap.end()) { + return 0; + } else { + return it->second.mDuration; + } +} + +int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const { + auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); + if (it == mStateKeyDurationMap.end()) { + return 0; + } else { + return it->second.mDurationFullBucket; + } +} + +void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { + HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey(); + for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) { + if (stateValuesKey->getValues()[i].mField.getTag() == atomId) { + stateValuesKey->mutableValue(i)->mValue = newState.mValue; + } + } } } // namespace statsd diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index f44e3275b83d..bd8017a7decd 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -28,10 +28,9 @@ class OringDurationTracker : public DurationTracker { public: OringDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, - int conditionIndex, - bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, - int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, - bool fullLink, + int conditionIndex, bool nesting, int64_t currentBucketStartNs, + int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, + bool conditionSliced, bool fullLink, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); OringDurationTracker(const OringDurationTracker& tracker) = default; @@ -45,6 +44,9 @@ public: void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; void onConditionChanged(bool condition, const int64_t timestamp) override; + void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) override; + bool flushCurrentBucket( const int64_t& eventTimeNs, std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override; @@ -56,6 +58,12 @@ public: const int64_t currentTimestamp) const override; void dumpStates(FILE* out, bool verbose) const override; + int64_t getCurrentStateKeyDuration() const override; + + int64_t getCurrentStateKeyFullBucketDuration() const override; + + void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); + private: // We don't need to keep track of individual durations. The information that's needed is: // 1) which keys are started. We record the first start time. diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 3810c486cf88..2fcb13b709f9 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -26,7 +26,6 @@ #include "MetricProducer.h" #include "condition/CombinationConditionTracker.h" #include "condition/SimpleConditionTracker.h" -#include "condition/StateConditionTracker.h" #include "external/StatsPullerManager.h" #include "matchers/CombinationLogMatchingTracker.h" #include "matchers/EventMatcherWizard.h" @@ -283,49 +282,6 @@ bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap, return true; } -/** - * A StateConditionTracker is built from a SimplePredicate which has only "start", and no "stop" - * or "stop_all". The start must be an atom matcher that matches a state atom. It must - * have dimension, the dimension must be the state atom's primary fields plus exclusive state - * field. For example, the StateConditionTracker is used in tracking UidProcessState and ScreenState. - * - */ -bool isStateConditionTracker(const SimplePredicate& simplePredicate, vector<Matcher>* primaryKeys) { - // 1. must not have "stop". must have "dimension" - if (!simplePredicate.has_stop() && simplePredicate.has_dimensions()) { - auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find( - simplePredicate.dimensions().field()); - // 2. must be based on a state atom. - if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) { - // 3. dimension must be primary fields + state field IN ORDER - size_t expectedDimensionCount = it->second.primaryFields.size() + 1; - vector<Matcher> dimensions; - translateFieldMatcher(simplePredicate.dimensions(), &dimensions); - if (dimensions.size() != expectedDimensionCount) { - return false; - } - // 3.1 check the primary fields first. - size_t index = 0; - for (const auto& field : it->second.primaryFields) { - Matcher matcher = getSimpleMatcher(it->first, field); - if (!(matcher == dimensions[index])) { - return false; - } - primaryKeys->push_back(matcher); - index++; - } - Matcher stateFieldMatcher = - getSimpleMatcher(it->first, it->second.exclusiveField); - // 3.2 last dimension should be the exclusive field. - if (!(dimensions.back() == stateFieldMatcher)) { - return false; - } - return true; - } - } - return false; -} // namespace statsd - bool initConditions(const ConfigKey& key, const StatsdConfig& config, const unordered_map<int64_t, int>& logTrackerMap, unordered_map<int64_t, int>& conditionTrackerMap, @@ -341,16 +297,8 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, int index = allConditionTrackers.size(); switch (condition.contents_case()) { case Predicate::ContentsCase::kSimplePredicate: { - vector<Matcher> primaryKeys; - if (isStateConditionTracker(condition.simple_predicate(), &primaryKeys)) { - allConditionTrackers.push_back(new StateConditionTracker(key, condition.id(), index, - condition.simple_predicate(), - logTrackerMap, primaryKeys)); - } else { - allConditionTrackers.push_back(new SimpleConditionTracker( - key, condition.id(), index, condition.simple_predicate(), - logTrackerMap)); - } + allConditionTrackers.push_back(new SimpleConditionTracker( + key, condition.id(), index, condition.simple_predicate(), logTrackerMap)); break; } case Predicate::ContentsCase::kCombination: { @@ -564,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, @@ -575,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/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h index a8ccc6289b9a..6af7a9adca20 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.h +++ b/cmds/statsd/src/metrics/metrics_manager_util.h @@ -132,8 +132,6 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& vector<int>& metricsWithActivation, std::set<int64_t>& noReportMetricIds); -bool isStateConditionTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys); - } // namespace statsd } // namespace os } // namespace android 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/condition/StateConditionTracker_test.cpp b/cmds/statsd/tests/condition/StateConditionTracker_test.cpp deleted file mode 100644 index 86b50ae82ff4..000000000000 --- a/cmds/statsd/tests/condition/StateConditionTracker_test.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (C) 2017 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "src/condition/StateConditionTracker.h" -#include "tests/statsd_test_util.h" - -#include <gmock/gmock.h> -#include <gtest/gtest.h> -#include <stdio.h> -#include <numeric> -#include <vector> - -using std::map; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ -namespace android { -namespace os { -namespace statsd { - -const int kUidProcTag = 27; - -SimplePredicate getUidProcStatePredicate() { - SimplePredicate simplePredicate; - simplePredicate.set_start(StringToId("UidProcState")); - - simplePredicate.mutable_dimensions()->set_field(kUidProcTag); - simplePredicate.mutable_dimensions()->add_child()->set_field(1); - simplePredicate.mutable_dimensions()->add_child()->set_field(2); - - simplePredicate.set_count_nesting(false); - return simplePredicate; -} - -// TODO(b/149590301): Update these tests to use new socket schema. -//void makeUidProcStateEvent(int32_t uid, int32_t state, LogEvent* event) { -// event->write(uid); -// event->write(state); -// event->init(); -//} -// -//TEST(StateConditionTrackerTest, TestStateChange) { -// int uid1 = 111; -// int uid2 = 222; -// -// int state1 = 1001; -// int state2 = 1002; -// unordered_map<int64_t, int> trackerNameIndexMap; -// trackerNameIndexMap[StringToId("UidProcState")] = 0; -// vector<Matcher> primaryFields; -// primaryFields.push_back(getSimpleMatcher(kUidProcTag, 1)); -// StateConditionTracker tracker(ConfigKey(12, 123), 123, 0, getUidProcStatePredicate(), -// trackerNameIndexMap, primaryFields); -// -// LogEvent event(kUidProcTag, 0 /*timestamp*/); -// makeUidProcStateEvent(uid1, state1, &event); -// -// vector<MatchingState> matcherState; -// matcherState.push_back(MatchingState::kMatched); -// vector<sp<ConditionTracker>> allPredicates; -// vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); -// vector<bool> changedCache(1, false); -// -// tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache); -// EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); -// EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); -// EXPECT_TRUE(changedCache[0]); -// -// changedCache[0] = false; -// conditionCache[0] = ConditionState::kNotEvaluated; -// tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache); -// EXPECT_EQ(0ULL, tracker.mLastChangedToTrueDimensions.size()); -// EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); -// EXPECT_FALSE(changedCache[0]); -// -// LogEvent event2(kUidProcTag, 0 /*timestamp*/); -// makeUidProcStateEvent(uid1, state2, &event2); -// -// changedCache[0] = false; -// conditionCache[0] = ConditionState::kNotEvaluated; -// tracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, changedCache); -// EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); -// EXPECT_EQ(1ULL, tracker.mLastChangedToFalseDimensions.size()); -// EXPECT_TRUE(changedCache[0]); -// -// LogEvent event3(kUidProcTag, 0 /*timestamp*/); -// makeUidProcStateEvent(uid2, state1, &event3); -// changedCache[0] = false; -// conditionCache[0] = ConditionState::kNotEvaluated; -// tracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, changedCache); -// EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); -// EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); -// EXPECT_TRUE(changedCache[0]); -//} - -} // 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..7d765d3fbbf5 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); @@ -946,13 +1060,6 @@ sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const in return processor; } -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) { - AttributionNodeInternal attribution; - attribution.set_uid(uid); - attribution.set_tag(tag); - return attribution; -} - void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) { std::sort(events->begin(), events->end(), [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) { @@ -964,6 +1071,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..f24705a0c89f 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); @@ -263,9 +291,6 @@ std::unique_ptr<LogEvent> CreateOverlayStateChangedEvent(int64_t timestampNs, co const bool usingAlertWindow, const OverlayStateChanged::State state); -// Helper function to create an AttributionNodeInternal proto. -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag); - // Create a statsd log event processor upon the start time in seconds, config and key. sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs, const StatsdConfig& config, const ConfigKey& key, @@ -277,6 +302,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/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java index a381f9c4560a..2909048da7ea 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java @@ -17,16 +17,23 @@ package com.android.statsd.shelltools; import com.android.os.StatsLog.ConfigMetricsReportList; +import com.google.common.io.Files; + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; import java.util.logging.ConsoleHandler; import java.util.logging.Formatter; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Utilities for local use of statsd. @@ -80,7 +87,8 @@ public class Utils { * @throws InterruptedException */ public static ConfigMetricsReportList getReportList(long configId, boolean clearData, - boolean useShellUid, Logger logger) throws IOException, InterruptedException { + boolean useShellUid, Logger logger, String deviceSerial) + throws IOException, InterruptedException { try { File outputFile = File.createTempFile("statsdret", ".bin"); outputFile.deleteOnExit(); @@ -88,6 +96,8 @@ public class Utils { outputFile, logger, "adb", + "-s", + deviceSerial, "shell", CMD_DUMP_REPORT, useShellUid ? SHELL_UID : "", @@ -117,12 +127,14 @@ public class Utils { * @throws IOException * @throws InterruptedException */ - public static void logAppBreadcrumb(int label, int state, Logger logger) + public static void logAppBreadcrumb(int label, int state, Logger logger, String deviceSerial) throws IOException, InterruptedException { runCommand( null, logger, "adb", + "-s", + deviceSerial, "shell", CMD_LOG_APP_BREADCRUMB, String.valueOf(label), @@ -145,13 +157,14 @@ public class Utils { * Algorithm: true if (sdk >= minSdk) || (sdk == minSdk-1 && codeName.startsWith(minCodeName)) * If all else fails, assume it will work (letting future commands deal with any errors). */ - public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename) { + public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename, + String deviceSerial) { BufferedReader in = null; try { File outFileSdk = File.createTempFile("shelltools_sdk", "tmp"); outFileSdk.deleteOnExit(); runCommand(outFileSdk, logger, - "adb", "shell", "getprop", "ro.build.version.sdk"); + "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.sdk"); in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileSdk))); // If NullPointerException/NumberFormatException/etc., just catch and return true. int sdk = Integer.parseInt(in.readLine().trim()); @@ -162,7 +175,7 @@ public class Utils { File outFileCode = File.createTempFile("shelltools_codename", "tmp"); outFileCode.deleteOnExit(); runCommand(outFileCode, logger, - "adb", "shell", "getprop", "ro.build.version.codename"); + "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.codename"); in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileCode))); return in.readLine().startsWith(minCodename); } else { @@ -190,4 +203,30 @@ public class Utils { return record.getMessage() + "\n"; } } + + /** + * Parse the result of "adb devices" to return the list of connected devices. + * @param logger Logger to log error messages + * @return List of the serial numbers of the connected devices. + */ + public static List<String> getDeviceSerials(Logger logger) { + try { + ArrayList<String> devices = new ArrayList<>(); + File outFile = File.createTempFile("device_serial", "tmp"); + outFile.deleteOnExit(); + Utils.runCommand(outFile, logger, "adb", "devices"); + List<String> outputLines = Files.readLines(outFile, Charset.defaultCharset()); + Pattern regex = Pattern.compile("^(.*)\tdevice$"); + for (String line : outputLines) { + Matcher m = regex.matcher(line); + if (m.find()) { + devices.add(m.group(1)); + } + } + return devices; + } catch (Exception ex) { + logger.log(Level.SEVERE, "Failed to list connected devices: " + ex.getMessage()); + } + return null; + } } diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java index 2eb46605b28d..7db514180b9a 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java @@ -26,6 +26,8 @@ import com.google.protobuf.TextFormat; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; /** @@ -49,7 +51,7 @@ public class LocalDrive { public static final String HELP_STRING = "Usage:\n\n" + - "statsd_localdrive upload CONFIG_FILE [CONFIG_ID] [--binary]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] upload CONFIG_FILE [CONFIG_ID] [--binary]\n" + " Uploads the given statsd config file (in binary or human-readable-text format).\n" + " If a config with this id already exists, removes it first.\n" + " CONFIG_FILE Location of config file on host.\n" + @@ -59,12 +61,12 @@ public class LocalDrive { // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID "\n" + - "statsd_localdrive update CONFIG_FILE [CONFIG_ID] [--binary]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] update CONFIG_FILE [CONFIG_ID] [--binary]\n" + " Same as upload, but does not remove the old config first (if it already exists).\n" + // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID "\n" + - "statsd_localdrive get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" + " Prints the output statslog data (in binary or human-readable-text format).\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + " --binary Output should be in binary, instead of default human-readable text.\n" + @@ -75,13 +77,13 @@ public class LocalDrive { // --include_current_bucket --proto "\n" + - "statsd_localdrive remove [CONFIG_ID]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] remove [CONFIG_ID]\n" + " Removes the config.\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID "\n" + - "statsd_localdrive clear [CONFIG_ID]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] clear [CONFIG_ID]\n" + " Clears the data associated with the config.\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID @@ -94,29 +96,59 @@ public class LocalDrive { /** Usage: make statsd_localdrive && statsd_localdrive */ public static void main(String[] args) { Utils.setUpLogger(sLogger, DEBUG); + if (args.length == 0) { + printHelp(); + return; + } + + int remainingArgsLength = args.length; + String deviceSerial = null; + if (args[0].equals("-s")) { + if (args.length == 1) { + printHelp(); + } + deviceSerial = args[1]; + remainingArgsLength -= 2; + } + + List<String> connectedDevices = Utils.getDeviceSerials(sLogger); + if (connectedDevices == null || connectedDevices.size() == 0) { + sLogger.log(Level.SEVERE, "No device connected."); + return; + } + if (connectedDevices.size() == 1 && deviceSerial == null) { + deviceSerial = connectedDevices.get(0); + } + + if (deviceSerial == null) { + sLogger.log(Level.SEVERE, "More than one devices connected. Please specify" + + " with -s DEVICE_SERIAL"); + return; + } - if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME)) { + if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME, deviceSerial)) { sLogger.severe("LocalDrive only works with statsd versions for Android " + MIN_CODENAME + " or higher."); return; } - if (args.length > 0) { - switch (args[0]) { + int idx = args.length - remainingArgsLength; + if (remainingArgsLength > 0) { + switch (args[idx]) { case "clear": - cmdClear(args); + cmdClear(args, idx, deviceSerial); return; case "get-data": - cmdGetData(args); + cmdGetData(args, idx, deviceSerial); return; case "remove": - cmdRemove(args); + cmdRemove(args, idx); return; case "update": - cmdUpdate(args); + cmdUpdate(args, idx, deviceSerial); return; case "upload": - cmdUpload(args); + cmdUpload(args, idx, deviceSerial); return; } } @@ -128,17 +160,18 @@ public class LocalDrive { } // upload CONFIG_FILE [CONFIG_ID] [--binary] - private static boolean cmdUpload(String[] args) { - return updateConfig(args, true); + private static boolean cmdUpload(String[] args, int idx, String deviceSerial) { + return updateConfig(args, idx, true, deviceSerial); } // update CONFIG_FILE [CONFIG_ID] [--binary] - private static boolean cmdUpdate(String[] args) { - return updateConfig(args, false); + private static boolean cmdUpdate(String[] args, int idx, String deviceSerial) { + return updateConfig(args, idx, false, deviceSerial); } - private static boolean updateConfig(String[] args, boolean removeOldConfig) { - int argCount = args.length - 1; // Used up one for upload/update. + private static boolean updateConfig(String[] args, int idx, boolean removeOldConfig, + String deviceSerial) { + int argCount = args.length - 1 - idx; // Used up one for upload/update. // Get CONFIG_FILE if (argCount < 1) { @@ -146,7 +179,7 @@ public class LocalDrive { printHelp(); return false; } - final String origConfigLocation = args[1]; + final String origConfigLocation = args[idx + 1]; if (!new File(origConfigLocation).exists()) { sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation); return false; @@ -154,13 +187,13 @@ public class LocalDrive { argCount--; // Get --binary - boolean binary = contains(args, 2, BINARY_FLAG); + boolean binary = contains(args, idx + 2, BINARY_FLAG); if (binary) argCount --; // Get CONFIG_ID long configId; try { - configId = getConfigId(argCount < 1, args, 2); + configId = getConfigId(argCount < 1, args, idx + 2); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); @@ -174,7 +207,8 @@ public class LocalDrive { try { Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG, Utils.SHELL_UID, String.valueOf(configId)); - Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger); + Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger, + deviceSerial); } catch (InterruptedException | IOException e) { sLogger.severe("Failed to remove config: " + e.getMessage()); return false; @@ -218,19 +252,19 @@ public class LocalDrive { } // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map] - private static boolean cmdGetData(String[] args) { - boolean binary = contains(args, 1, BINARY_FLAG); - boolean noUidMap = contains(args, 1, NO_UID_MAP_FLAG); - boolean clearData = contains(args, 1, CLEAR_DATA); + private static boolean cmdGetData(String[] args, int idx, String deviceSerial) { + boolean binary = contains(args, idx + 1, BINARY_FLAG); + boolean noUidMap = contains(args, idx + 1, NO_UID_MAP_FLAG); + boolean clearData = contains(args, idx + 1, CLEAR_DATA); // Get CONFIG_ID - int argCount = args.length - 1; // Used up one for get-data. + int argCount = args.length - 1 - idx; // Used up one for get-data. if (binary) argCount--; if (noUidMap) argCount--; if (clearData) argCount--; long configId; try { - configId = getConfigId(argCount < 1, args, 1); + configId = getConfigId(argCount < 1, args, idx + 1); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); @@ -243,7 +277,8 @@ public class LocalDrive { // Even if the args request no modifications, we still parse it to make sure it's valid. ConfigMetricsReportList reportList; try { - reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger); + reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger, + deviceSerial); } catch (IOException | InterruptedException e) { sLogger.severe("Failed to get report list: " + e.getMessage()); return false; @@ -274,11 +309,11 @@ public class LocalDrive { } // clear [CONFIG_ID] - private static boolean cmdClear(String[] args) { + private static boolean cmdClear(String[] args, int idx, String deviceSerial) { // Get CONFIG_ID long configId; try { - configId = getConfigId(false, args, 1); + configId = getConfigId(false, args, idx + 1); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); @@ -287,7 +322,8 @@ public class LocalDrive { sLogger.fine(String.format("cmdClear with %d", configId)); try { - Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger); + Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger, + deviceSerial); } catch (IOException | InterruptedException e) { sLogger.severe("Failed to get report list: " + e.getMessage()); return false; @@ -296,11 +332,11 @@ public class LocalDrive { } // remove [CONFIG_ID] - private static boolean cmdRemove(String[] args) { + private static boolean cmdRemove(String[] args, int idx) { // Get CONFIG_ID long configId; try { - configId = getConfigId(false, args, 1); + configId = getConfigId(false, args, idx + 1); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java index 75518a3ea56f..2a7cfd306174 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -71,6 +72,7 @@ public class TestDrive { private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName()); private String mAdditionalAllowedPackage; + private String mDeviceSerial; private final Set<Long> mTrackedMetrics = new HashSet<>(); public static void main(String[] args) { @@ -81,15 +83,41 @@ public class TestDrive { if (args.length < 1) { LOGGER.log(Level.SEVERE, "Usage: ./test_drive [-p additional_allowed_package] " + + "[-s DEVICE_SERIAL_NUMBER]" + "<atomId1> <atomId2> ... <atomIdN>"); return; } - if (args.length >= 3 && args[0].equals("-p")) { - testDrive.mAdditionalAllowedPackage = args[1]; + List<String> connectedDevices = Utils.getDeviceSerials(LOGGER); + if (connectedDevices == null || connectedDevices.size() == 0) { + LOGGER.log(Level.SEVERE, "No device connected."); + return; + } + + int arg_index = 0; + while (arg_index < args.length) { + String arg = args[arg_index]; + if (arg.equals("-p")) { + testDrive.mAdditionalAllowedPackage = args[++arg_index]; + } else if (arg.equals("-s")) { + testDrive.mDeviceSerial = args[++arg_index]; + } else { + break; + } + arg_index++; + } + + if (connectedDevices.size() == 1 && testDrive.mDeviceSerial == null) { + testDrive.mDeviceSerial = connectedDevices.get(0); + } + + if (testDrive.mDeviceSerial == null) { + LOGGER.log(Level.SEVERE, "More than one devices connected. Please specify" + + " with -s DEVICE_SERIAL"); + return; } - for (int i = testDrive.mAdditionalAllowedPackage == null ? 0 : 2; i < args.length; i++) { + for (int i = arg_index; i < args.length; i++) { try { int atomId = Integer.valueOf(args[i]); if (Atom.getDescriptor().findFieldByNumber(atomId) == null) { @@ -109,7 +137,7 @@ public class TestDrive { LOGGER.log(Level.SEVERE, "Failed to create valid config."); return; } - remoteConfigPath = testDrive.pushConfig(config); + remoteConfigPath = testDrive.pushConfig(config, testDrive.mDeviceSerial); LOGGER.info("Pushed the following config to statsd:"); LOGGER.info(config.toString()); if (!hasPulledAtom(trackedAtoms)) { @@ -120,17 +148,18 @@ public class TestDrive { } else { LOGGER.info("Now wait for 1.5 minutes ..."); Thread.sleep(15_000); - Utils.logAppBreadcrumb(0, 0, LOGGER); + Utils.logAppBreadcrumb(0, 0, LOGGER, testDrive.mDeviceSerial); Thread.sleep(75_000); } testDrive.dumpMetrics(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to test drive: " + e.getMessage(), e); } finally { - testDrive.removeConfig(); + testDrive.removeConfig(testDrive.mDeviceSerial); if (remoteConfigPath != null) { try { - Utils.runCommand(null, LOGGER, "adb", "shell", "rm", remoteConfigPath); + Utils.runCommand(null, LOGGER, + "adb", "-s", testDrive.mDeviceSerial, "shell", "rm", remoteConfigPath); } catch (Exception e) { LOGGER.log(Level.WARNING, "Unable to remove remote config file: " + remoteConfigPath, e); @@ -140,7 +169,8 @@ public class TestDrive { } private void dumpMetrics() throws Exception { - ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, false, LOGGER); + ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, false, LOGGER, + mDeviceSerial); // We may get multiple reports. Take the last one. ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1); for (StatsLogReport statsLog : report.getMetricsList()) { @@ -216,22 +246,24 @@ public class TestDrive { return atomMatcherBuilder.build(); } - private static String pushConfig(StatsdConfig config) throws IOException, InterruptedException { + private static String pushConfig(StatsdConfig config, String deviceSerial) + throws IOException, InterruptedException { File configFile = File.createTempFile("statsdconfig", ".config"); configFile.deleteOnExit(); Files.write(config.toByteArray(), configFile); String remotePath = "/data/local/tmp/" + configFile.getName(); - Utils.runCommand(null, LOGGER, "adb", "push", configFile.getAbsolutePath(), remotePath); - Utils.runCommand(null, LOGGER, - "adb", "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG, + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "push", configFile.getAbsolutePath(), remotePath); + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG, String.valueOf(CONFIG_ID)); return remotePath; } - private static void removeConfig() { + private static void removeConfig(String deviceSerial) { try { - Utils.runCommand(null, LOGGER, - "adb", "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID)); + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID)); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to remove config: " + e.getMessage()); } 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/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 27c9cfcdd05b..aa290404c001 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -25,6 +25,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IOnAppsChangedListener; import android.content.pm.LauncherApps; +import android.content.pm.ShortcutQueryWrapper; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IShortcutChangeCallback; import android.content.pm.PackageInstaller; @@ -67,9 +68,8 @@ interface ILauncherApps { LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage, String packageName, in UserHandle user); - ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName, - in List shortcutIds, in List<LocusId> locusIds, in ComponentName componentName, - int flags, in UserHandle user); + ParceledListSlice getShortcuts(String callingPackage, in ShortcutQueryWrapper query, + in UserHandle user); void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds, in UserHandle user); boolean startShortcut(String callingPackage, String packageName, String featureId, String id, @@ -93,9 +93,8 @@ interface ILauncherApps { in IPackageInstallerCallback callback); ParceledListSlice getAllSessions(String callingPackage); - void registerShortcutChangeCallback(String callingPackage, long changedSince, - String packageName, in List shortcutIds, in List<LocusId> locusIds, - in ComponentName componentName, int flags, in IShortcutChangeCallback callback); + void registerShortcutChangeCallback(String callingPackage, in ShortcutQueryWrapper query, + in IShortcutChangeCallback callback); void unregisterShortcutChangeCallback(String callingPackage, in IShortcutChangeCallback callback); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 6c161fcb8646..87dc0a17f41c 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -1046,8 +1046,7 @@ public class LauncherApps { // changed callback, but that only returns shortcuts with the "key" information, so // that won't return disabled message. return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(), - query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds, - query.mActivity, query.mQueryFlags, user) + new ShortcutQueryWrapper(query), user) .getList()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1826,8 +1825,7 @@ public class LauncherApps { mShortcutChangeCallbacks.put(callback, new Pair<>(executor, proxy)); try { mService.registerShortcutChangeCallback(mContext.getPackageName(), - query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds, - query.mActivity, query.mQueryFlags, proxy); + new ShortcutQueryWrapper(query), proxy); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/ShortcutQueryWrapper.aidl b/core/java/android/content/pm/ShortcutQueryWrapper.aidl new file mode 100644 index 000000000000..d02600acf232 --- /dev/null +++ b/core/java/android/content/pm/ShortcutQueryWrapper.aidl @@ -0,0 +1,20 @@ +/** + * 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 android.content.pm; + +parcelable ShortcutQueryWrapper; + diff --git a/core/java/android/content/pm/ShortcutQueryWrapper.java b/core/java/android/content/pm/ShortcutQueryWrapper.java new file mode 100644 index 000000000000..c6134416adbc --- /dev/null +++ b/core/java/android/content/pm/ShortcutQueryWrapper.java @@ -0,0 +1,190 @@ +/* + * 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 android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.LocusId; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.ArrayList; +import java.util.List; + +/** + * @hide + */ +@DataClass(genParcelable = true, genToString = true) +public final class ShortcutQueryWrapper extends LauncherApps.ShortcutQuery implements Parcelable { + + public ShortcutQueryWrapper(LauncherApps.ShortcutQuery query) { + this(); + mChangedSince = query.mChangedSince; + mPackage = query.mPackage; + mLocusIds = query.mLocusIds; + mShortcutIds = query.mShortcutIds; + mActivity = query.mActivity; + mQueryFlags = query.mQueryFlags; + } + + public long getChangedSince() { + return mChangedSince; + } + + @Nullable + public String getPackage() { + return mPackage; + } + + @Nullable + public List<LocusId> getLocusIds() { + return mLocusIds; + } + + @Nullable + public List<String> getShortcutIds() { + return mShortcutIds; + } + + @Nullable + public ComponentName getActivity() { + return mActivity; + } + + public int getQueryFlags() { + return mQueryFlags; + } + + // Code below generated by codegen v1.0.14. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/ShortcutQueryWrapper.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public ShortcutQueryWrapper() { + + // onConstructed(); // You can define this method to get a callback + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "ShortcutQueryWrapper { " + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mPackage != null) flg |= 0x2; + if (mShortcutIds != null) flg |= 0x4; + if (mLocusIds != null) flg |= 0x8; + if (mActivity != null) flg |= 0x10; + dest.writeByte(flg); + dest.writeLong(mChangedSince); + if (mPackage != null) dest.writeString(mPackage); + if (mShortcutIds != null) dest.writeStringList(mShortcutIds); + if (mLocusIds != null) dest.writeParcelableList(mLocusIds, flags); + if (mActivity != null) dest.writeTypedObject(mActivity, flags); + dest.writeInt(mQueryFlags); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ ShortcutQueryWrapper(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + long changedSince = in.readLong(); + String pkg = (flg & 0x2) == 0 ? null : in.readString(); + List<String> shortcutIds = null; + if ((flg & 0x4) != 0) { + shortcutIds = new ArrayList<>(); + in.readStringList(shortcutIds); + } + List<LocusId> locusIds = null; + if ((flg & 0x8) != 0) { + locusIds = new ArrayList<>(); + in.readParcelableList(locusIds, LocusId.class.getClassLoader()); + } + ComponentName activity = (flg & 0x10) == 0 ? null + : (ComponentName) in.readTypedObject(ComponentName.CREATOR); + int queryFlags = in.readInt(); + + this.mChangedSince = changedSince; + this.mPackage = pkg; + this.mShortcutIds = shortcutIds; + this.mLocusIds = locusIds; + this.mActivity = activity; + this.mQueryFlags = queryFlags; + com.android.internal.util.AnnotationValidations.validate( + QueryFlags.class, null, mQueryFlags); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<ShortcutQueryWrapper> CREATOR + = new Parcelable.Creator<ShortcutQueryWrapper>() { + @Override + public ShortcutQueryWrapper[] newArray(int size) { + return new ShortcutQueryWrapper[size]; + } + + @Override + public ShortcutQueryWrapper createFromParcel(@NonNull Parcel in) { + return new ShortcutQueryWrapper(in); + } + }; + + @DataClass.Generated( + time = 1582049937960L, + codegenVersion = "1.0.14", + sourceFile = "frameworks/base/core/java/android/content/pm/ShortcutQueryWrapper.java", + inputSignatures = "public long getChangedSince()\npublic @android.annotation.Nullable java.lang.String getPackage()\npublic @android.annotation.Nullable java.util.List<android.content.LocusId> getLocusIds()\npublic @android.annotation.Nullable java.util.List<java.lang.String> getShortcutIds()\npublic @android.annotation.Nullable android.content.ComponentName getActivity()\npublic int getQueryFlags()\nclass ShortcutQueryWrapper extends android.content.pm.LauncherApps.ShortcutQuery implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} 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/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 65f45d895027..ea5cc7f2e8bc 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -634,17 +634,39 @@ public final class DisplayManager { public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface, int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { - return createVirtualDisplay(null /* projection */, name, width, height, densityDpi, surface, - flags, callback, handler, null /* uniqueId */); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, + height, densityDpi); + builder.setFlags(flags); + if (surface != null) { + builder.setSurface(surface); + } + return createVirtualDisplay(null /* projection */, builder.build(), callback, handler); } + // TODO : Remove this hidden API after remove all callers. (Refer to MultiDisplayService) /** @hide */ public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection, @NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface, int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler, @Nullable String uniqueId) { - return mGlobal.createVirtualDisplay(mContext, projection, - name, width, height, densityDpi, surface, flags, callback, handler, uniqueId); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, + height, densityDpi); + builder.setFlags(flags); + if (uniqueId != null) { + builder.setUniqueId(uniqueId); + } + if (surface != null) { + builder.setSurface(surface); + } + return createVirtualDisplay(projection, builder.build(), callback, handler); + } + + /** @hide */ + public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection, + @NonNull VirtualDisplayConfig virtualDisplayConfig, + @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { + return mGlobal.createVirtualDisplay(mContext, projection, virtualDisplayConfig, callback, + handler); } /** diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 526db85b47d4..4d645e6052a7 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -451,35 +451,26 @@ public final class DisplayManagerGlobal { } } - public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection, - String name, int width, int height, int densityDpi, Surface surface, int flags, - VirtualDisplay.Callback callback, Handler handler, String uniqueId) { - if (TextUtils.isEmpty(name)) { - throw new IllegalArgumentException("name must be non-null and non-empty"); - } - if (width <= 0 || height <= 0 || densityDpi <= 0) { - throw new IllegalArgumentException("width, height, and densityDpi must be " - + "greater than 0"); - } - + public VirtualDisplay createVirtualDisplay(@NonNull Context context, MediaProjection projection, + @NonNull VirtualDisplayConfig virtualDisplayConfig, VirtualDisplay.Callback callback, + Handler handler) { VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler); IMediaProjection projectionToken = projection != null ? projection.getProjection() : null; int displayId; try { - displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken, - context.getPackageName(), name, width, height, densityDpi, surface, flags, - uniqueId); + displayId = mDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper, + projectionToken, context.getPackageName()); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } if (displayId < 0) { - Log.e(TAG, "Could not create virtual display: " + name); + Log.e(TAG, "Could not create virtual display: " + virtualDisplayConfig.getName()); return null; } Display display = getRealDisplay(displayId); if (display == null) { Log.wtf(TAG, "Could not obtain display info for newly created " - + "virtual display: " + name); + + "virtual display: " + virtualDisplayConfig.getName()); try { mDm.releaseVirtualDisplay(callbackWrapper); } catch (RemoteException ex) { @@ -487,7 +478,8 @@ public final class DisplayManagerGlobal { } return null; } - return new VirtualDisplay(this, display, callbackWrapper, surface); + return new VirtualDisplay(this, display, callbackWrapper, + virtualDisplayConfig.getSurface()); } public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) { diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index d22188ec5d7f..c697106d0c17 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -22,6 +22,7 @@ import android.hardware.display.BrightnessConfiguration; import android.hardware.display.Curve; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; import android.hardware.display.WifiDisplay; import android.hardware.display.WifiDisplayStatus; import android.media.projection.IMediaProjection; @@ -71,9 +72,9 @@ interface IDisplayManager { // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate // MediaProjection token for certain combinations of flags. - int createVirtualDisplay(in IVirtualDisplayCallback callback, - in IMediaProjection projectionToken, String packageName, String name, - int width, int height, int densityDpi, in Surface surface, int flags, String uniqueId); + int createVirtualDisplay(in VirtualDisplayConfig virtualDisplayConfig, + in IVirtualDisplayCallback callback, in IMediaProjection projectionToken, + String packageName); // No permissions required, but must be same Uid as the creator. void resizeVirtualDisplay(in IVirtualDisplayCallback token, diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.aidl b/core/java/android/hardware/display/VirtualDisplayConfig.aidl new file mode 100644 index 000000000000..c28f1dfb9806 --- /dev/null +++ b/core/java/android/hardware/display/VirtualDisplayConfig.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.hardware.display; + +parcelable VirtualDisplayConfig; diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java new file mode 100644 index 000000000000..10e1c7c2e0df --- /dev/null +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -0,0 +1,491 @@ +/* + * 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 android.hardware.display; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.projection.MediaProjection; +import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.Surface; + +import com.android.internal.util.DataClass; + +/** + * Holds configuration used to create {@link VirtualDisplay} instances. See + * {@link MediaProjection#createVirtualDisplay(VirtualDisplayConfig, VirtualDisplay.Callback, Handler)}. + * + * @hide + */ +@DataClass(genParcelable = true, genAidl = true, genBuilder = true) +public final class VirtualDisplayConfig implements Parcelable { + /** + * The name of the virtual display, must be non-empty. + */ + @NonNull + private String mName; + + /** + * The width of the virtual display in pixels. Must be greater than 0. + */ + @IntRange(from = 1) + private int mWidth; + + /** + * The height of the virtual display in pixels. Must be greater than 0. + */ + @IntRange(from = 1) + private int mHeight; + + /** + * The density of the virtual display in dpi. Must be greater than 0. + */ + @IntRange(from = 1) + private int mDensityDpi; + + /** + * A combination of virtual display flags. + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, + * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. + */ + private int mFlags = 0; + + /** + * The surface to which the content of the virtual display should be rendered, or null if + * there is none initially. + */ + @Nullable + private Surface mSurface = null; + + /** + * The unique identifier for the display. Shouldn't be displayed to the user. + * @hide + */ + @Nullable + private String mUniqueId = null; + + /** + * The id of the display that the virtual display should mirror, or + * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially. + */ + private int mDisplayIdToMirror = DEFAULT_DISPLAY; + + + + // Code below generated by codegen v1.0.15. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ VirtualDisplayConfig( + @NonNull String name, + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @IntRange(from = 1) int densityDpi, + int flags, + @Nullable Surface surface, + @Nullable String uniqueId, + int displayIdToMirror) { + this.mName = name; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mName); + this.mWidth = width; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mWidth, + "from", 1); + this.mHeight = height; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mHeight, + "from", 1); + this.mDensityDpi = densityDpi; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mDensityDpi, + "from", 1); + this.mFlags = flags; + this.mSurface = surface; + this.mUniqueId = uniqueId; + this.mDisplayIdToMirror = displayIdToMirror; + + // onConstructed(); // You can define this method to get a callback + } + + /** + * The name of the virtual display, must be non-empty. + */ + @DataClass.Generated.Member + public @NonNull String getName() { + return mName; + } + + /** + * The width of the virtual display in pixels. Must be greater than 0. + */ + @DataClass.Generated.Member + public @IntRange(from = 1) int getWidth() { + return mWidth; + } + + /** + * The height of the virtual display in pixels. Must be greater than 0. + */ + @DataClass.Generated.Member + public @IntRange(from = 1) int getHeight() { + return mHeight; + } + + /** + * The density of the virtual display in dpi. Must be greater than 0. + */ + @DataClass.Generated.Member + public @IntRange(from = 1) int getDensityDpi() { + return mDensityDpi; + } + + /** + * A combination of virtual display flags. + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, + * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. + */ + @DataClass.Generated.Member + public int getFlags() { + return mFlags; + } + + /** + * The surface to which the content of the virtual display should be rendered, or null if + * there is none initially. + */ + @DataClass.Generated.Member + public @Nullable Surface getSurface() { + return mSurface; + } + + /** + * The unique identifier for the display. Shouldn't be displayed to the user. + * + * @hide + */ + @DataClass.Generated.Member + public @Nullable String getUniqueId() { + return mUniqueId; + } + + /** + * The id of the display that the virtual display should mirror, or + * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially. + */ + @DataClass.Generated.Member + public int getDisplayIdToMirror() { + return mDisplayIdToMirror; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + int flg = 0; + if (mSurface != null) flg |= 0x20; + if (mUniqueId != null) flg |= 0x40; + dest.writeInt(flg); + dest.writeString(mName); + dest.writeInt(mWidth); + dest.writeInt(mHeight); + dest.writeInt(mDensityDpi); + dest.writeInt(mFlags); + if (mSurface != null) dest.writeTypedObject(mSurface, flags); + if (mUniqueId != null) dest.writeString(mUniqueId); + dest.writeInt(mDisplayIdToMirror); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ VirtualDisplayConfig(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int flg = in.readInt(); + String name = in.readString(); + int width = in.readInt(); + int height = in.readInt(); + int densityDpi = in.readInt(); + int flags = in.readInt(); + Surface surface = (flg & 0x20) == 0 ? null : (Surface) in.readTypedObject(Surface.CREATOR); + String uniqueId = (flg & 0x40) == 0 ? null : in.readString(); + int displayIdToMirror = in.readInt(); + + this.mName = name; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mName); + this.mWidth = width; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mWidth, + "from", 1); + this.mHeight = height; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mHeight, + "from", 1); + this.mDensityDpi = densityDpi; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mDensityDpi, + "from", 1); + this.mFlags = flags; + this.mSurface = surface; + this.mUniqueId = uniqueId; + this.mDisplayIdToMirror = displayIdToMirror; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<VirtualDisplayConfig> CREATOR + = new Parcelable.Creator<VirtualDisplayConfig>() { + @Override + public VirtualDisplayConfig[] newArray(int size) { + return new VirtualDisplayConfig[size]; + } + + @Override + public VirtualDisplayConfig createFromParcel(@NonNull Parcel in) { + return new VirtualDisplayConfig(in); + } + }; + + /** + * A builder for {@link VirtualDisplayConfig} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @NonNull String mName; + private @IntRange(from = 1) int mWidth; + private @IntRange(from = 1) int mHeight; + private @IntRange(from = 1) int mDensityDpi; + private int mFlags; + private @Nullable Surface mSurface; + private @Nullable String mUniqueId; + private int mDisplayIdToMirror; + + private long mBuilderFieldsSet = 0L; + + /** + * Creates a new Builder. + * + * @param name + * The name of the virtual display, must be non-empty. + * @param width + * The width of the virtual display in pixels. Must be greater than 0. + * @param height + * The height of the virtual display in pixels. Must be greater than 0. + * @param densityDpi + * The density of the virtual display in dpi. Must be greater than 0. + */ + public Builder( + @NonNull String name, + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @IntRange(from = 1) int densityDpi) { + mName = name; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mName); + mWidth = width; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mWidth, + "from", 1); + mHeight = height; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mHeight, + "from", 1); + mDensityDpi = densityDpi; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mDensityDpi, + "from", 1); + } + + /** + * The name of the virtual display, must be non-empty. + */ + @DataClass.Generated.Member + public @NonNull Builder setName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mName = value; + return this; + } + + /** + * The width of the virtual display in pixels. Must be greater than 0. + */ + @DataClass.Generated.Member + public @NonNull Builder setWidth(@IntRange(from = 1) int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mWidth = value; + return this; + } + + /** + * The height of the virtual display in pixels. Must be greater than 0. + */ + @DataClass.Generated.Member + public @NonNull Builder setHeight(@IntRange(from = 1) int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mHeight = value; + return this; + } + + /** + * The density of the virtual display in dpi. Must be greater than 0. + */ + @DataClass.Generated.Member + public @NonNull Builder setDensityDpi(@IntRange(from = 1) int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mDensityDpi = value; + return this; + } + + /** + * A combination of virtual display flags. + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, + * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. + */ + @DataClass.Generated.Member + public @NonNull Builder setFlags(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mFlags = value; + return this; + } + + /** + * The surface to which the content of the virtual display should be rendered, or null if + * there is none initially. + */ + @DataClass.Generated.Member + public @NonNull Builder setSurface(@NonNull Surface value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mSurface = value; + return this; + } + + /** + * The unique identifier for the display. Shouldn't be displayed to the user. + * + * @hide + */ + @DataClass.Generated.Member + public @NonNull Builder setUniqueId(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x40; + mUniqueId = value; + return this; + } + + /** + * The id of the display that the virtual display should mirror, or + * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially. + */ + @DataClass.Generated.Member + public @NonNull Builder setDisplayIdToMirror(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x80; + mDisplayIdToMirror = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull VirtualDisplayConfig build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x100; // Mark builder used + + if ((mBuilderFieldsSet & 0x10) == 0) { + mFlags = 0; + } + if ((mBuilderFieldsSet & 0x20) == 0) { + mSurface = null; + } + if ((mBuilderFieldsSet & 0x40) == 0) { + mUniqueId = null; + } + if ((mBuilderFieldsSet & 0x80) == 0) { + mDisplayIdToMirror = DEFAULT_DISPLAY; + } + VirtualDisplayConfig o = new VirtualDisplayConfig( + mName, + mWidth, + mHeight, + mDensityDpi, + mFlags, + mSurface, + mUniqueId, + mDisplayIdToMirror); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x100) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1585179350902L, + codegenVersion = "1.0.15", + sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java", + inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange(from=1L) int mWidth\nprivate @android.annotation.IntRange(from=1L) int mHeight\nprivate @android.annotation.IntRange(from=1L) int mDensityDpi\nprivate int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index b52b437b4557..a298c856a0fb 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -18,6 +18,7 @@ package android.inputmethodservice; import android.annotation.BinderThread; import android.annotation.MainThread; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; @@ -37,6 +38,7 @@ import android.view.inputmethod.InputMethodSession; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; +import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; import com.android.internal.view.IInlineSuggestionsRequestCallback; @@ -52,7 +54,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; /** * Implements the internal IInputMethod interface to convert incoming calls @@ -90,12 +91,13 @@ class IInputMethodWrapper extends IInputMethod.Stub * * <p>This field must be set and cleared only from the binder thread(s), where the system * guarantees that {@link #bindInput(InputBinding)}, - * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and + * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean, boolean)}, and * {@link #unbindInput()} are called with the same order as the original calls * in {@link com.android.server.inputmethod.InputMethodManagerService}. * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p> */ - AtomicBoolean mIsUnbindIssued = null; + @Nullable + CancellationGroup mCancellationGroup = null; // NOTE: we should have a cache of these. static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback { @@ -187,11 +189,11 @@ class IInputMethodWrapper extends IInputMethod.Stub final IBinder startInputToken = (IBinder) args.arg1; final IInputContext inputContext = (IInputContext) args.arg2; final EditorInfo info = (EditorInfo) args.arg3; - final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4; + final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; SomeArgs moreArgs = (SomeArgs) args.arg5; final InputConnection ic = inputContext != null ? new InputConnectionWrapper( - mTarget, inputContext, moreArgs.argi3, isUnbindIssued) + mTarget, inputContext, moreArgs.argi3, cancellationGroup) : null; info.makeCompatible(mTargetSdkVersion); inputMethod.dispatchStartInputWithToken( @@ -295,15 +297,15 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void bindInput(InputBinding binding) { - if (mIsUnbindIssued != null) { + if (mCancellationGroup != null) { Log.e(TAG, "bindInput must be paired with unbindInput."); } - mIsUnbindIssued = new AtomicBoolean(); + mCancellationGroup = new CancellationGroup(); // This IInputContext is guaranteed to implement all the methods. final int missingMethodFlags = 0; InputConnection ic = new InputConnectionWrapper(mTarget, IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags, - mIsUnbindIssued); + mCancellationGroup); InputBinding nu = new InputBinding(ic, binding); mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu)); } @@ -311,10 +313,10 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void unbindInput() { - if (mIsUnbindIssued != null) { + if (mCancellationGroup != null) { // Signal the flag then forget it. - mIsUnbindIssued.set(true); - mIsUnbindIssued = null; + mCancellationGroup.cancelAll(); + mCancellationGroup = null; } else { Log.e(TAG, "unbindInput must be paired with bindInput."); } @@ -326,16 +328,16 @@ class IInputMethodWrapper extends IInputMethod.Stub public void startInput(IBinder startInputToken, IInputContext inputContext, @InputConnectionInspector.MissingMethodFlags final int missingMethods, EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) { - if (mIsUnbindIssued == null) { + if (mCancellationGroup == null) { Log.e(TAG, "startInput must be called after bindInput."); - mIsUnbindIssued = new AtomicBoolean(); + mCancellationGroup = new CancellationGroup(); } SomeArgs args = SomeArgs.obtain(); args.argi1 = restarting ? 1 : 0; args.argi2 = shouldPreRenderIme ? 1 : 0; args.argi3 = missingMethods; - mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO( - DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args)); + mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(DO_START_INPUT, startInputToken, + inputContext, attribute, mCancellationGroup, args)); } @BinderThread diff --git a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java index ef138a0c2217..dbb669be1402 100644 --- a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java +++ b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java @@ -39,6 +39,7 @@ import android.view.inputmethod.ExtractedText; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IMultiClientInputMethodSession; +import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.os.SomeArgs; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.view.IInputContext; @@ -46,7 +47,6 @@ import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputConnectionWrapper; import java.lang.ref.WeakReference; -import java.util.concurrent.atomic.AtomicBoolean; /** * Re-dispatches all the incoming per-client events to the specified {@link Looper} thread. @@ -80,19 +80,19 @@ final class MultiClientInputMethodClientCallbackAdaptor { @Nullable InputEventReceiver mInputEventReceiver; - private final AtomicBoolean mFinished = new AtomicBoolean(false); + private final CancellationGroup mCancellationGroup = new CancellationGroup(); IInputMethodSession.Stub createIInputMethodSession() { synchronized (mSessionLock) { return new InputMethodSessionImpl( - mSessionLock, mCallbackImpl, mHandler, mFinished); + mSessionLock, mCallbackImpl, mHandler, mCancellationGroup); } } IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() { synchronized (mSessionLock) { return new MultiClientInputMethodSessionImpl( - mSessionLock, mCallbackImpl, mHandler, mFinished); + mSessionLock, mCallbackImpl, mHandler, mCancellationGroup); } } @@ -105,7 +105,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { mHandler = new Handler(looper, null, true); mReadChannel = readChannel; mInputEventReceiver = new ImeInputEventReceiver(mReadChannel, mHandler.getLooper(), - mFinished, mDispatcherState, mCallbackImpl.mOriginalCallback); + mCancellationGroup, mDispatcherState, mCallbackImpl.mOriginalCallback); } } @@ -139,16 +139,17 @@ final class MultiClientInputMethodClientCallbackAdaptor { } private static final class ImeInputEventReceiver extends InputEventReceiver { - private final AtomicBoolean mFinished; + private final CancellationGroup mCancellationGroupOnFinishSession; private final KeyEvent.DispatcherState mDispatcherState; private final MultiClientInputMethodServiceDelegate.ClientCallback mClientCallback; private final KeyEventCallbackAdaptor mKeyEventCallbackAdaptor; - ImeInputEventReceiver(InputChannel readChannel, Looper looper, AtomicBoolean finished, + ImeInputEventReceiver(InputChannel readChannel, Looper looper, + CancellationGroup cancellationGroupOnFinishSession, KeyEvent.DispatcherState dispatcherState, MultiClientInputMethodServiceDelegate.ClientCallback callback) { super(readChannel, looper); - mFinished = finished; + mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; mDispatcherState = dispatcherState; mClientCallback = callback; mKeyEventCallbackAdaptor = new KeyEventCallbackAdaptor(callback); @@ -156,7 +157,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { @Override public void onInputEvent(InputEvent event) { - if (mFinished.get()) { + if (mCancellationGroupOnFinishSession.isCanceled()) { // The session has been finished. finishInputEvent(event, false); return; @@ -187,14 +188,14 @@ final class MultiClientInputMethodClientCallbackAdaptor { private CallbackImpl mCallbackImpl; @GuardedBy("mSessionLock") private Handler mHandler; - private final AtomicBoolean mSessionFinished; + private final CancellationGroup mCancellationGroupOnFinishSession; InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler, - AtomicBoolean sessionFinished) { + CancellationGroup cancellationGroupOnFinishSession) { mSessionLock = lock; mCallbackImpl = callback; mHandler = handler; - mSessionFinished = sessionFinished; + mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; } @Override @@ -272,7 +273,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { if (mCallbackImpl == null || mHandler == null) { return; } - mSessionFinished.set(true); + mCancellationGroupOnFinishSession.cancelAll(); mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::finishSession, mCallbackImpl)); mCallbackImpl = null; @@ -311,14 +312,14 @@ final class MultiClientInputMethodClientCallbackAdaptor { private CallbackImpl mCallbackImpl; @GuardedBy("mSessionLock") private Handler mHandler; - private final AtomicBoolean mSessionFinished; + private final CancellationGroup mCancellationGroupOnFinishSession; MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback, - Handler handler, AtomicBoolean sessionFinished) { + Handler handler, CancellationGroup cancellationGroupOnFinishSession) { mSessionLock = lock; mCallbackImpl = callback; mHandler = handler; - mSessionFinished = sessionFinished; + mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; } @Override @@ -335,7 +336,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { new WeakReference<>(null); args.arg1 = (inputContext == null) ? null : new InputConnectionWrapper(fakeIMS, inputContext, missingMethods, - mSessionFinished); + mCancellationGroupOnFinishSession); args.arg2 = editorInfo; args.argi1 = controlFlags; args.argi2 = softInputMode; diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 9ff7ebee6da4..73c6b3daf2ec 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -169,6 +169,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_OEM_PAID, NET_CAPABILITY_MCX, NET_CAPABILITY_PARTIAL_CONNECTIVITY, + NET_CAPABILITY_TEMPORARILY_NOT_METERED, }) public @interface NetCapability { } @@ -336,8 +337,16 @@ public final class NetworkCapabilities implements Parcelable { @SystemApi public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; + /** + * This capability will be set for networks that are generally metered, but are currently + * unmetered, e.g., because the user is in a particular area. This capability can be changed at + * any time. When it is removed, applications are responsible for stopping any data transfer + * that should not occur on a metered network. + */ + public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PARTIAL_CONNECTIVITY; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_TEMPORARILY_NOT_METERED; /** * Network capabilities that are expected to be mutable, i.e., can change while a particular @@ -353,7 +362,8 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_FOREGROUND) | (1 << NET_CAPABILITY_NOT_CONGESTED) | (1 << NET_CAPABILITY_NOT_SUSPENDED) - | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY); + | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY + | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)); /** * Network capabilities that are not allowed in NetworkRequests. This exists because the @@ -424,6 +434,7 @@ public final class NetworkCapabilities implements Parcelable { */ private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES = (1 << NET_CAPABILITY_NOT_METERED) + | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED) | (1 << NET_CAPABILITY_NOT_RESTRICTED) | (1 << NET_CAPABILITY_NOT_VPN) | (1 << NET_CAPABILITY_NOT_ROAMING) @@ -1864,6 +1875,7 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_OEM_PAID: return "OEM_PAID"; case NET_CAPABILITY_MCX: return "MCX"; case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY"; + case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED"; default: return Integer.toString(capability); } } diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index 20e5f243163f..683993f762c0 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -251,6 +251,11 @@ public final class BinderProxy implements IBinder { } } } + // For gathering this debug output, we're making synchronous binder calls + // out of system_server to all processes hosting binder objects it holds a reference to; + // since some of those processes might be frozen, we don't want to block here + // forever. Disable the freezer. + Process.enableFreezer(false); for (WeakReference<BinderProxy> weakRef : proxiesToQuery) { BinderProxy bp = weakRef.get(); String key; @@ -273,6 +278,7 @@ public final class BinderProxy implements IBinder { counts.put(key, i + 1); } } + Process.enableFreezer(true); Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray( new Map.Entry[counts.size()]); 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/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 35f955f7e78b..3534bb0f763f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1747,17 +1747,18 @@ public final class ViewRootImpl implements ViewParent, || !mBlastSurfaceControl.isValid()) { return null; } + + Surface ret = null; if (mBlastBufferQueue == null) { mBlastBufferQueue = new BLASTBufferQueue( mBlastSurfaceControl, width, height); + // We only return the Surface the first time, as otherwise + // it hasn't changed and there is no need to update. + ret = mBlastBufferQueue.getSurface(); } mBlastBufferQueue.update(mBlastSurfaceControl, width, height); - mTransaction.show(mBlastSurfaceControl) - .reparent(mBlastSurfaceControl, mSurfaceControl) - .apply(); - - return mBlastBufferQueue.getSurface(); + return ret; } private void setBoundsLayerCrop() { @@ -7352,8 +7353,14 @@ public final class ViewRootImpl implements ViewParent, if (!mUseBLASTAdapter) { mSurface.copyFrom(mSurfaceControl); } else { - mSurface.transferFrom(getOrCreateBLASTSurface(mSurfaceSize.x, - mSurfaceSize.y)); + final Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x, + mSurfaceSize.y); + // If blastSurface == null that means it hasn't changed since the last time we + // called. In this situation, avoid calling transferFrom as we would then + // inc the generation ID and cause EGL resources to be recreated. + if (blastSurface != null) { + mSurface.transferFrom(blastSurface); + } } } else { destroySurface(); diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 561ee604aa7f..316a5f2c88d2 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -36,7 +36,6 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import java.util.List; @@ -70,8 +69,7 @@ import java.util.List; public final class WindowManagerImpl implements WindowManager { @UnsupportedAppUsage private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); - @VisibleForTesting - public final Context mContext; + private final Context mContext; private final Window mParentWindow; private IBinder mDefaultToken; 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/DecorContext.java b/core/java/com/android/internal/policy/DecorContext.java index f00776897f2c..99b4b5fb7707 100644 --- a/core/java/com/android/internal/policy/DecorContext.java +++ b/core/java/com/android/internal/policy/DecorContext.java @@ -41,17 +41,17 @@ import java.lang.ref.WeakReference; public class DecorContext extends ContextThemeWrapper { private PhoneWindow mPhoneWindow; private WindowManager mWindowManager; - private Resources mResources; + private Resources mActivityResources; private ContentCaptureManager mContentCaptureManager; - private WeakReference<Context> mContext; + private WeakReference<Context> mActivityContext; // TODO(b/149928768): Non-activity context can be passed. @VisibleForTesting - public DecorContext(Context baseContext, Context context) { - super(baseContext.createDisplayContext(context.getDisplayNoVerify()), null); - mContext = new WeakReference<>(context); - mResources = context.getResources(); + public DecorContext(Context context, Context activityContext) { + super(context.createDisplayContext(activityContext.getDisplayNoVerify()), null); + mActivityContext = new WeakReference<>(activityContext); + mActivityResources = activityContext.getResources(); } void setPhoneWindow(PhoneWindow phoneWindow) { @@ -61,56 +61,58 @@ public class DecorContext extends ContextThemeWrapper { @Override public Object getSystemService(String name) { - final Context context = mContext.get(); if (Context.WINDOW_SERVICE.equals(name)) { - if (context != null && mWindowManager == null) { - WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(name); + if (mWindowManager == null) { + WindowManagerImpl wm = + (WindowManagerImpl) super.getSystemService(Context.WINDOW_SERVICE); mWindowManager = wm.createLocalWindowManager(mPhoneWindow); } return mWindowManager; } if (Context.CONTENT_CAPTURE_MANAGER_SERVICE.equals(name)) { - if (context != null && mContentCaptureManager == null) { - mContentCaptureManager = (ContentCaptureManager) context.getSystemService(name); + if (mContentCaptureManager == null) { + Context activityContext = mActivityContext.get(); + if (activityContext != null) { + mContentCaptureManager = (ContentCaptureManager) activityContext + .getSystemService(name); + } } return mContentCaptureManager; } - // LayoutInflater and WallpaperManagerService should also be obtained from context - // instead of application context. - return (context != null) ? context.getSystemService(name) : super.getSystemService(name); + return super.getSystemService(name); } @Override public Resources getResources() { - Context context = mContext.get(); + Context activityContext = mActivityContext.get(); // Attempt to update the local cached Resources from the activity context. If the activity // is no longer around, return the old cached values. - if (context != null) { - mResources = context.getResources(); + if (activityContext != null) { + mActivityResources = activityContext.getResources(); } - return mResources; + return mActivityResources; } @Override public AssetManager getAssets() { - return mResources.getAssets(); + return mActivityResources.getAssets(); } @Override public AutofillOptions getAutofillOptions() { - Context context = mContext.get(); - if (context != null) { - return context.getAutofillOptions(); + Context activityContext = mActivityContext.get(); + if (activityContext != null) { + return activityContext.getAutofillOptions(); } return null; } @Override public ContentCaptureOptions getContentCaptureOptions() { - Context context = mContext.get(); - if (context != null) { - return context.getContentCaptureOptions(); + Context activityContext = mActivityContext.get(); + if (activityContext != null) { + return activityContext.getContentCaptureOptions(); } return null; } 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/android/content/pm/ShortcutQueryWrapperTest.java b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java new file mode 100644 index 000000000000..8f8488f8b287 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java @@ -0,0 +1,77 @@ +/* + * 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 android.content.pm; + +import static org.junit.Assert.assertEquals; + +import android.content.ComponentName; +import android.content.LocusId; +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import androidx.test.runner.AndroidJUnit4; + +import com.google.android.collect.Lists; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +@Presubmit +public class ShortcutQueryWrapperTest { + + private static final long CHANGED_SINCE = TimeUnit.SECONDS.toMillis(1); + private static final String PACKAGE_NAME = "com.android.test"; + private static final List<String> SHORTCUT_IDS = Lists.newArrayList("s1", "s2", "s3"); + private static final List<LocusId> LOCUS_IDS = Lists.newArrayList( + new LocusId("id1"), new LocusId("id2"), new LocusId("id3")); + private static final ComponentName COMPONENT_NAME = new ComponentName( + PACKAGE_NAME, "ShortcutQueryTest"); + private static final int QUERY_FLAG = LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS; + + private ShortcutQueryWrapper mShortcutQuery; + + @Before + public void setUp() throws Exception { + mShortcutQuery = new ShortcutQueryWrapper(new LauncherApps.ShortcutQuery() + .setChangedSince(CHANGED_SINCE) + .setPackage(PACKAGE_NAME) + .setShortcutIds(SHORTCUT_IDS) + .setLocusIds(LOCUS_IDS) + .setActivity(COMPONENT_NAME) + .setQueryFlags(QUERY_FLAG)); + } + + @Test + public void testWriteAndReadFromParcel() { + Parcel p = Parcel.obtain(); + mShortcutQuery.writeToParcel(p, 0); + p.setDataPosition(0); + ShortcutQueryWrapper q = ShortcutQueryWrapper.CREATOR.createFromParcel(p); + assertEquals("Changed since doesn't match!", CHANGED_SINCE, q.getChangedSince()); + assertEquals("Package name doesn't match!", PACKAGE_NAME, q.getPackage()); + assertEquals("Shortcut ids doesn't match", SHORTCUT_IDS, q.getShortcutIds()); + assertEquals("Locus ids doesn't match", LOCUS_IDS, q.getLocusIds()); + assertEquals("Component name doesn't match", COMPONENT_NAME, q.getActivity()); + assertEquals("Query flag doesn't match", QUERY_FLAG, q.getQueryFlags()); + p.recycle(); + } +} diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index d649b945492b..61f58b0d27ce 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -645,14 +645,60 @@ public class DeviceConfigTest { Properties modifiedProperties2 = new Properties.Builder(namespaceToBan2).setString(KEY, VALUE) + .setString(KEY3, NULL_VALUE).setString(KEY4, VALUE2).build(); + DeviceConfig.setProperties(modifiedProperties2); + modifiedProperties2 = DeviceConfig.getProperties(namespaceToBan2); + assertThat(modifiedProperties2.getKeyset()).containsExactly(KEY, KEY3, KEY4); + assertThat(modifiedProperties2.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE); + assertThat(modifiedProperties2.getString(KEY4, DEFAULT_VALUE)).isEqualTo(VALUE2); + // Since value is null DEFAULT_VALUE should be returned + assertThat(modifiedProperties2.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); + } + + @Test + public void allConfigsUnbannedIfAnyUnbannedConfigUpdated() + throws DeviceConfig.BadConfigException { + // Given namespaces will be permanently banned, thus they need to be different every time + final String namespaceToBan1 = NAMESPACE + System.currentTimeMillis(); + final String namespaceToBan2 = NAMESPACE + System.currentTimeMillis() + 1; + + // Set namespaces properties + Properties properties1 = new Properties.Builder(namespaceToBan1).setString(KEY, VALUE) + .setString(KEY4, NULL_VALUE).build(); + DeviceConfig.setProperties(properties1); + Properties properties2 = new Properties.Builder(namespaceToBan2).setString(KEY2, VALUE2) + .setString(KEY4, NULL_VALUE).build(); + DeviceConfig.setProperties(properties2); + + // Ban namespace with related properties + DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, namespaceToBan1); + DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, namespaceToBan2); + + // Verify given namespace properties are banned + assertThrows(DeviceConfig.BadConfigException.class, + () -> DeviceConfig.setProperties(properties1)); + assertThrows(DeviceConfig.BadConfigException.class, + () -> DeviceConfig.setProperties(properties2)); + + // Modify properties and verify we can set them + Properties modifiedProperties1 = new Properties.Builder(namespaceToBan1).setString(KEY, + VALUE) .setString(KEY4, NULL_VALUE).setString(KEY2, VALUE2).build(); DeviceConfig.setProperties(modifiedProperties1); - modifiedProperties2 = DeviceConfig.getProperties(namespaceToBan1); - assertThat(modifiedProperties2.getKeyset()).containsExactly(KEY, KEY2, KEY4); - assertThat(modifiedProperties2.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE); - assertThat(modifiedProperties2.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); + modifiedProperties1 = DeviceConfig.getProperties(namespaceToBan1); + assertThat(modifiedProperties1.getKeyset()).containsExactly(KEY, KEY2, KEY4); + assertThat(modifiedProperties1.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE); + assertThat(modifiedProperties1.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); + // Since value is null DEFAULT_VALUE should be returned + assertThat(modifiedProperties1.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); + + // verify that other banned namespaces are unbanned now. + DeviceConfig.setProperties(properties2); + Properties result = DeviceConfig.getProperties(namespaceToBan2); + assertThat(result.getKeyset()).containsExactly(KEY2, KEY4); + assertThat(result.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); // Since value is null DEFAULT_VALUE should be returned - assertThat(modifiedProperties2.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); + assertThat(result.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); } // TODO(mpape): resolve b/142727848 and re-enable listener tests 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..bc0cdc1e029b 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -34,6 +34,8 @@ import static com.android.internal.app.ChooserListAdapter.SHORTCUT_TARGET_SCORE_ import static com.android.internal.app.ChooserWrapperActivity.sOverrides; import static com.android.internal.app.MatcherUtils.first; +import static junit.framework.Assert.assertTrue; + import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; @@ -67,6 +69,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 +78,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 +145,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 +997,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 +1069,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 +1157,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 +1439,299 @@ 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())); + } + + @Test + public void testAutolaunch_singleTarget_wifthWorkProfileAndTabbedViewOff_noAutolaunch() { + ResolverActivity.ENABLE_TABBED_VIEW = false; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + waitForIdle(); + + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + + assertTrue(chosen[0] == null); + } + + @Test + public void testAutolaunch_singleTarget_noWorkProfile_autolaunch() { + ResolverActivity.ENABLE_TABBED_VIEW = false; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(1); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + waitForIdle(); + + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + + assertThat(chosen[0], is(personalResolvedComponentInfos.get(0).getResolveInfoAt(0))); + } + 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/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 5a3aff937b79..0bf8663c7a85 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -30,6 +30,8 @@ import static com.android.internal.app.MatcherUtils.first; import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo; import static com.android.internal.app.ResolverWrapperActivity.sOverrides; +import static junit.framework.Assert.assertTrue; + import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; @@ -710,6 +712,54 @@ public class ResolverActivityTest { .check(matches(isDisplayed())); } + @Test + public void testAutolaunch_singleTarget_withWorkProfileAndTabbedViewOff_noAutolaunch() { + ResolverActivity.ENABLE_TABBED_VIEW = false; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Intent sendIntent = createSendImageIntent(); + sendIntent.setType("TestType"); + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + waitForIdle(); + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + + assertTrue(chosen[0] == null); + } + + @Test + public void testAutolaunch_singleTarget_noWorkProfile_autolaunch() { + ResolverActivity.ENABLE_TABBED_VIEW = false; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(1); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Intent sendIntent = createSendImageIntent(); + sendIntent.setType("TestType"); + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + waitForIdle(); + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + + assertThat(chosen[0], is(personalResolvedComponentInfos.get(0).getResolveInfoAt(0))); + } + private Intent createSendImageIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); diff --git a/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java b/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java index d019704fb684..3e40466e4b64 100644 --- a/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java +++ b/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java @@ -20,24 +20,19 @@ import static android.view.Display.DEFAULT_DISPLAY; import static org.junit.Assert.assertEquals; -import android.app.Activity; -import android.app.EmptyActivity; import android.content.Context; import android.hardware.display.DisplayManagerGlobal; import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.DisplayAdjustments; import android.view.DisplayInfo; -import android.view.WindowManager; -import android.view.WindowManagerImpl; -import androidx.test.core.app.ApplicationProvider; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; + import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,13 +46,9 @@ public final class DecorContextTest { private Context mContext; private static final int EXTERNAL_DISPLAY = DEFAULT_DISPLAY + 1; - @Rule - public ActivityTestRule<EmptyActivity> mActivityRule = - new ActivityTestRule<>(EmptyActivity.class); - @Before - public void setUp() { - mContext = ApplicationProvider.getApplicationContext(); + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getContext(); } @Test @@ -85,19 +76,4 @@ public final class DecorContextTest { Display associatedDisplay = decorContext.getDisplay(); assertEquals(expectedDisplayId, associatedDisplay.getDisplayId()); } - - @Test - public void testGetWindowManagerFromVisualDecorContext() throws Throwable { - mActivityRule.runOnUiThread(() -> { - Activity activity = mActivityRule.getActivity(); - final DecorContext decorContext = new DecorContext(mContext.getApplicationContext(), - activity); - WindowManagerImpl actualWm = (WindowManagerImpl) - decorContext.getSystemService(WindowManager.class); - WindowManagerImpl expectedWm = (WindowManagerImpl) - activity.getSystemService(WindowManager.class); - // Verify that window manager is from activity not application context. - assertEquals(expectedWm.mContext, actualWm.mContext); - }); - } } 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/MediaCas.java b/media/java/android/media/MediaCas.java index ad9486cc6597..405410a054de 100644 --- a/media/java/android/media/MediaCas.java +++ b/media/java/android/media/MediaCas.java @@ -19,6 +19,7 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.Context; import android.hardware.cas.V1_0.HidlCasPluginDescriptor; import android.hardware.cas.V1_0.ICas; @@ -1076,6 +1077,17 @@ public final class MediaCas implements AutoCloseable { } } + /** + * Release Cas session. This is primarily used as a test API for CTS. + * @hide + */ + @TestApi + public void forceResourceLost() { + if (mResourceListener != null) { + mResourceListener.onReclaimResources(); + } + } + @Override public void close() { if (mICas != null) { diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java index 632cfb0f1e30..37e141537c79 100644 --- a/media/java/android/media/projection/MediaProjection.java +++ b/media/java/android/media/projection/MediaProjection.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; +import android.hardware.display.VirtualDisplayConfig; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; import android.os.Handler; @@ -100,11 +101,18 @@ public final class MediaProjection { int width, int height, int dpi, boolean isSecure, @Nullable Surface surface, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); - int flags = isSecure ? DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE : 0; - return dm.createVirtualDisplay(this, name, width, height, dpi, surface, - flags | DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR | - DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, callback, handler, - null /* uniqueId */); + int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR + | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; + if (isSecure) { + flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; + } + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, + height, dpi); + builder.setFlags(flags); + if (surface != null) { + builder.setSurface(surface); + } + return dm.createVirtualDisplay(this, builder.build(), callback, handler); } /** @@ -133,9 +141,35 @@ public final class MediaProjection { public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height, int dpi, int flags, @Nullable Surface surface, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { - DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); - return dm.createVirtualDisplay(this, name, width, height, dpi, surface, flags, callback, - handler, null /* uniqueId */); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, + height, dpi); + builder.setFlags(flags); + if (surface != null) { + builder.setSurface(surface); + } + return createVirtualDisplay(builder.build(), callback, handler); + } + + /** + * Creates a {@link android.hardware.display.VirtualDisplay} to capture the + * contents of the screen. + * + * @param virtualDisplayConfig The arguments for the virtual display configuration. See + * {@link VirtualDisplayConfig} for using it. + * @param callback Callback to call when the virtual display's state + * changes, or null if none. + * @param handler The {@link android.os.Handler} on which the callback should be + * invoked, or null if the callback should be invoked on the calling + * thread's main {@link android.os.Looper}. + * + * @see android.hardware.display.VirtualDisplay + * @hide + */ + @Nullable + public VirtualDisplay createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig, + @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { + DisplayManager dm = mContext.getSystemService(DisplayManager.class); + return dm.createVirtualDisplay(this, virtualDisplayConfig, callback, handler); } /** diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index d4494acb7e7a..cf1f1b509ad6 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -602,6 +602,9 @@ public class Tuner implements AutoCloseable { */ @Nullable public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) { + if (mFrontend == null) { + throw new IllegalStateException("frontend is not initialized"); + } return nativeGetFrontendStatus(statusTypes); } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 4a7e8e1fa151..312e5fe14c37 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -124,7 +124,11 @@ using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtGuardInterval; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtSettings; +using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus; using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo; +using ::android::hardware::tv::tuner::V1_0::FrontendStatus; +using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo; +using ::android::hardware::tv::tuner::V1_0::FrontendStatusType; using ::android::hardware::tv::tuner::V1_0::FrontendType; using ::android::hardware::tv::tuner::V1_0::ITuner; using ::android::hardware::tv::tuner::V1_0::LnbPosition; @@ -1453,6 +1457,254 @@ jobject JTuner::getDemuxCaps() { numBytesInSectionFilter, filterCaps, linkCaps, bTimeFilter); } +jobject JTuner::getFrontendStatus(jintArray types) { + if (mFe == NULL) { + return NULL; + } + JNIEnv *env = AndroidRuntime::getJNIEnv(); + jsize size = env->GetArrayLength(types); + std::vector<FrontendStatusType> v(size); + env->GetIntArrayRegion(types, 0, size, reinterpret_cast<jint*>(&v[0])); + + Result res; + hidl_vec<FrontendStatus> status; + mFe->getStatus(v, + [&](Result r, const hidl_vec<FrontendStatus>& s) { + res = r; + status = s; + }); + if (res != Result::SUCCESS) { + return NULL; + } + + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendStatus"); + jmethodID init = env->GetMethodID(clazz, "<init>", "()V"); + jobject statusObj = env->NewObject(clazz, init); + + jclass intClazz = env->FindClass("java/lang/Integer"); + jmethodID initInt = env->GetMethodID(intClazz, "<init>", "(I)V"); + jclass booleanClazz = env->FindClass("java/lang/Boolean"); + jmethodID initBoolean = env->GetMethodID(booleanClazz, "<init>", "(Z)V"); + + for (auto s : status) { + switch(s.getDiscriminator()) { + case FrontendStatus::hidl_discriminator::isDemodLocked: { + jfieldID field = env->GetFieldID(clazz, "mIsDemodLocked", "Ljava/lang/Boolean;"); + jobject newBooleanObj = env->NewObject( + booleanClazz, initBoolean, static_cast<jboolean>(s.isDemodLocked())); + env->SetObjectField(statusObj, field, newBooleanObj); + break; + } + case FrontendStatus::hidl_discriminator::snr: { + jfieldID field = env->GetFieldID(clazz, "mSnr", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.snr())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::ber: { + jfieldID field = env->GetFieldID(clazz, "mBer", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.ber())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::per: { + jfieldID field = env->GetFieldID(clazz, "mPer", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.per())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::preBer: { + jfieldID field = env->GetFieldID(clazz, "mPerBer", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.preBer())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::signalQuality: { + jfieldID field = env->GetFieldID(clazz, "mSignalQuality", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.signalQuality())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::signalStrength: { + jfieldID field = env->GetFieldID(clazz, "mSignalStrength", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.signalStrength())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::symbolRate: { + jfieldID field = env->GetFieldID(clazz, "mSymbolRate", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.symbolRate())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::innerFec: { + jfieldID field = env->GetFieldID(clazz, "mInnerFec", "Ljava/lang/Long;"); + jclass longClazz = env->FindClass("java/lang/Long"); + jmethodID initLong = env->GetMethodID(longClazz, "<init>", "(J)V"); + jobject newLongObj = env->NewObject( + longClazz, initLong, static_cast<jlong>(s.innerFec())); + env->SetObjectField(statusObj, field, newLongObj); + break; + } + case FrontendStatus::hidl_discriminator::modulation: { + jfieldID field = env->GetFieldID(clazz, "mModulation", "Ljava/lang/Integer;"); + FrontendModulationStatus modulation = s.modulation(); + jint intModulation; + bool valid = true; + switch(modulation.getDiscriminator()) { + case FrontendModulationStatus::hidl_discriminator::dvbc: { + intModulation = static_cast<jint>(modulation.dvbc()); + break; + } + case FrontendModulationStatus::hidl_discriminator::dvbs: { + intModulation = static_cast<jint>(modulation.dvbs()); + break; + } + case FrontendModulationStatus::hidl_discriminator::isdbs: { + intModulation = static_cast<jint>(modulation.isdbs()); + break; + } + case FrontendModulationStatus::hidl_discriminator::isdbs3: { + intModulation = static_cast<jint>(modulation.isdbs3()); + break; + } + case FrontendModulationStatus::hidl_discriminator::isdbt: { + intModulation = static_cast<jint>(modulation.isdbt()); + break; + } + default: { + valid = false; + break; + } + } + if (valid) { + jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation); + env->SetObjectField(statusObj, field, newIntegerObj); + } + break; + } + case FrontendStatus::hidl_discriminator::inversion: { + jfieldID field = env->GetFieldID(clazz, "mInversion", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.inversion())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::lnbVoltage: { + jfieldID field = env->GetFieldID(clazz, "mLnbVoltage", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.lnbVoltage())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::plpId: { + jfieldID field = env->GetFieldID(clazz, "mPlpId", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.plpId())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::isEWBS: { + jfieldID field = env->GetFieldID(clazz, "mIsEwbs", "Ljava/lang/Boolean;"); + jobject newBooleanObj = env->NewObject( + booleanClazz, initBoolean, static_cast<jboolean>(s.isEWBS())); + env->SetObjectField(statusObj, field, newBooleanObj); + break; + } + case FrontendStatus::hidl_discriminator::agc: { + jfieldID field = env->GetFieldID(clazz, "mAgc", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.agc())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::isLnaOn: { + jfieldID field = env->GetFieldID(clazz, "mIsLnaOn", "Ljava/lang/Boolean;"); + jobject newBooleanObj = env->NewObject( + booleanClazz, initBoolean, static_cast<jboolean>(s.isLnaOn())); + env->SetObjectField(statusObj, field, newBooleanObj); + break; + } + case FrontendStatus::hidl_discriminator::isLayerError: { + jfieldID field = env->GetFieldID(clazz, "mIsLayerErrors", "[Z"); + hidl_vec<bool> layerErr = s.isLayerError(); + + jbooleanArray valObj = env->NewBooleanArray(layerErr.size()); + + for (size_t i = 0; i < layerErr.size(); i++) { + jboolean x = layerErr[i]; + env->SetBooleanArrayRegion(valObj, i, 1, &x); + } + env->SetObjectField(statusObj, field, valObj); + break; + } + case FrontendStatus::hidl_discriminator::mer: { + jfieldID field = env->GetFieldID(clazz, "mMer", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.mer())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::freqOffset: { + jfieldID field = env->GetFieldID(clazz, "mFreqOffset", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.freqOffset())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::hierarchy: { + jfieldID field = env->GetFieldID(clazz, "mHierarchy", "Ljava/lang/Integer;"); + jobject newIntegerObj = env->NewObject( + intClazz, initInt, static_cast<jint>(s.hierarchy())); + env->SetObjectField(statusObj, field, newIntegerObj); + break; + } + case FrontendStatus::hidl_discriminator::isRfLocked: { + jfieldID field = env->GetFieldID(clazz, "mIsRfLocked", "Ljava/lang/Boolean;"); + jobject newBooleanObj = env->NewObject( + booleanClazz, initBoolean, static_cast<jboolean>(s.isRfLocked())); + env->SetObjectField(statusObj, field, newBooleanObj); + break; + } + case FrontendStatus::hidl_discriminator::plpInfo: { + jfieldID field = env->GetFieldID(clazz, "mPlpInfo", + "[Landroid/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo;"); + jclass plpClazz = env->FindClass( + "android/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo"); + jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZI)V"); + + hidl_vec<FrontendStatusAtsc3PlpInfo> plpInfos = s.plpInfo(); + + jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, NULL); + for (int i = 0; i < plpInfos.size(); i++) { + auto info = plpInfos[i]; + jint plpId = (jint) info.plpId; + jboolean isLocked = (jboolean) info.isLocked; + jint uec = (jint) info.uec; + + jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec); + env->SetObjectArrayElement(valObj, i, plpObj); + } + + env->SetObjectField(statusObj, field, valObj); + break; + } + default: { + break; + } + } + } + + return statusObj; +} + } // namespace android //////////////////////////////////////////////////////////////////////////////// @@ -2086,8 +2338,10 @@ static int android_media_tv_Tuner_set_lna(JNIEnv *env, jobject thiz, jboolean en return tuner->setLna(enable); } -static jobject android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) { - return NULL; +static jobject android_media_tv_Tuner_get_frontend_status( + JNIEnv* env, jobject thiz, jintArray types) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->getFrontendStatus(types); } static jobject android_media_tv_Tuner_get_av_sync_hw_id( diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 7e860b9c872f..e6f10b24c840 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -187,6 +187,7 @@ struct JTuner : public RefBase { jobject openDescrambler(); jobject openDvr(DvrType type, jlong bufferSize); jobject getDemuxCaps(); + jobject getFrontendStatus(jintArray types); protected: Result openDemux(); diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java index c51c8fa73c4e..8d18b77700b5 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java @@ -70,7 +70,9 @@ public class BluetoothPacketDecoder extends PacketDecoder { } byte header = buffer[0]; - if ((header & 0xC0) != 0x80) { + // Check for the header bit 7. + // Ignore the reserved bit 6. + if ((header & 0x80) != 0x80) { Log.e(TAG, "packet does not start with header"); return; } 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/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 2fde87c08ad5..d3d04e5a31d0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2813,6 +2813,7 @@ public class SettingsProvider extends ContentProvider { if (settingsState.isNewConfigBannedLocked(prefix, keyValues)) { return false; } + settingsState.unbanAllConfigIfBannedConfigUpdatedLocked(prefix); List<String> changedSettings = settingsState.setSettingsLocked(prefix, keyValues, packageName); if (!changedSettings.isEmpty()) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index d93c0150410d..6b8219ea9c70 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -459,6 +459,16 @@ final class SettingsState { } @GuardedBy("mLock") + public void unbanAllConfigIfBannedConfigUpdatedLocked(String prefix) { + // If the prefix updated is a banned namespace, clear mNamespaceBannedHashes + // to unban all unbanned namespaces. + if (mNamespaceBannedHashes.get(prefix) != null) { + mNamespaceBannedHashes.clear(); + scheduleWriteIfNeededLocked(); + } + } + + @GuardedBy("mLock") public void banConfigurationLocked(String prefix, Map<String, String> keyValues) { if (prefix == null || keyValues.isEmpty()) { return; 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/drawable/ic_empty_bubble_overflow_dark.xml b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_dark.xml new file mode 100644 index 000000000000..8f8f1b664692 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_dark.xml @@ -0,0 +1,162 @@ +<!-- +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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="412dp" + android:height="300dp" + android:viewportWidth="412" + android:viewportHeight="300"> + <group> + <clip-path + android:pathData="M206,150m-150,0a150,150 0,1 1,300 0a150,150 0,1 1,-300 0"/> + <path + android:pathData="M296,105.2h-9.6l-3.1,-2.5l-3.1,2.5H116c-1.7,0 -3,1.3 -3,3v111.7c0,1.7 1.3,3 3,3h180c1.7,0 3,-1.3 3,-3V108.2C299,106.6 297.7,105.2 296,105.2C296,105.2 296,105.2 296,105.2z" + android:fillColor="#3C4043"/> + <path + android:strokeWidth="1" + android:pathData="M252.4,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#D2E3FC" + android:strokeColor="#4285F4"/> + <path + android:pathData="M261.9,95.7m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0" + android:fillColor="#4285F4"/> + <path + android:strokeWidth="1" + android:pathData="M160.6,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#FAD2CF" + android:strokeColor="#EA4335"/> + <path + android:pathData="M170.1,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#EA4335"/> + <path + android:strokeWidth="1" + android:pathData="M192.1,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#FEEFC3" + android:strokeColor="#FBBC04"/> + <path + android:strokeWidth="1" + android:pathData="M221.8,85.4m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#CEEAD6" + android:strokeColor="#34A853"/> + <path + android:pathData="M201.6,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#FBBC04"/> + <path + android:pathData="M231.4,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#34A853"/> + <path + android:pathData="M282.8,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#3C4043"/> + <path + android:pathData="M278.7,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M282.8,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M286.9,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M129.2,72.9h-3.4l0.3,1c1,-0.3 2.1,-0.4 3.2,-0.4v-1V72.9zM122.6,74.8c-0.5,0.3 -1,0.6 -1.4,1l0,0l0,0l0,0l0,0h-0.6l0,0l0,0l0,0l0,0l0,0l0,0c-0.2,0.2 -0.3,0.3 -0.4,0.5l0.8,0.7c0.7,-0.8 1.5,-1.5 2.4,-2.1l-0.5,-0.8L122.6,74.8zM118,80L118,80L118,80L118,80L118,80L118,80L118,80L118,80c-0.5,1 -0.8,2 -1,3l1,0.2c0.2,-1 0.5,-2 1,-3L118,80zM117.8,86.7l-1,0.1c0.1,0.6 0.2,1.1 0.3,1.7l0,0l0,0h0.1l0,0l0,0l0,0l0,0c0.1,0.5 0.3,0.9 0.5,1.4l0.9,-0.4c-0.4,-1 -0.7,-2 -0.8,-3.1L117.8,86.7zM120.2,92.5l-0.8,0.6l0.2,0.3l0,0l0,0l0,0l0,0h0.3l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0.4,0.4l0,0l0,0l0,0l0,0h0.1l0,0l0,0l0,0l0,0l0,0h0.5l0,0l0,0l0,0l0,0l0,0l0,0l0.6,0.4l0.6,-0.8c-0.9,-0.6 -1.7,-1.4 -2.3,-2.2L120.2,92.5zM125.4,96.2l-0.3,0.9c1.1,0.4 2.2,0.6 3.4,0.7l0.1,-1C127.5,96.7 126.4,96.5 125.4,96.2zM134.7,95.4c-0.9,0.5 -2,0.9 -3,1.1l0.2,1h0.4c1,-0.3 2,-0.6 2.9,-1.2l-0.5,-0.9L134.7,95.4zM139.2,90.9c-0.5,0.9 -1.2,1.8 -1.9,2.5l0.7,0.7v-0.1h0.2l0,0l0,0c0.7,-0.7 1.3,-1.6 1.8,-2.4l-0.9,-0.5L139.2,90.9zM141.6,84.7h-1c0,0.2 0,0.4 0,0.6c0,0.9 -0.1,1.7 -0.3,2.6l1,0.2c0.1,-0.4 0.2,-0.8 0.2,-1.2l0,0v-0.1l0,0v-0.1l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0c0,-0.2 0,-0.3 0,-0.5l0,0L141.6,84.7zM139.3,78.2l-0.8,0.6c0.6,0.9 1.1,1.8 1.5,2.8l0.9,-0.3c-0.1,-0.2 -0.2,-0.4 -0.2,-0.7l0,0l0,0h-0.1l0,0l0,0l0,0l0,0l0,0l0,0c-0.3,-0.7 -0.7,-1.4 -1.1,-2l0,0l0,0l0,0l0,0l0,0l0,0l0,0L139.3,78.2zM134,73.9l-0.4,0.9c1,0.4 1.9,1 2.7,1.6l0.6,-0.8l0,0l0,0l0,0l0,0l0,0c-0.3,-0.3 -0.7,-0.5 -1,-0.7l0,0h-0.1h-0.6c-0.4,-0.2 -0.8,-0.4 -1.2,-0.6L134,73.9zM129.2,72.9v1c0.4,0 0.9,0 1.3,0.1l0.1,-1l-0.9,-0.1L129.2,72.9L129.2,72.9z" + android:fillColor="#34A853"/> + <path + android:pathData="M206,252m-11.7,0a11.7,11.7 0,1 1,23.4 0a11.7,11.7 0,1 1,-23.4 0" + android:fillColor="#F1F3F4"/> + <path + android:pathData="M201.7,247.7L210.3,256.3" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#202124" + android:strokeLineCap="round"/> + <path + android:pathData="M210.3,247.7L201.7,256.3" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#202124" + android:strokeLineCap="round"/> + <path + android:strokeWidth="1" + android:pathData="M205.3,221.9m-10.4,0a10.4,10.4 0,1 1,20.8 0a10.4,10.4 0,1 1,-20.8 0" + android:fillColor="#CEEAD6" + android:strokeColor="#34A853"/> + <path + android:pathData="M481.4,292.2c48,58.3 119.8,125.8 58.6,162.9c-38.7,23.5 -53.9,24 -98.3,33.2c-43.8,9.1 -93.6,-89.8 -101.1,-134.5C329.6,288.6 452.6,257.2 481.4,292.2z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M458.2,320.7l-21.1,-71.4L400.5,193c-2.7,-5.1 -1.2,-11.4 3.5,-14.7l0,0c2.8,-2 6.6,-1.5 8.8,1.1c0,0 40.6,38.4 53.2,61.1l81.5,134.8l-69.9,-39.1L458.2,320.7z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M403.8,184.8l5.4,6.9c1.2,1.5 3.3,1.9 4.9,0.9l3,-1.8" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M420.9,325.8l-37.8,-88.6l-58.4,-37.8c-5.7,-5.4 -7.4,-13.8 -4.2,-21l0,0c2,-4.6 7.4,-6.7 12,-4.6c0.2,0.1 0.4,0.2 0.7,0.3c0,0 70.7,36.3 81.5,48.3l59.8,95.5l-49.9,24.9L420.9,325.8z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M324.6,183.9l8,6.2c2.1,1.7 5.2,1.4 7,-0.6l2.9,-3.3" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M392.4,231c3.8,-5.1 9.1,-8.9 15.1,-10.9" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M401.3,283.8L405.8,292.6" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M378.2,346.9l-34.7,-75.6l-60,-61.2c-6.3,-4.7 -9,-12.8 -6.7,-20.4l0,0c1.5,-4.8 6.5,-7.5 11.3,-6c0.2,0.1 0.4,0.1 0.7,0.2c0,0 73.5,48.2 82.6,61.7l64.1,95.7l-40.3,23.5L378.2,346.9z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M280.8,196.6l7.6,4.6c2.6,1.6 5.9,1.1 7.9,-1.1l4.1,-4.5" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M347.5,251c3.8,-5.1 9.1,-8.9 15.1,-10.9" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M207.2,234.1c-8.8,-11 4.7,-31.5 19.8,-19c17.7,14.7 74.7,64.3 74.7,64.3l103.8,101.8c0,0 -36.4,53.8 -44.5,42.3C287.8,319.3 234.4,267.9 207.2,234.1z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M209.6,226.2l9.3,9.5c1,0.8 3,0.4 3.1,-1c0.2,-2.2 4.6,-6.2 7,-6.6c1.1,-0.3 1.7,-1.4 1.4,-2.4c-0.1,-0.2 -0.2,-0.4 -0.3,-0.6l-4.4,-3.9" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M284.1,296.2c3.1,-5.5 7.8,-10 13.5,-12.8" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + </group> + <path + android:pathData="M206,4c80.6,0 146,65.4 146,146c0,38.7 -15.4,75.9 -42.8,103.2c-57,57 -149.5,57 -206.5,0s-57,-149.5 0,-206.5C130.1,19.3 167.3,3.9 206,4M206,0C123.2,0 56,67.2 56,150s67.2,150 150,150s150,-67.2 150,-150S288.8,0 206,0z" + android:fillColor="#D2E3FC"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_light.xml b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_light.xml new file mode 100644 index 000000000000..5e02f67700e7 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_light.xml @@ -0,0 +1,162 @@ +<!-- +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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="412dp" + android:height="300dp" + android:viewportWidth="412" + android:viewportHeight="300"> + <group> + <clip-path + android:pathData="M206,150m-150,0a150,150 0,1 1,300 0a150,150 0,1 1,-300 0"/> + <path + android:pathData="M296,105.2h-9.6l-3.1,-2.5l-3.1,2.5H116c-1.7,0 -3,1.3 -3,3v111.7c0,1.7 1.3,3 3,3h180c1.7,0 3,-1.3 3,-3V108.2C299,106.6 297.7,105.2 296,105.2L296,105.2z" + android:fillColor="#F1F3F4"/> + <path + android:strokeWidth="1" + android:pathData="M252.4,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#D2E3FC" + android:strokeColor="#4285F4"/> + <path + android:pathData="M261.9,95.7m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0" + android:fillColor="#4285F4"/> + <path + android:strokeWidth="1" + android:pathData="M160.6,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#FAD2CF" + android:strokeColor="#EA4335"/> + <path + android:pathData="M170.1,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#EA4335"/> + <path + android:strokeWidth="1" + android:pathData="M192.1,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#FEEFC3" + android:strokeColor="#FBBC04"/> + <path + android:strokeWidth="1" + android:pathData="M221.8,85.4m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#CEEAD6" + android:strokeColor="#34A853"/> + <path + android:pathData="M201.6,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#FBBC04"/> + <path + android:pathData="M231.4,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#34A853"/> + <path + android:pathData="M282.8,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#F1F3F4"/> + <path + android:pathData="M278.7,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#3474E0"/> + <path + android:pathData="M282.8,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#3474E0"/> + <path + android:pathData="M286.9,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#3474E0"/> + <path + android:pathData="M129.2,72.9h-3.4l0.3,1c1,-0.3 2.1,-0.4 3.2,-0.4v-1v0.4H129.2zM122.6,74.8c-0.5,0.3 -1,0.6 -1.4,1l0,0l0,0l0,0l0,0h-0.6l0,0l0,0l0,0l0,0l0,0l0,0c-0.2,0.2 -0.3,0.3 -0.4,0.5L121,77c0.7,-0.8 1.5,-1.5 2.4,-2.1l-0.5,-0.8L122.6,74.8zM118,80L118,80L118,80L118,80L118,80L118,80L118,80L118,80c-0.5,1 -0.8,2 -1,3l1,0.2c0.2,-1 0.5,-2 1,-3L118,80zM117.8,86.7l-1,0.1c0.1,0.6 0.2,1.1 0.3,1.7l0,0l0,0h0.1l0,0l0,0l0,0l0,0c0.1,0.5 0.3,0.9 0.5,1.4l0.9,-0.4c-0.4,-1 -0.7,-2 -0.8,-3.1V86.7zM120.2,92.5l-0.8,0.6l0.2,0.3l0,0l0,0l0,0l0,0h0.3l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0.4,0.4l0,0l0,0l0,0l0,0h0.1l0,0l0,0l0,0l0,0l0,0h0.5l0,0l0,0l0,0l0,0l0,0l0,0l0.6,0.4l0.6,-0.8c-0.9,-0.6 -1.7,-1.4 -2.3,-2.2L120.2,92.5zM125.4,96.2l-0.3,0.9c1.1,0.4 2.2,0.6 3.4,0.7l0.1,-1C127.5,96.7 126.4,96.5 125.4,96.2zM134.7,95.4c-0.9,0.5 -2,0.9 -3,1.1l0.2,1h0.4c1,-0.3 2,-0.6 2.9,-1.2L134.7,95.4L134.7,95.4zM139.2,90.9c-0.5,0.9 -1.2,1.8 -1.9,2.5l0.7,0.7V94h0.2l0,0l0,0c0.7,-0.7 1.3,-1.6 1.8,-2.4l-0.9,-0.5L139.2,90.9zM141.6,84.7h-1c0,0.2 0,0.4 0,0.6c0,0.9 -0.1,1.7 -0.3,2.6l1,0.2c0.1,-0.4 0.2,-0.8 0.2,-1.2l0,0v-0.1l0,0v-0.1l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0c0,-0.2 0,-0.3 0,-0.5l0,0L141.6,84.7zM139.3,78.2l-0.8,0.6c0.6,0.9 1.1,1.8 1.5,2.8l0.9,-0.3c-0.1,-0.2 -0.2,-0.4 -0.2,-0.7l0,0l0,0h-0.1l0,0l0,0l0,0l0,0l0,0l0,0c-0.3,-0.7 -0.7,-1.4 -1.1,-2l0,0l0,0l0,0l0,0l0,0l0,0l0,0L139.3,78.2zM134,73.9l-0.4,0.9c1,0.4 1.9,1 2.7,1.6l0.6,-0.8l0,0l0,0l0,0l0,0l0,0c-0.3,-0.3 -0.7,-0.5 -1,-0.7l0,0h-0.1h-0.6c-0.4,-0.2 -0.8,-0.4 -1.2,-0.6V73.9zM129.2,72.9v1c0.4,0 0.9,0 1.3,0.1l0.1,-1l-0.9,-0.1H129.2L129.2,72.9z" + android:fillColor="#34A853"/> + <path + android:pathData="M206,252m-11.7,0a11.7,11.7 0,1 1,23.4 0a11.7,11.7 0,1 1,-23.4 0" + android:fillColor="#9AA0A6"/> + <path + android:pathData="M201.7,247.7L210.3,256.3" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#F1F3F4" + android:strokeLineCap="round"/> + <path + android:pathData="M210.3,247.7L201.7,256.3" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#F1F3F4" + android:strokeLineCap="round"/> + <path + android:strokeWidth="1" + android:pathData="M205.3,221.9m-10.4,0a10.4,10.4 0,1 1,20.8 0a10.4,10.4 0,1 1,-20.8 0" + android:fillColor="#CEEAD6" + android:strokeColor="#34A853"/> + <path + android:pathData="M481.4,292.2c48,58.3 119.8,125.8 58.6,162.9c-38.7,23.5 -53.9,24 -98.3,33.2c-43.8,9.1 -93.6,-89.8 -101.1,-134.5C329.6,288.6 452.6,257.2 481.4,292.2z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M458.2,320.7l-21.1,-71.4L400.5,193c-2.7,-5.1 -1.2,-11.4 3.5,-14.7l0,0c2.8,-2 6.6,-1.5 8.8,1.1c0,0 40.6,38.4 53.2,61.1l81.5,134.8l-69.9,-39.1L458.2,320.7z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M403.8,184.8l5.4,6.9c1.2,1.5 3.3,1.9 4.9,0.9l3,-1.8" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M420.9,325.8l-37.8,-88.6l-58.4,-37.8c-5.7,-5.4 -7.4,-13.8 -4.2,-21l0,0c2,-4.6 7.4,-6.7 12,-4.6c0.2,0.1 0.4,0.2 0.7,0.3c0,0 70.7,36.3 81.5,48.3l59.8,95.5l-49.9,24.9L420.9,325.8z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M324.6,183.9l8,6.2c2.1,1.7 5.2,1.4 7,-0.6l2.9,-3.3" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M392.4,231c3.8,-5.1 9.1,-8.9 15.1,-10.9" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M401.3,283.8L405.8,292.6" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M378.2,346.9l-34.7,-75.6l-60,-61.2c-6.3,-4.7 -9,-12.8 -6.7,-20.4l0,0c1.5,-4.8 6.5,-7.5 11.3,-6c0.2,0.1 0.4,0.1 0.7,0.2c0,0 73.5,48.2 82.6,61.7l64.1,95.7l-40.3,23.5L378.2,346.9z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M280.8,196.6l7.6,4.6c2.6,1.6 5.9,1.1 7.9,-1.1l4.1,-4.5" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M347.5,251c3.8,-5.1 9.1,-8.9 15.1,-10.9" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M207.2,234.1c-8.8,-11 4.7,-31.5 19.8,-19c17.7,14.7 74.7,64.3 74.7,64.3l103.8,101.8c0,0 -36.4,53.8 -44.5,42.3C287.8,319.3 234.4,267.9 207.2,234.1z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M209.6,226.2l9.3,9.5c1,0.8 3,0.4 3.1,-1c0.2,-2.2 4.6,-6.2 7,-6.6c1.1,-0.3 1.7,-1.4 1.4,-2.4c-0.1,-0.2 -0.2,-0.4 -0.3,-0.6l-4.4,-3.9" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M284.1,296.2c3.1,-5.5 7.8,-10 13.5,-12.8" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + </group> + <path + android:pathData="M206,4c80.6,0 146,65.4 146,146c0,38.7 -15.4,75.9 -42.8,103.2c-57,57 -149.5,57 -206.5,0s-57,-149.5 0,-206.5C130.1,19.3 167.3,3.9 206,4M206,0C123.2,0 56,67.2 56,150s67.2,150 150,150s150,-67.2 150,-150S288.8,0 206,0z" + android:fillColor="#D2E3FC"/> +</vector> diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml index 65b04fd8fd99..306061911f8d 100644 --- a/packages/SystemUI/res/layout/bubble_overflow_activity.xml +++ b/packages/SystemUI/res/layout/bubble_overflow_activity.xml @@ -20,6 +20,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:paddingTop="@dimen/bubble_overflow_padding" + android:paddingLeft="@dimen/bubble_overflow_padding" + android:paddingRight="@dimen/bubble_overflow_padding" android:orientation="vertical" android:layout_gravity="center_horizontal"> @@ -39,6 +41,13 @@ android:orientation="vertical" android:gravity="center"> + <ImageView + android:layout_width="@dimen/bubble_empty_overflow_image_height" + android:layout_height="@dimen/bubble_empty_overflow_image_height" + android:id="@+id/bubble_overflow_empty_state_image" + android:scaleType="fitCenter" + android:layout_gravity="center"/> + <TextView android:id="@+id/bubble_overflow_empty_title" android:text="@string/bubble_overflow_empty_title" @@ -57,6 +66,7 @@ android:text="@string/bubble_overflow_empty_subtitle" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingBottom="@dimen/bubble_empty_overflow_subtitle_padding" android:gravity="center"/> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/bubble_overflow_view.xml b/packages/SystemUI/res/layout/bubble_overflow_view.xml index d67c81d67ada..88a05ec5824a 100644 --- a/packages/SystemUI/res/layout/bubble_overflow_view.xml +++ b/packages/SystemUI/res/layout/bubble_overflow_view.xml @@ -37,5 +37,6 @@ android:layout_height="wrap_content" android:maxLines="1" android:layout_gravity="center" + android:paddingTop="@dimen/bubble_overflow_text_padding" android:gravity="center"/> </LinearLayout> 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/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml index 7dcc46c3ca09..ec004296ff9d 100644 --- a/packages/SystemUI/res/layout/people_strip.xml +++ b/packages/SystemUI/res/layout/people_strip.xml @@ -43,6 +43,7 @@ android:forceHasOverlappingRendering="false"> <TextView + android:id="@+id/header_label" style="@style/TextAppearance.NotificationSectionHeaderButton" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 23be78bd6a77..5b213edd5f0f 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> @@ -1147,11 +1148,17 @@ <!-- Default (and minimum) height of the expanded view shown when the bubble is expanded --> <dimen name="bubble_expanded_default_height">180dp</dimen> <!-- Default height of bubble overflow --> - <dimen name="bubble_overflow_height">380dp</dimen> + <dimen name="bubble_overflow_height">460dp</dimen> <!-- Bubble overflow padding when there are no bubbles --> <dimen name="bubble_overflow_empty_state_padding">16dp</dimen> <!-- Padding of container for overflow bubbles --> - <dimen name="bubble_overflow_padding">5dp</dimen> + <dimen name="bubble_overflow_padding">15dp</dimen> + <!-- Padding of label for bubble overflow view --> + <dimen name="bubble_overflow_text_padding">7dp</dimen> + <!-- Height of bubble overflow empty state illustration --> + <dimen name="bubble_empty_overflow_image_height">200dp</dimen> + <!-- Padding of bubble overflow empty state subtitle --> + <dimen name="bubble_empty_overflow_subtitle_padding">50dp</dimen> <!-- Height of the triangle that points to the expanded bubble --> <dimen name="bubble_pointer_height">4dp</dimen> <!-- Width of the triangle that points to the expanded bubble --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 80fd826f28c6..35ad422c56b8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -26,7 +26,7 @@ import com.android.systemui.shared.recents.IPinnedStackAnimationListener; /** * Temporary callbacks into SystemUI. - * Next id = 25 + * Next id = 26 */ interface ISystemUiProxy { @@ -140,4 +140,10 @@ interface ISystemUiProxy { * Sets listener to get pinned stack animation callbacks. */ void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) = 24; + + /** + * Notifies that quickstep will switch to a new task + * @param rotation indicates which Surface.Rotation the gesture was started in + */ + void onQuickSwitchToNewTask(int rotation) = 25; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java index 29100ef8f70f..8bd7c790682d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java @@ -18,6 +18,7 @@ package com.android.systemui.shared.system; import android.content.Context; import android.graphics.PixelFormat; +import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.IBinder; import android.util.Size; @@ -59,6 +60,7 @@ public class SurfaceViewRequestReceiver { if (mSurfaceControlViewHost != null) { mSurfaceControlViewHost.die(); } + SurfaceControl surfaceControl = SurfaceViewRequestUtils.getSurfaceControl(bundle); if (surfaceControl != null) { if (viewSize == null) { @@ -70,8 +72,10 @@ public class SurfaceViewRequestReceiver { WindowlessWindowManager windowlessWindowManager = new WindowlessWindowManager(context.getResources().getConfiguration(), surfaceControl, hostToken); + DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); mSurfaceControlViewHost = new SurfaceControlViewHost(context, - context.getDisplayNoVerify(), windowlessWindowManager); + dm.getDisplay(SurfaceViewRequestUtils.getDisplayId(bundle)), + windowlessWindowManager); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( viewSize.getWidth(), diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java index 4409276f8c27..6742a4dc06b7 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java @@ -26,30 +26,38 @@ import android.view.SurfaceView; public class SurfaceViewRequestUtils { private static final String KEY_HOST_TOKEN = "host_token"; private static final String KEY_SURFACE_CONTROL = "surface_control"; + private static final String KEY_DISPLAY_ID = "display_id"; /** Creates a SurfaceView based bundle that stores the input host token and surface control. */ public static Bundle createSurfaceBundle(SurfaceView surfaceView) { Bundle bundle = new Bundle(); bundle.putBinder(KEY_HOST_TOKEN, surfaceView.getHostToken()); bundle.putParcelable(KEY_SURFACE_CONTROL, surfaceView.getSurfaceControl()); + bundle.putInt(KEY_DISPLAY_ID, surfaceView.getDisplay().getDisplayId()); return bundle; } /** * Retrieves the SurfaceControl from a bundle created by * {@link #createSurfaceBundle(SurfaceView)}. - **/ + */ public static SurfaceControl getSurfaceControl(Bundle bundle) { return bundle.getParcelable(KEY_SURFACE_CONTROL); } /** - * Retrieves the input token from a bundle created by - * {@link #createSurfaceBundle(SurfaceView)}. - **/ + * Retrieves the input token from a bundle created by {@link #createSurfaceBundle(SurfaceView)}. + */ public static @Nullable IBinder getHostToken(Bundle bundle) { return bundle.getBinder(KEY_HOST_TOKEN); } + /** + * Retrieves the display id from a bundle created by {@link #createSurfaceBundle(SurfaceView)}. + */ + public static int getDisplayId(Bundle bundle) { + return bundle.getInt(KEY_DISPLAY_ID); + } + private SurfaceViewRequestUtils() {} } 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/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index 2231d11b7bc2..37841f24a3cf 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -21,18 +21,17 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.app.Activity; -import android.app.Notification; -import android.app.Person; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Bundle; -import android.os.Parcelable; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -55,6 +54,7 @@ public class BubbleOverflowActivity extends Activity { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES; private LinearLayout mEmptyState; + private ImageView mEmptyStateImage; private BubbleController mBubbleController; private BubbleOverflowAdapter mAdapter; private RecyclerView mRecyclerView; @@ -73,6 +73,7 @@ public class BubbleOverflowActivity extends Activity { mEmptyState = findViewById(R.id.bubble_overflow_empty_state); mRecyclerView = findViewById(R.id.bubble_overflow_recycler); + mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image); Resources res = getResources(); final int columns = res.getInteger(R.integer.bubbles_overflow_columns); @@ -81,11 +82,15 @@ public class BubbleOverflowActivity extends Activity { DisplayMetrics displayMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); - final int viewWidth = displayMetrics.widthPixels / columns; + final int recyclerViewWidth = (displayMetrics.widthPixels + - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding)); + final int viewWidth = recyclerViewWidth / columns; final int maxOverflowBubbles = res.getInteger(R.integer.bubbles_max_overflow); final int rows = (int) Math.ceil((double) maxOverflowBubbles / columns); - final int viewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height) / rows; + final int recyclerViewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height) + - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding); + final int viewHeight = recyclerViewHeight / rows; mAdapter = new BubbleOverflowAdapter(mOverflowBubbles, mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight); @@ -94,6 +99,31 @@ public class BubbleOverflowActivity extends Activity { mBubbleController.setOverflowCallback(() -> { onDataChanged(mBubbleController.getOverflowBubbles()); }); + onThemeChanged(); + } + + /** + * Handle theme changes. + */ + void onThemeChanged() { + final int mode = + getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + switch (mode) { + case Configuration.UI_MODE_NIGHT_NO: + if (DEBUG_OVERFLOW) { + Log.d(TAG, "Set overflow UI to light mode"); + } + mEmptyStateImage.setImageDrawable( + getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_light)); + break; + case Configuration.UI_MODE_NIGHT_YES: + if (DEBUG_OVERFLOW) { + Log.d(TAG, "Set overflow UI to dark mode"); + } + mEmptyStateImage.setImageDrawable( + getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_dark)); + break; + } } void setBackgroundColor() { @@ -134,6 +164,7 @@ public class BubbleOverflowActivity extends Activity { @Override public void onResume() { super.onResume(); + onThemeChanged(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 6a7b0da0d8d8..eff693436451 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -713,7 +713,7 @@ public class BubbleStackView extends FrameLayout { } else { mBubbleContainer.removeView(mBubbleOverflow.getBtn()); mBubbleOverflow.updateIcon(mContext, this); - overflowBtnIndex = mBubbleContainer.getChildCount() - 1; + overflowBtnIndex = mBubbleContainer.getChildCount(); } mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); 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/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 15c9dbad1680..8cff20ac31f7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -37,7 +37,6 @@ import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.os.RemoteException; import android.util.Log; import android.util.Size; import android.view.SurfaceControl; @@ -541,7 +540,12 @@ public class PipTaskOrganizer extends TaskOrganizer { return null; } final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout; - return new Size(windowLayout.minWidth, windowLayout.minHeight); + // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout> + // without minWidth/minHeight + if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) { + return new Size(windowLayout.minWidth, windowLayout.minHeight); + } + return null; } private float getAspectRatioOrDefault(@Nullable PictureInPictureParams params) { 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/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index df85ed524536..66bc177da81d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -55,6 +55,7 @@ import android.os.UserHandle; import android.util.Log; import android.view.InputMonitor; import android.view.MotionEvent; +import android.view.Surface; import android.view.accessibility.AccessibilityManager; import com.android.internal.policy.ScreenDecorationsUtils; @@ -416,6 +417,19 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + @Override + public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) { + if (!verifyCaller("onQuickSwitchToNewTask")) { + return; + } + long token = Binder.clearCallingIdentity(); + try { + mHandler.post(() -> notifyQuickSwitchToNewTask(rotation)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -785,6 +799,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + private void notifyQuickSwitchToNewTask(@Surface.Rotation int rotation) { + for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { + mConnectionCallbacks.get(i).onQuickSwitchToNewTask(rotation); + } + } + public void notifyQuickScrubStarted() { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onQuickScrubStarted(); @@ -850,6 +870,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public interface OverviewProxyListener { default void onConnectionChanged(boolean isConnected) {} default void onQuickStepStarted() {} + default void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {} default void onOverviewShown(boolean fromHome) {} default void onQuickScrubStarted() {} /** Notify changes in the nav bar button alpha */ 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/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index fe2f1f3eefc5..1297f996b743 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -177,12 +177,32 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle currentUserId); ent.setSensitive(sensitive, deviceSensitive); ent.getRow().setNeedsRedaction(needsRedaction); - if (mGroupManager.isChildInGroupWithSummary(ent.getSbn())) { - NotificationEntry summary = mGroupManager.getGroupSummary(ent.getSbn()); - List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(summary); + boolean isChildInGroup = mGroupManager.isChildInGroupWithSummary(ent.getSbn()); + + boolean groupChangesAllowed = mVisualStabilityManager.areGroupChangesAllowed() + || !ent.hasFinishedInitialization(); + NotificationEntry parent = mGroupManager.getGroupSummary(ent.getSbn()); + if (!groupChangesAllowed) { + // We don't to change groups while the user is looking at them + boolean wasChildInGroup = ent.isChildInGroup(); + if (isChildInGroup && !wasChildInGroup) { + isChildInGroup = wasChildInGroup; + mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager); + } else if (!isChildInGroup && wasChildInGroup) { + // We allow grouping changes if the group was collapsed + if (mGroupManager.isLogicalGroupExpanded(ent.getSbn())) { + isChildInGroup = wasChildInGroup; + parent = ent.getRow().getNotificationParent().getEntry(); + mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager); + } + } + } + + if (isChildInGroup) { + List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent); if (orderedChildren == null) { orderedChildren = new ArrayList<>(); - mTmpChildOrderMap.put(summary, orderedChildren); + mTmpChildOrderMap.put(parent, orderedChildren); } orderedChildren.add(ent); } else { @@ -205,7 +225,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle } for (ExpandableNotificationRow viewToRemove : viewsToRemove) { - if (mGroupManager.isChildInGroupWithSummary(viewToRemove.getEntry().getSbn())) { + if (mEntryManager.getPendingOrActiveNotif(viewToRemove.getEntry().getKey()) != null) { // we are only transferring this notification to its parent, don't generate an // animation mListContainer.setChildTransferInProgress(true); 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/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index 7ef1d0eba3f1..1696f0715865 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -26,6 +26,7 @@ import com.android.internal.widget.ConversationLayout import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationContentView +import com.android.systemui.statusbar.phone.NotificationGroupManager import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject import javax.inject.Singleton @@ -60,6 +61,7 @@ class ConversationNotificationProcessor @Inject constructor( @Singleton class ConversationNotificationManager @Inject constructor( private val notificationEntryManager: NotificationEntryManager, + private val notificationGroupManager: NotificationGroupManager, private val context: Context ) { // Need this state to be thread safe, since it's accessed from the ui thread @@ -81,10 +83,19 @@ class ConversationNotificationManager @Inject constructor( if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) { val important = ranking.channel.isImportantConversation + var changed = false entry.row?.layouts?.asSequence() ?.flatMap(::getLayouts) ?.mapNotNull { it as? ConversationLayout } - ?.forEach { it.setIsImportantConversation(important) } + ?.forEach { + if (important != it.isImportantConversation) { + it.setIsImportantConversation(important) + changed = true + } + } + if (changed) { + notificationGroupManager.updateIsolation(entry) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java index 059d6ffa9180..148cdea92052 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java @@ -95,8 +95,8 @@ public class DynamicChildBindController { private void freeChildContent(NotificationEntry entry) { RowContentBindParams params = mStage.getStageParams(entry); - params.freeContentViews(FLAG_CONTENT_VIEW_CONTRACTED); - params.freeContentViews(FLAG_CONTENT_VIEW_EXPANDED); + params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED); + params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED); mStage.requestRebind(entry, null); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index d2f781d2e19c..295adae9c9ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -252,7 +252,7 @@ public class NotificationEntryManager implements } @Override - public void onReorderingAllowed() { + public void onChangeAllowed() { updateNotifications("reordering is now allowed"); } @@ -539,7 +539,8 @@ public class NotificationEntryManager implements } } - private void addNotificationInternal(StatusBarNotification notification, + private void addNotificationInternal( + StatusBarNotification notification, RankingMap rankingMap) throws InflationException { String key = notification.getKey(); if (DEBUG) { @@ -579,6 +580,9 @@ public class NotificationEntryManager implements for (NotifCollectionListener listener : mNotifCollectionListeners) { listener.onEntryAdded(entry); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingApplied(); + } } public void addNotification(StatusBarNotification notification, RankingMap ranking) { @@ -635,6 +639,9 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPostEntryUpdated(entry); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingApplied(); + } } public void updateNotification(StatusBarNotification notification, RankingMap ranking) { @@ -693,6 +700,9 @@ public class NotificationEntryManager implements for (NotifCollectionListener listener : mNotifCollectionListeners) { listener.onRankingUpdate(rankingMap); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingApplied(); + } } private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) { @@ -799,6 +809,9 @@ public class NotificationEntryManager implements */ public void updateRanking(RankingMap rankingMap, String reason) { updateRankingAndSort(rankingMap, reason); + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingApplied(); + } } /** Resorts / filters the current notification set with the current RankingMap */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java index b357ada7bcf1..7ac59954cb57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java @@ -42,12 +42,14 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000; - private final ArrayList<Callback> mCallbacks = new ArrayList<>(); + private final ArrayList<Callback> mReorderingAllowedCallbacks = new ArrayList<>(); + private final ArrayList<Callback> mGroupChangesAllowedCallbacks = new ArrayList<>(); private final Handler mHandler; private boolean mPanelExpanded; private boolean mScreenOn; private boolean mReorderingAllowed; + private boolean mGroupChangedAllowed; private boolean mIsTemporaryReorderingAllowed; private long mTemporaryReorderingStart; private VisibilityLocationProvider mVisibilityLocationProvider; @@ -83,13 +85,22 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl /** * Add a callback to invoke when reordering is allowed again. - * @param callback */ public void addReorderingAllowedCallback(Callback callback) { - if (mCallbacks.contains(callback)) { + if (mReorderingAllowedCallbacks.contains(callback)) { return; } - mCallbacks.add(callback); + mReorderingAllowedCallbacks.add(callback); + } + + /** + * Add a callback to invoke when group changes are allowed again. + */ + public void addGroupChangesAllowedCallback(Callback callback) { + if (mGroupChangesAllowedCallbacks.contains(callback)) { + return; + } + mGroupChangesAllowedCallbacks.add(callback); } /** @@ -97,7 +108,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl */ public void setPanelExpanded(boolean expanded) { mPanelExpanded = expanded; - updateReorderingAllowed(); + updateAllowedStates(); } /** @@ -105,7 +116,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl */ public void setScreenOn(boolean screenOn) { mScreenOn = screenOn; - updateReorderingAllowed(); + updateAllowedStates(); } /** @@ -116,25 +127,30 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl return; } mPulsing = pulsing; - updateReorderingAllowed(); + updateAllowedStates(); } - private void updateReorderingAllowed() { + private void updateAllowedStates() { boolean reorderingAllowed = (!mScreenOn || !mPanelExpanded || mIsTemporaryReorderingAllowed) && !mPulsing; boolean changedToTrue = reorderingAllowed && !mReorderingAllowed; mReorderingAllowed = reorderingAllowed; if (changedToTrue) { - notifyCallbacks(); + notifyChangeAllowed(mReorderingAllowedCallbacks); + } + boolean groupChangesAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing; + changedToTrue = groupChangesAllowed && !mGroupChangedAllowed; + mGroupChangedAllowed = groupChangesAllowed; + if (changedToTrue) { + notifyChangeAllowed(mGroupChangesAllowedCallbacks); } } - private void notifyCallbacks() { - for (int i = 0; i < mCallbacks.size(); i++) { - Callback callback = mCallbacks.get(i); - callback.onReorderingAllowed(); + private void notifyChangeAllowed(ArrayList<Callback> callbacks) { + for (int i = 0; i < callbacks.size(); i++) { + callbacks.get(i).onChangeAllowed(); } - mCallbacks.clear(); + callbacks.clear(); } /** @@ -145,6 +161,13 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl } /** + * @return whether changes in the grouping should be allowed right now. + */ + public boolean areGroupChangesAllowed() { + return mGroupChangedAllowed; + } + + /** * @return whether a specific notification is allowed to reorder. Certain notifications are * allowed to reorder even if {@link #isReorderingAllowed()} returns false, like newly added * notifications or heads-up notifications that are out of view. @@ -197,12 +220,12 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl mTemporaryReorderingStart = SystemClock.elapsedRealtime(); } mIsTemporaryReorderingAllowed = true; - updateReorderingAllowed(); + updateAllowedStates(); } private final Runnable mOnTemporaryReorderingExpired = () -> { mIsTemporaryReorderingAllowed = false; - updateReorderingAllowed(); + updateAllowedStates(); }; /** @@ -229,9 +252,9 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl public interface Callback { /** - * Called when reordering is allowed again. + * Called when changing is allowed again. */ - void onReorderingAllowed(); + void onChangeAllowed(); } } 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..dd7be2775209 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 @@ -67,7 +67,6 @@ import com.android.systemui.statusbar.notification.icon.IconPack; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotificationGuts; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; import java.util.ArrayList; @@ -105,18 +104,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 @@ -149,7 +136,6 @@ public final class NotificationEntry extends ListEntry { */ public EditedSuggestionInfo editedSuggestionInfo; - private NotificationEntry parent; // our parent (if we're in a group) private ExpandableNotificationRow row; // the outer expanded view private ExpandableNotificationRowController mRowController; @@ -283,6 +269,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 */ @@ -583,10 +577,6 @@ public final class NotificationEntry extends ListEntry { if (row != null) row.resetUserExpansion(); } - public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { - if (row != null) row.freeContentViewWhenSafe(inflationFlag); - } - public boolean rowExists() { return row != null; } @@ -719,7 +709,7 @@ public final class NotificationEntry extends ListEntry { } public boolean isChildInGroup() { - return parent == null; + return row != null && row.isChildInGroup(); } /** 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/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt new file mode 100644 index 000000000000..1bac938a9fca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -0,0 +1,44 @@ +/* + * 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.coordinator + +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import javax.inject.Inject +import javax.inject.Singleton + +/** + * A coordinator that elevates important conversation notifications + */ +@Singleton +class ConversationCoordinator @Inject constructor() : Coordinator { + + private val notificationPromoter = object : NotifPromoter(TAG) { + override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean { + return entry.channel?.isImportantConversation == true + } + } + + override fun attach(pipeline: NotifPipeline) { + pipeline.addPromoter(notificationPromoter) + } + + companion object { + private const val TAG = "ConversationCoordinator" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java index 573c129f199a..2a3b2b7d815d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; +import static com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager.alertAgain; import android.annotation.Nullable; @@ -28,6 +29,8 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; +import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; @@ -55,6 +58,8 @@ public class HeadsUpCoordinator implements Coordinator { private static final String TAG = "HeadsUpCoordinator"; private final HeadsUpManager mHeadsUpManager; + private final HeadsUpViewBinder mHeadsUpViewBinder; + private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final NotificationRemoteInputManager mRemoteInputManager; // tracks the current HeadUpNotification reported by HeadsUpManager @@ -66,8 +71,12 @@ public class HeadsUpCoordinator implements Coordinator { @Inject public HeadsUpCoordinator( HeadsUpManager headsUpManager, + HeadsUpViewBinder headsUpViewBinder, + NotificationInterruptStateProvider notificationInterruptStateProvider, NotificationRemoteInputManager remoteInputManager) { mHeadsUpManager = headsUpManager; + mHeadsUpViewBinder = headsUpViewBinder; + mNotificationInterruptStateProvider = notificationInterruptStateProvider; mRemoteInputManager = remoteInputManager; } @@ -84,8 +93,51 @@ public class HeadsUpCoordinator implements Coordinator { return mNotifSection; } + private void onHeadsUpViewBound(NotificationEntry entry) { + mHeadsUpManager.showNotification(entry); + } + private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() { /** + * Notification was just added and if it should heads up, bind the view and then show it. + */ + @Override + public void onEntryAdded(NotificationEntry entry) { + if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { + mHeadsUpViewBinder.bindHeadsUpView( + entry, + HeadsUpCoordinator.this::onHeadsUpViewBound); + } + } + + /** + * Notification could've updated to be heads up or not heads up. Even if it did update to + * heads up, if the notification specified that it only wants to alert once, don't heads + * up again. + */ + @Override + public void onEntryUpdated(NotificationEntry entry) { + boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification()); + // includes check for whether this notification should be filtered: + boolean shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry); + final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey()); + if (wasHeadsUp) { + if (shouldHeadsUp) { + mHeadsUpManager.updateNotification(entry.getKey(), hunAgain); + } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) { + // We don't want this to be interrupting anymore, let's remove it + mHeadsUpManager.removeNotification( + entry.getKey(), false /* removeImmediately */); + } + } else if (shouldHeadsUp && hunAgain) { + // This notification was updated to be heads up, show it! + mHeadsUpViewBinder.bindHeadsUpView( + entry, + HeadsUpCoordinator.this::onHeadsUpViewBound); + } + } + + /** * Stop alerting HUNs that are removed from the notification collection */ @Override @@ -98,6 +150,11 @@ public class HeadsUpCoordinator implements Coordinator { mHeadsUpManager.removeNotification(entry.getKey(), removeImmediatelyForRemoteInput); } } + + @Override + public void onEntryCleanUp(NotificationEntry entry) { + mHeadsUpViewBinder.abortBindCallback(entry); + } }; private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() { @@ -153,6 +210,9 @@ public class HeadsUpCoordinator implements Coordinator { mNotifPromoter.invalidateList(); mNotifSection.invalidateList(); } + if (!isHeadsUp) { + mHeadsUpViewBinder.unbindHeadsUpView(entry); + } } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index 03c0ae6fde50..2b279bbd553a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -56,6 +56,7 @@ public class NotifCoordinators implements Dumpable { DeviceProvisionedCoordinator deviceProvisionedCoordinator, BubbleCoordinator bubbleCoordinator, HeadsUpCoordinator headsUpCoordinator, + ConversationCoordinator conversationCoordinator, PreparationCoordinator preparationCoordinator) { dumpManager.registerDumpable(TAG, this); mCoordinators.add(new HideLocallyDismissedNotifsCoordinator()); @@ -66,6 +67,7 @@ public class NotifCoordinators implements Dumpable { mCoordinators.add(deviceProvisionedCoordinator); mCoordinators.add(bubbleCoordinator); if (featureFlags.isNewNotifPipelineRenderingEnabled()) { + mCoordinators.add(conversationCoordinator); mCoordinators.add(headsUpCoordinator); mCoordinators.add(preparationCoordinator); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 742615c7fd0f..9973ef9ae14e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -33,9 +33,7 @@ import com.android.systemui.statusbar.notification.collection.inflation.NotifInf import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager; -import com.android.systemui.statusbar.policy.HeadsUpManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -63,8 +61,6 @@ public class PreparationCoordinator implements Coordinator { private final NotifViewBarn mViewBarn; private final Map<NotificationEntry, Integer> mInflationStates = new ArrayMap<>(); private final IStatusBarService mStatusBarService; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; - private final HeadsUpManager mHeadsUpManager; @Inject public PreparationCoordinator( @@ -72,9 +68,7 @@ public class PreparationCoordinator implements Coordinator { NotifInflaterImpl notifInflater, NotifInflationErrorManager errorManager, NotifViewBarn viewBarn, - IStatusBarService service, - NotificationInterruptStateProvider notificationInterruptStateProvider, - HeadsUpManager headsUpManager + IStatusBarService service ) { mLogger = logger; mNotifInflater = notifInflater; @@ -83,8 +77,6 @@ public class PreparationCoordinator implements Coordinator { mNotifErrorManager.addInflationErrorListener(mInflationErrorListener); mViewBarn = viewBarn; mStatusBarService = service; - mNotificationInterruptStateProvider = notificationInterruptStateProvider; - mHeadsUpManager = headsUpManager; } @Override @@ -158,11 +150,6 @@ public class PreparationCoordinator implements Coordinator { mLogger.logNotifInflated(entry.getKey()); mViewBarn.registerViewForEntry(entry, entry.getRow()); mInflationStates.put(entry, STATE_INFLATED); - - // TODO: should eventually be moved to HeadsUpCoordinator - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { - mHeadsUpManager.showNotification(entry); - } mNotifInflatingFilter.invalidateList(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 723728488826..32f1822804f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.collection.inflation; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; - import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -227,24 +225,17 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { final boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, entry.getImportance()); - final boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight - && !mPresenter.isPresenterFullyCollapsed(); final boolean isLowPriority = entry.isAmbient(); RowContentBindParams params = mRowContentBindStage.getStageParams(entry); params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); params.setUseLowPriority(entry.isAmbient()); - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { - params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); - } //TODO: Replace this API with RowContentBindParams directly row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry)); params.rebindAllContentViews(); mRowContentBindStage.requestRebind(entry, en -> { row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - row.setUsesIncreasedHeadsUpHeight(useIncreasedHeadsUp); row.setIsLowPriority(isLowPriority); mInflationCallback.onAsyncInflationFinished(en); }); 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/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 0c0cded32db2..41ca52d5a626 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -74,7 +74,9 @@ public interface NotifCollectionListener { * non-lifetime-extended notification entries will have their ranking object updated. * * Ranking updates occur whenever a notification is added, updated, or removed, or when a - * standalone ranking is sent from the server. + * standalone ranking is sent from the server. If a non-standalone ranking is applied, the event + * that accompanied the ranking is emitted first (e.g. {@link #onEntryAdded}), followed by the + * ranking event. */ default void onRankingApplied() { } 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/headsup/HeadsUpBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java new file mode 100644 index 000000000000..a7b1f37edf0e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java @@ -0,0 +1,95 @@ +/* + * 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.headsup; + +import androidx.annotation.NonNull; + +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Controller class for old pipeline heads up view binding. It listens to + * {@link NotificationEntryManager} entry events and appropriately binds or unbinds the heads up + * view. + * + * This has a subtle contract with {@link NotificationAlertingManager} where this controller handles + * the heads up binding, but {@link NotificationAlertingManager} listens for general inflation + * events to actually mark it heads up/update. In the new pipeline, we combine the classes. + * See {@link HeadsUpCoordinator}. + */ +@Singleton +public class HeadsUpBindController { + private final HeadsUpViewBinder mHeadsUpViewBinder; + private final NotificationInterruptStateProvider mInterruptStateProvider; + + @Inject + HeadsUpBindController( + HeadsUpViewBinder headsUpViewBinder, + NotificationInterruptStateProvider notificationInterruptStateProvider) { + mInterruptStateProvider = notificationInterruptStateProvider; + mHeadsUpViewBinder = headsUpViewBinder; + } + + /** + * Attach this controller and add its listeners. + */ + public void attach( + NotificationEntryManager entryManager, + HeadsUpManager headsUpManager) { + entryManager.addCollectionListener(mCollectionListener); + headsUpManager.addListener(mOnHeadsUpChangedListener); + } + + private NotifCollectionListener mCollectionListener = new NotifCollectionListener() { + @Override + public void onEntryAdded(NotificationEntry entry) { + if (mInterruptStateProvider.shouldHeadsUp(entry)) { + mHeadsUpViewBinder.bindHeadsUpView(entry, null); + } + } + + @Override + public void onEntryUpdated(NotificationEntry entry) { + if (mInterruptStateProvider.shouldHeadsUp(entry)) { + mHeadsUpViewBinder.bindHeadsUpView(entry, null); + } + } + + @Override + public void onEntryCleanUp(NotificationEntry entry) { + mHeadsUpViewBinder.abortBindCallback(entry); + } + }; + + private OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() { + @Override + public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) { + if (!isHeadsUp) { + mHeadsUpViewBinder.unbindHeadsUpView(entry); + } + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java new file mode 100644 index 000000000000..37acfa8dc0a4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java @@ -0,0 +1,112 @@ +/* + * 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.headsup; + +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + +import android.util.ArrayMap; + +import androidx.annotation.Nullable; +import androidx.core.os.CancellationSignal; + +import com.android.internal.util.NotificationMessagingUtil; +import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; + +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Wrapper around heads up view binding logic. {@link HeadsUpViewBinder} is responsible for + * figuring out the right heads up inflation parameters and inflating/freeing the heads up + * content view. + * + * TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated + * (i.e. when {@link HeadsUpBindController} is removed). + */ +@Singleton +public class HeadsUpViewBinder { + private final RowContentBindStage mStage; + private final NotificationMessagingUtil mNotificationMessagingUtil; + private final Map<NotificationEntry, CancellationSignal> mOngoingBindCallbacks = + new ArrayMap<>(); + + private NotificationPresenter mNotificationPresenter; + + @Inject + HeadsUpViewBinder( + NotificationMessagingUtil notificationMessagingUtil, + RowContentBindStage bindStage) { + mNotificationMessagingUtil = notificationMessagingUtil; + mStage = bindStage; + } + + /** + * Set notification presenter to determine parameters for heads up view inflation. + */ + public void setPresenter(NotificationPresenter presenter) { + mNotificationPresenter = presenter; + } + + /** + * Bind heads up view to the notification row. + * @param callback callback after heads up view is bound + */ + public void bindHeadsUpView(NotificationEntry entry, @Nullable BindCallback callback) { + RowContentBindParams params = mStage.getStageParams(entry); + final boolean isImportantMessage = mNotificationMessagingUtil.isImportantMessaging( + entry.getSbn(), entry.getImportance()); + final boolean useIncreasedHeadsUp = isImportantMessage + && !mNotificationPresenter.isPresenterFullyCollapsed(); + params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); + params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); + CancellationSignal signal = mStage.requestRebind(entry, en -> { + en.getRow().setUsesIncreasedHeadsUpHeight(params.useIncreasedHeadsUpHeight()); + if (callback != null) { + callback.onBindFinished(en); + } + }); + abortBindCallback(entry); + mOngoingBindCallbacks.put(entry, signal); + } + + /** + * Abort any callbacks waiting for heads up view binding to finish for a given notification. + * @param entry notification with bind in progress + */ + public void abortBindCallback(NotificationEntry entry) { + CancellationSignal ongoingBindCallback = mOngoingBindCallbacks.remove(entry); + if (ongoingBindCallback != null) { + ongoingBindCallback.cancel(); + } + } + + /** + * Unbind the heads up view from the notification row. + */ + public void unbindHeadsUpView(NotificationEntry entry) { + abortBindCallback(entry); + mStage.getStageParams(entry).markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP); + mStage.requestRebind(entry, null); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 7a7178c3464a..d1cceaeb6dd5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.NotificationListController import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer +import com.android.systemui.statusbar.notification.headsup.HeadsUpBindController import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper @@ -35,6 +36,7 @@ import com.android.systemui.statusbar.phone.NotificationGroupManager import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder import com.android.systemui.statusbar.policy.RemoteInputUriController import dagger.Lazy import java.io.FileDescriptor @@ -63,7 +65,9 @@ class NotificationsControllerImpl @Inject constructor( private val bubbleController: BubbleController, private val groupManager: NotificationGroupManager, private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper, - private val headsUpManager: HeadsUpManager + private val headsUpManager: HeadsUpManager, + private val headsUpBindController: HeadsUpBindController, + private val headsUpViewBinder: HeadsUpViewBinder ) : NotificationsController { override fun initialize( @@ -91,6 +95,7 @@ class NotificationsControllerImpl @Inject constructor( presenter, listContainer, bindRowCallback) + headsUpViewBinder.setPresenter(presenter) notifBindPipelineInitializer.initialize() if (featureFlags.isNewNotifPipelineEnabled) { @@ -109,6 +114,7 @@ class NotificationsControllerImpl @Inject constructor( groupAlertTransferHelper.bind(entryManager, groupManager) headsUpManager.addListener(groupManager) headsUpManager.addListener(groupAlertTransferHelper) + headsUpBindController.attach(entryManager, headsUpManager) groupManager.setHeadsUpManager(headsUpManager) groupAlertTransferHelper.setHeadsUpManager(headsUpManager) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java index b5725029450d..5d070981f81b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.interruption; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import android.app.Notification; import android.service.notification.StatusBarNotification; @@ -95,16 +94,10 @@ public class NotificationAlertingManager { // TODO: Instead of this back and forth, we should listen to changes in heads up and // cancel on-going heads up view inflation using the bind pipeline. if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) { - // Possible for shouldHeadsUp to change between the inflation starting and ending. - // If it does and we no longer need to heads up, we should free the view. - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { - mHeadsUpManager.showNotification(entry); - if (!mStatusBarStateController.isDozing()) { - // Mark as seen immediately - setNotificationShown(entry.getSbn()); - } - } else { - entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP); + mHeadsUpManager.showNotification(entry); + if (!mStatusBarStateController.isDozing()) { + // Mark as seen immediately + setNotificationShown(entry.getSbn()); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt index be3873a5fd77..88cca43fd1a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt @@ -96,7 +96,7 @@ class PeopleNotificationIdentifierImpl @Inject constructor( return TYPE_NON_PERSON } - val childTypes = groupManager.getLogicalChildren(statusBarNotification) + val childTypes = groupManager.getChildren(statusBarNotification) ?.asSequence() ?.map { getPeopleNotificationType(it.sbn, it.ranking) } ?: return TYPE_NON_PERSON 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..2917346153d2 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 @@ -17,12 +17,7 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; -import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; -import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; import android.animation.Animator; @@ -89,6 +84,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 +144,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; @@ -461,41 +458,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * Marks a content view as freeable, setting it so that future inflations do not reinflate * and ensuring that the view is freed when it is safe to remove. * - * TODO: This should be moved to the respective coordinator and call - * {@link RowContentBindParams#freeContentViews} directly after disappear animation - * finishes instead of depending on binding API to know when it's "safe". - * * @param inflationFlag flag corresponding to the content view to be freed + * @deprecated By default, {@link NotificationContentInflater#unbindContent} will tell the + * view hierarchy to only free when the view is safe to remove so this method is no longer + * needed. Will remove when all uses are gone. */ + @Deprecated public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { - // View should not be reinflated in the future - Runnable freeViewRunnable = () -> { - // Possible for notification to be removed after free request. - if (!isRemoved()) { - RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); - params.freeContentViews(inflationFlag); - mRowContentBindStage.requestRebind(mEntry, null /* callback */); - } - }; - switch (inflationFlag) { - case FLAG_CONTENT_VIEW_CONTRACTED: - getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, - freeViewRunnable); - break; - case FLAG_CONTENT_VIEW_EXPANDED: - getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_EXPANDED, - freeViewRunnable); - break; - case FLAG_CONTENT_VIEW_HEADS_UP: - getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, - freeViewRunnable); - break; - case FLAG_CONTENT_VIEW_PUBLIC: - getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, - freeViewRunnable); - default: - break; - } + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.markContentViewsFreeable(inflationFlag); + mRowContentBindStage.requestRebind(mEntry, null /* callback */); } /** @@ -1145,7 +1117,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(); } @@ -1569,7 +1541,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (needsRedaction) { params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC); } else { - params.freeContentViews(FLAG_CONTENT_VIEW_PUBLIC); + params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC); } mRowContentBindStage.requestRebind(mEntry, null /* callback */); } @@ -1582,7 +1554,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 +1574,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 +1595,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/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java index d744fc398d7a..893e8490eb90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.notification.row; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.util.ArrayMap; import android.util.ArraySet; import android.widget.FrameLayout; @@ -25,12 +28,15 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.os.CancellationSignal; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Set; @@ -75,14 +81,18 @@ import javax.inject.Singleton; public final class NotifBindPipeline { private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>(); private final NotifBindPipelineLogger mLogger; + private final List<BindCallback> mScratchCallbacksList = new ArrayList<>(); + private final Handler mMainHandler; private BindStage mStage; @Inject NotifBindPipeline( CommonNotifCollection collection, - NotifBindPipelineLogger logger) { + NotifBindPipelineLogger logger, + @Main Looper mainLooper) { collection.addCollectionListener(mCollectionListener); mLogger = logger; + mMainHandler = new NotifBindPipelineHandler(mainLooper); } /** @@ -107,7 +117,7 @@ public final class NotifBindPipeline { final BindEntry bindEntry = getBindEntry(entry); bindEntry.row = row; if (bindEntry.invalidated) { - startPipeline(entry); + requestPipelineRun(entry); } } @@ -130,7 +140,28 @@ public final class NotifBindPipeline { signal.setOnCancelListener(() -> callbacks.remove(callback)); } - startPipeline(entry); + requestPipelineRun(entry); + } + + /** + * Request pipeline to start. + * + * We avoid starting the pipeline immediately as multiple clients may request rebinds + * back-to-back due to a single change (e.g. notification update), and it's better to start + * the real work once rather than repeatedly start and cancel it. + */ + private void requestPipelineRun(NotificationEntry entry) { + mLogger.logRequestPipelineRun(entry.getKey()); + + final BindEntry bindEntry = getBindEntry(entry); + + // Abort any existing pipeline run + mStage.abortStage(entry, bindEntry.row); + + if (!mMainHandler.hasMessages(START_PIPELINE_MSG, entry)) { + Message msg = Message.obtain(mMainHandler, START_PIPELINE_MSG, entry); + mMainHandler.sendMessage(msg); + } } /** @@ -151,7 +182,6 @@ public final class NotifBindPipeline { return; } - mStage.abortStage(entry, row); mStage.executeStage(entry, row, (en) -> onPipelineComplete(en)); } @@ -162,10 +192,15 @@ public final class NotifBindPipeline { mLogger.logFinishedPipeline(entry.getKey(), callbacks.size()); bindEntry.invalidated = false; - for (BindCallback cb : callbacks) { - cb.onBindFinished(entry); - } + // Move all callbacks to separate list as callbacks may themselves add/remove callbacks. + // TODO: Throw an exception for this re-entrant behavior once we deprecate + // NotificationGroupAlertTransferHelper + mScratchCallbacksList.addAll(callbacks); callbacks.clear(); + for (int i = 0; i < mScratchCallbacksList.size(); i++) { + mScratchCallbacksList.get(i).onBindFinished(entry); + } + mScratchCallbacksList.clear(); } private final NotifCollectionListener mCollectionListener = new NotifCollectionListener() { @@ -183,6 +218,7 @@ public final class NotifBindPipeline { mStage.abortStage(entry, row); } mStage.deleteStageParams(entry); + mMainHandler.removeMessages(START_PIPELINE_MSG, entry); } }; @@ -211,4 +247,25 @@ public final class NotifBindPipeline { public final Set<BindCallback> callbacks = new ArraySet<>(); public boolean invalidated; } + + private static final int START_PIPELINE_MSG = 1; + + private class NotifBindPipelineHandler extends Handler { + + NotifBindPipelineHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case START_PIPELINE_MSG: + NotificationEntry entry = (NotificationEntry) msg.obj; + startPipeline(entry); + break; + default: + throw new IllegalArgumentException("Unknown message type: " + msg.what); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt index 2717d7ad143b..199730427aec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt @@ -40,6 +40,14 @@ class NotifBindPipelineLogger @Inject constructor( }) } + fun logRequestPipelineRun(notifKey: String) { + buffer.log(TAG, INFO, { + str1 = notifKey + }, { + "Request pipeline run for notif: $str1" + }) + } + fun logStartPipeline(notifKey: String) { buffer.log(TAG, INFO, { str1 = notifKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 719f74fdcde2..9d5443729d45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -118,6 +118,9 @@ public class NotificationContentInflater implements NotificationRowContentBinder mRemoteViewCache.clearCache(entry); } + // Cancel any pending frees on any view we're trying to bind since we should be bound after. + cancelContentViewFrees(row, contentToBind); + AsyncInflationTask task = new AsyncInflationTask( mBgExecutor, mInflateSynchronously, @@ -198,44 +201,69 @@ public class NotificationContentInflater implements NotificationRowContentBinder } /** - * Frees the content view associated with the inflation flag. Will only succeed if the - * view is safe to remove. + * Frees the content view associated with the inflation flag as soon as the view is not showing. * * @param inflateFlag the flag corresponding to the content view which should be freed */ - private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row, + private void freeNotificationView( + NotificationEntry entry, + ExpandableNotificationRow row, @InflationFlag int inflateFlag) { switch (inflateFlag) { case FLAG_CONTENT_VIEW_CONTRACTED: - if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) { + row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, () -> { row.getPrivateLayout().setContractedChild(null); mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED); - } + }); break; case FLAG_CONTENT_VIEW_EXPANDED: - if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_EXPANDED)) { + row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_EXPANDED, () -> { row.getPrivateLayout().setExpandedChild(null); mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED); - } + }); break; case FLAG_CONTENT_VIEW_HEADS_UP: - if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) { + row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, () -> { row.getPrivateLayout().setHeadsUpChild(null); mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null); - } + }); break; case FLAG_CONTENT_VIEW_PUBLIC: - if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) { + row.getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, () -> { row.getPublicLayout().setContractedChild(null); mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC); - } + }); break; default: break; } } + /** + * Cancel any pending content view frees from {@link #freeNotificationView} for the provided + * content views. + * + * @param row top level notification row containing the content views + * @param contentViews content views to cancel pending frees on + */ + private void cancelContentViewFrees( + ExpandableNotificationRow row, + @InflationFlag int contentViews) { + if ((contentViews & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { + row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED); + } + if ((contentViews & FLAG_CONTENT_VIEW_EXPANDED) != 0) { + row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_EXPANDED); + } + if ((contentViews & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { + row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_HEADSUP); + } + if ((contentViews & FLAG_CONTENT_VIEW_PUBLIC) != 0) { + row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED); + } + } + private static InflationProgress inflateSmartReplyViews(InflationProgress result, @InflationFlag int reInflateFlags, NotificationEntry entry, Context context, Context packageContext, HeadsUpManager headsUpManager, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 8efdc1b56e8e..b18bf01ea91f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -385,6 +385,7 @@ public class NotificationContentView extends FrameLayout { */ public void setContractedChild(@Nullable View child) { if (mContractedChild != null) { + mOnContentViewInactiveListeners.remove(mContractedChild); mContractedChild.animate().cancel(); removeView(mContractedChild); } @@ -432,6 +433,7 @@ public class NotificationContentView extends FrameLayout { ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput); } } + mOnContentViewInactiveListeners.remove(mExpandedChild); mExpandedChild.animate().cancel(); removeView(mExpandedChild); mExpandedRemoteInput = null; @@ -470,6 +472,7 @@ public class NotificationContentView extends FrameLayout { ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput); } } + mOnContentViewInactiveListeners.remove(mHeadsUpChild); mHeadsUpChild.animate().cancel(); removeView(mHeadsUpChild); mHeadsUpRemoteInput = null; @@ -1108,7 +1111,6 @@ public class NotificationContentView extends FrameLayout { public void onNotificationUpdated(NotificationEntry entry) { mStatusBarNotification = entry.getSbn(); - mOnContentViewInactiveListeners.clear(); mBeforeN = entry.targetSdk < Build.VERSION_CODES.N; updateAllSingleLineViews(); ExpandableNotificationRow row = entry.getRow(); @@ -1623,7 +1625,7 @@ public class NotificationContentView extends FrameLayout { * @param visibleType visible type corresponding to the content view to listen * @param listener runnable to run once when the content view becomes inactive */ - public void performWhenContentInactive(int visibleType, Runnable listener) { + void performWhenContentInactive(int visibleType, Runnable listener) { View view = getViewForVisibleType(visibleType); // View is already inactive if (view == null || isContentViewInactive(visibleType)) { @@ -1634,6 +1636,22 @@ public class NotificationContentView extends FrameLayout { } /** + * Remove content inactive listeners for a given content view . See + * {@link #performWhenContentInactive}. + * + * @param visibleType visible type corresponding to the content type + */ + void removeContentInactiveRunnable(int visibleType) { + View view = getViewForVisibleType(visibleType); + // View is already inactive + if (view == null) { + return; + } + + mOnContentViewInactiveListeners.remove(view); + } + + /** * Whether or not the content view is inactive. This means it should not be visible * or the showing content as removing it would cause visual jank. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index 8e2bfb84e2dd..6fc1264d69e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -20,7 +20,6 @@ import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; -import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS; import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; @@ -44,7 +43,6 @@ import android.os.Handler; import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.transition.ChangeBounds; @@ -56,15 +54,12 @@ import android.util.Log; import android.util.Slog; import android.view.View; import android.view.accessibility.AccessibilityEvent; -import android.widget.Button; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.notification.ConversationIconFactory; -import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.notification.VisualStabilityManager; @@ -517,7 +512,6 @@ public class NotificationConversationInfo extends LinearLayout implements bgHandler.post( new UpdateChannelRunnable(mINotificationManager, mPackageName, mAppUid, mSelectedAction, mNotificationChannel)); - mVisualStabilityManager.temporarilyAllowReordering(); } /** 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/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java index 88ed0bb37d0d..d3fec695f012 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -109,11 +109,14 @@ public final class RowContentBindParams { } /** - * Free the content view so that it will no longer be bound after the rebind request. + * Mark the content view to be freed. The view may not be immediately freeable since it may + * be visible and animating out but this lets the binder know to free the view when safe. + * Note that the callback passed into {@link RowContentBindStage#requestRebind} + * may return before the view is actually freed since the view is considered up-to-date. * * @see InflationFlag */ - public void freeContentViews(@InflationFlag int contentViews) { + public void markContentViewsFreeable(@InflationFlag int contentViews) { mContentViews &= ~contentViews; mDirtyContentViews &= ~contentViews; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 0c311b403c48..5205bab8fea3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -130,10 +130,18 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { if (mNotificationHeader != null) { mNotificationHeader.setAppOpsOnClickListener(listener); } - mAppOps.setOnClickListener(listener); - mCameraIcon.setOnClickListener(listener); - mMicIcon.setOnClickListener(listener); - mOverlayIcon.setOnClickListener(listener); + if (mAppOps != null) { + mAppOps.setOnClickListener(listener); + } + if (mCameraIcon != null) { + mCameraIcon.setOnClickListener(listener); + } + if (mMicIcon != null) { + mMicIcon.setOnClickListener(listener); + } + if (mOverlayIcon != null) { + mOverlayIcon.setOnClickListener(listener); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java index d38bc9f7a84d..d02037cf61fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java @@ -20,6 +20,7 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.LayoutRes; import android.annotation.NonNull; @@ -555,6 +556,12 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section updateSectionBoundaries(); } + void setHeaderForegroundColor(@ColorInt int color) { + mPeopleHubView.setTextColor(color); + mGentleHeader.setForegroundColor(color); + mAlertingHeader.setForegroundColor(color); + } + /** * For now, declare the available notification buckets (sections) here so that other * presentation code can decide what to do based on an entry's buckets diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 823b18660bed..6054b507185e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -33,6 +33,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TimeAnimator; import android.animation.ValueAnimator; +import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -3865,9 +3866,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); - final int action = ev.getAction(); + final int action = ev.getActionMasked(); + if (ev.findPointerIndex(mActivePointerId) == -1 && action != MotionEvent.ACTION_DOWN) { + // Incomplete gesture, possibly due to window swap mid-gesture. Ignore until a new + // one starts. + Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent " + + MotionEvent.actionToString(ev.getActionMasked())); + return true; + } - switch (action & MotionEvent.ACTION_MASK) { + switch (action) { case MotionEvent.ACTION_DOWN: { if (getChildCount() == 0 || !isInContentBounds(ev)) { return false; @@ -4806,7 +4814,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mUsingLightTheme = lightTheme; Context context = new ContextThemeWrapper(mContext, lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI); - final int textColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor); + final @ColorInt int textColor = + Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor); + mSectionsManager.setHeaderForegroundColor(textColor); mFooterView.setTextColor(textColor); mEmptyShadeView.setTextColor(textColor); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt index bc25c71e4fe5..a1d898fb84b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt @@ -16,11 +16,13 @@ package com.android.systemui.statusbar.notification.stack +import android.annotation.ColorInt import android.content.Context import android.util.AttributeSet import android.view.View import android.view.ViewGroup import android.widget.ImageView +import android.widget.TextView import com.android.systemui.R import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin import com.android.systemui.statusbar.notification.people.DataListener @@ -31,12 +33,14 @@ class PeopleHubView(context: Context, attrs: AttributeSet) : StackScrollerDecorView(context, attrs), SwipeableView { private lateinit var contents: ViewGroup + private lateinit var label: TextView lateinit var personViewAdapters: Sequence<DataListener<PersonViewModel?>> private set override fun onFinishInflate() { contents = requireViewById(R.id.people_list) + label = requireViewById(R.id.header_label) personViewAdapters = (0 until contents.childCount) .asSequence() // so we can map .mapNotNull { idx -> @@ -49,6 +53,8 @@ class PeopleHubView(context: Context, attrs: AttributeSet) : setVisible(true /* nowVisible */, false /* animate */) } + fun setTextColor(@ColorInt color: Int) = label.setTextColor(color) + override fun findContentView(): View = contents override fun findSecondaryView(): View? = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java index a3d8eecdfd68..5777ba120ef0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java @@ -16,9 +16,11 @@ package com.android.systemui.statusbar.notification.stack; +import android.annotation.ColorInt; import android.annotation.Nullable; import android.annotation.StringRes; import android.content.Context; +import android.content.res.ColorStateList; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; @@ -124,4 +126,9 @@ public class SectionHeaderView extends StackScrollerDecorView { mLabelTextId = resId; mLabelView.setText(resId); } + + void setForegroundColor(@ColorInt int color) { + mLabelView.setTextColor(color); + mClearAllButton.setImageTintList(ColorStateList.valueOf(color)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 8efda213d659..f103bd01fc3f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -19,6 +19,7 @@ import static android.view.Display.INVALID_DISPLAY; import android.content.Context; import android.content.res.Resources; +import android.database.ContentObserver; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PointF; @@ -26,10 +27,14 @@ import android.graphics.Region; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.input.InputManager; +import android.net.Uri; +import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import android.view.ISystemGestureExclusionListener; import android.view.InputChannel; @@ -40,6 +45,7 @@ import android.view.InputMonitor; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.Surface; import android.view.ViewConfiguration; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -53,8 +59,10 @@ import com.android.systemui.plugins.NavigationEdgeBackPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; @@ -72,6 +80,8 @@ public class EdgeBackGestureHandler implements DisplayListener, private static final String TAG = "EdgeBackGestureHandler"; private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( "gestures.back_timeout", 250); + private static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform"; + private ISystemGestureExclusionListener mGestureExclusionListener = new ISystemGestureExclusionListener.Stub() { @@ -88,6 +98,33 @@ public class EdgeBackGestureHandler implements DisplayListener, } }; + private OverviewProxyService.OverviewProxyListener mQuickSwitchListener = + new OverviewProxyService.OverviewProxyListener() { + @Override + public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) { + mStartingQuickstepRotation = rotation; + updateDisabledForQuickstep(); + } + }; + + private TaskStackChangeListener mTaskStackChangeListener = new TaskStackChangeListener() { + @Override + public void onRecentTaskListFrozenChanged(boolean frozen) { + if (!frozen) { + mStartingQuickstepRotation = -1; + mDisabledForQuickstep = false; + } + } + }; + + private final ContentObserver mFixedRotationObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, Uri uri) { + updatedFixedRotation(); + } + }; + private final Context mContext; private final OverviewProxyService mOverviewProxyService; private PluginManager mPluginManager; @@ -110,6 +147,11 @@ public class EdgeBackGestureHandler implements DisplayListener, private final float mTouchSlop; // Duration after which we consider the event as longpress. private final int mLongPressTimeout; + private int mStartingQuickstepRotation = -1; + // We temporarily disable back gesture when user is quickswitching + // between apps of different orientations + private boolean mDisabledForQuickstep; + private boolean mFixedRotationFlagEnabled; private final PointF mDownPoint = new PointF(); private final PointF mEndPoint = new PointF(); @@ -193,6 +235,13 @@ public class EdgeBackGestureHandler implements DisplayListener, */ public void onNavBarAttached() { mIsAttached = true; + updatedFixedRotation(); + if (mFixedRotationFlagEnabled) { + setRotationCallbacks(true); + } + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME), + false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL); updateIsEnabled(); } @@ -201,9 +250,25 @@ public class EdgeBackGestureHandler implements DisplayListener, */ public void onNavBarDetached() { mIsAttached = false; + if (mFixedRotationFlagEnabled) { + setRotationCallbacks(false); + } + mContext.getContentResolver().unregisterContentObserver(mFixedRotationObserver); updateIsEnabled(); } + private void setRotationCallbacks(boolean enable) { + if (enable) { + ActivityManagerWrapper.getInstance().registerTaskStackListener( + mTaskStackChangeListener); + mOverviewProxyService.addCallback(mQuickSwitchListener); + } else { + ActivityManagerWrapper.getInstance().unregisterTaskStackListener( + mTaskStackChangeListener); + mOverviewProxyService.removeCallback(mQuickSwitchListener); + } + } + public void onNavigationModeChanged(int mode, Context currentUserContext) { mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode); updateIsEnabled(); @@ -405,7 +470,8 @@ public class EdgeBackGestureHandler implements DisplayListener, mLogGesture = false; mInRejectedExclusion = false; mAllowGesture = !QuickStepContract.isBackGestureDisabled(mSysUiFlags) - && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); + && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()) + && !mDisabledForQuickstep; if (mAllowGesture) { mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge); mEdgeBackPlugin.onMotionEvent(ev); @@ -466,6 +532,11 @@ public class EdgeBackGestureHandler implements DisplayListener, Dependency.get(ProtoTracer.class).update(); } + private void updateDisabledForQuickstep() { + int rotation = mContext.getResources().getConfiguration().windowConfiguration.getRotation(); + mDisabledForQuickstep = mStartingQuickstepRotation != rotation; + } + @Override public void onDisplayAdded(int displayId) { } @@ -474,6 +545,10 @@ public class EdgeBackGestureHandler implements DisplayListener, @Override public void onDisplayChanged(int displayId) { + if (mStartingQuickstepRotation > -1) { + updateDisabledForQuickstep(); + } + if (displayId == mDisplayId) { updateDisplaySize(); } @@ -502,6 +577,17 @@ public class EdgeBackGestureHandler implements DisplayListener, InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } + private void updatedFixedRotation() { + boolean oldFlag = mFixedRotationFlagEnabled; + mFixedRotationFlagEnabled = Settings.Global.getInt(mContext.getContentResolver(), + FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0; + if (oldFlag == mFixedRotationFlagEnabled) { + return; + } + + setRotationCallbacks(mFixedRotationFlagEnabled); + } + public void setInsets(int leftInset, int rightInset) { mLeftInset = leftInset; mRightInset = rightInset; @@ -514,6 +600,7 @@ public class EdgeBackGestureHandler implements DisplayListener, pw.println("EdgeBackGestureHandler:"); pw.println(" mIsEnabled=" + mIsEnabled); pw.println(" mAllowGesture=" + mAllowGesture); + pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); pw.println(" mInRejectedExclusion" + mInRejectedExclusion); pw.println(" mExcludeRegion=" + mExcludeRegion); pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 63fe7005e703..6b0df95f54dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -337,7 +337,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, // VisualStabilityManager.Callback overrides: @Override - public void onReorderingAllowed() { + public void onChangeAllowed() { mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false); for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) { if (isAlerting(entry.getKey())) { 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/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 8c31a372a756..dd9c8207af06 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -391,7 +391,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis alertNotificationWhenPossible(entry, mHeadsUpManager); } else { // The transfer is no longer valid. Free the content. - entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag()); + mRowContentBindStage.getStageParams(entry).markContentViewsFreeable( + contentFlag); + mRowContentBindStage.requestRebind(entry, null); } } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index ccf670708e44..84dd48b6eb6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import android.annotation.Nullable; +import android.app.NotificationChannel; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.Log; @@ -85,6 +86,17 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State return group.expanded; } + /** + * @return if the group that this notification is associated with logically is expanded + */ + public boolean isLogicalGroupExpanded(StatusBarNotification sbn) { + NotificationGroup group = mGroupMap.get(sbn.getGroupKey()); + if (group == null) { + return false; + } + return group.expanded; + } + public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) { NotificationGroup group = mGroupMap.get(getGroupKey(sbn)); if (group == null) { @@ -147,7 +159,15 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State } } + /** + * Notify the group manager that a new entry was added + */ public void onEntryAdded(final NotificationEntry added) { + updateIsolation(added); + onEntryAddedInternal(added); + } + + private void onEntryAddedInternal(final NotificationEntry added) { if (added.isRowRemoved()) { added.setDebugThrowable(new Throwable()); } @@ -193,9 +213,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State } private void onEntryBecomingChild(NotificationEntry entry) { - if (shouldIsolate(entry)) { - isolateNotification(entry); - } + updateIsolation(entry); } private void updateSuppression(NotificationGroup group) { @@ -242,15 +260,6 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State return count; } - private NotificationEntry getIsolatedChild(String groupKey) { - for (StatusBarNotification sbn : mIsolatedEntries.values()) { - if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) { - return mGroupMap.get(sbn.getKey()).summary; - } - } - return null; - } - /** * Update an entry's group information * @param entry notification entry to update @@ -278,7 +287,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) { onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary); } - onEntryAdded(entry); + onEntryAddedInternal(entry); mIsUpdatingUnchangedGroup = false; if (isIsolated(entry.getSbn().getKey())) { mIsolatedEntries.put(entry.getKey(), entry.getSbn()); @@ -413,14 +422,29 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State return null; } ArrayList<NotificationEntry> children = new ArrayList<>(group.children.values()); - NotificationEntry isolatedChild = getIsolatedChild(summary.getGroupKey()); - if (isolatedChild != null) { - children.add(isolatedChild); + for (StatusBarNotification sbn : mIsolatedEntries.values()) { + if (sbn.getGroupKey().equals(summary.getGroupKey())) { + children.add(mGroupMap.get(sbn.getKey()).summary); + } } return children; } /** + * Get the children that are in the summary's group, not including those isolated. + * + * @param summary summary of a group + * @return list of the children + */ + public @Nullable ArrayList<NotificationEntry> getChildren(StatusBarNotification summary) { + NotificationGroup group = mGroupMap.get(summary.getGroupKey()); + if (group == null) { + return null; + } + return new ArrayList<>(group.children.values()); + } + + /** * If there is a {@link NotificationGroup} associated with the provided entry, this method * will update the suppression of that group. */ @@ -495,17 +519,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State @Override public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { - onAlertStateChanged(entry, isHeadsUp); - } - - private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting) { - if (isAlerting) { - if (shouldIsolate(entry)) { - isolateNotification(entry); - } - } else { - stopIsolatingNotification(entry); - } + updateIsolation(entry); } /** @@ -519,13 +533,17 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State private boolean shouldIsolate(NotificationEntry entry) { StatusBarNotification sbn = entry.getSbn(); - NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey()); if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) { return false; } + NotificationChannel channel = entry.getChannel(); + if (channel != null && channel.isImportantConversation()) { + return true; + } if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) { return false; } + NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey()); return (sbn.getNotification().fullScreenIntent != null || notificationGroup == null || !notificationGroup.expanded @@ -545,7 +563,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State mIsolatedEntries.put(sbn.getKey(), sbn); - onEntryAdded(entry); + onEntryAddedInternal(entry); // We also need to update the suppression of the old group, because this call comes // even before the groupManager knows about the notification at all. // When the notification gets added afterwards it is already isolated and therefore @@ -557,17 +575,31 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State } /** + * Update the isolation of an entry, splitting it from the group. + */ + public void updateIsolation(NotificationEntry entry) { + boolean isIsolated = isIsolated(entry.getSbn().getKey()); + if (shouldIsolate(entry)) { + if (!isIsolated) { + isolateNotification(entry); + } + } else if (isIsolated) { + stopIsolatingNotification(entry); + } + } + + /** * Stop isolating a notification and re-group it with its original logical group. * * @param entry the notification to un-isolate */ private void stopIsolatingNotification(NotificationEntry entry) { StatusBarNotification sbn = entry.getSbn(); - if (mIsolatedEntries.containsKey(sbn.getKey())) { + if (isIsolated(sbn.getKey())) { // not isolated anymore, we need to update the groups onEntryRemovedInternal(entry, entry.getSbn()); mIsolatedEntries.remove(sbn.getKey()); - onEntryAdded(entry); + onEntryAddedInternal(entry); for (OnGroupChangeListener listener : mListeners) { listener.onGroupsChanged(); } 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/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 66a1d3ff756a..0d5a14960850 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -161,7 +161,6 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } - entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP); } protected void updatePinnedMode() { 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/OnHeadsUpChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java index 94aa39142926..86998ab2fdd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package com.android.systemui.statusbar.policy; 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/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index 471149c936e4..6decb88ee148 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import android.animation.ObjectAnimator; import android.content.Context; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.annotation.UiThreadTest; @@ -52,7 +53,11 @@ public class ExpandHelperTest extends SysuiTestCase { mDependency.injectMockDependency(NotificationMediaManager.class); allowTestableLooperAsMainThread(); Context context = getContext(); - mRow = new NotificationTestHelper(context, mDependency).createRow(); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + mRow = helper.createRow(); mCallback = mock(ExpandHelper.Callback.class); mExpandHelper = new ExpandHelper(context, mCallback, 10, 100); } 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/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 6f3fbb9cbd2c..037f04ec1d7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -223,7 +223,10 @@ public class BubbleControllerTest extends SysuiTestCase { mNotificationShadeWindowController.attach(); // Need notifications for bubbles - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mRow = mNotificationTestHelper.createBubble(mDeleteIntent); mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); mNonBubbleNotifRow = mNotificationTestHelper.createRow(); 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..d2f912770577 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -109,7 +109,10 @@ public class BubbleDataTest extends SysuiTestCase { @Before public void setUp() throws Exception { - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); MockitoAnnotations.initMocks(this); mEntryA1 = createBubbleEntry(1, "a1", "package.a"); @@ -599,13 +602,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 +792,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 +815,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/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index a31e3f8d7cc9..545de210d5b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -210,7 +210,10 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mNotificationShadeWindowController.attach(); // Need notifications for bubbles - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mRow = mNotificationTestHelper.createBubble(mDeleteIntent); mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); mNonBubbleNotifRow = mNotificationTestHelper.createRow(); 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/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index 83877f2e72f1..e55ea41d94e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -98,8 +98,10 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { mLockscreenUserManager); mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager); mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager); + when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true); + when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true); - mHelper = new NotificationTestHelper(mContext, mDependency); + mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); mViewHierarchyManager = new NotificationViewHierarchyManager(mContext, mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java index 0a38f163cfba..2b9456160122 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.verify; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.widget.FrameLayout; @@ -46,7 +47,10 @@ public class AboveShelfObserverTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - mNotificationTestHelper = new NotificationTestHelper(getContext(), mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mHostLayout = new FrameLayout(getContext()); mObserver = new AboveShelfObserver(mHostLayout); ExpandableNotificationRow row = mNotificationTestHelper.createRow(); 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/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java index bf2d59880ffb..29040147d1ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java @@ -83,8 +83,8 @@ public class DynamicChildBindControllerTest extends SysuiTestCase { mDynamicChildBindController.updateChildContentViews(mGroupNotifs); // THEN we free content views - verify(bindParams).freeContentViews(FLAG_CONTENT_VIEW_CONTRACTED); - verify(bindParams).freeContentViews(FLAG_CONTENT_VIEW_EXPANDED); + verify(bindParams).markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED); + verify(bindParams).markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED); verify(mBindStage).requestRebind(eq(lastChild), any()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java index 97e0a3199e17..277ac244cec5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java @@ -32,6 +32,7 @@ import android.content.pm.PackageManager; import android.os.Bundle; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.annotation.UiThreadTest; @@ -98,7 +99,11 @@ public class NotificationFilterTest extends SysuiTestCase { mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment); when(mEnvironment.isDeviceProvisioned()).thenReturn(true); when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true); - mRow = new NotificationTestHelper(getContext(), mDependency).createRow(); + NotificationTestHelper testHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + mRow = testHelper.createRow(); mNotificationFilter = new NotificationFilter(mock(StatusBarStateController.class)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java index 9079223649ff..3d06c57cac37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java @@ -110,7 +110,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.setScreenOn(true); mVisualStabilityManager.addReorderingAllowedCallback(mCallback); mVisualStabilityManager.setScreenOn(false); - verify(mCallback).onReorderingAllowed(); + verify(mCallback).onChangeAllowed(); } @Test @@ -119,7 +119,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.setScreenOn(true); mVisualStabilityManager.addReorderingAllowedCallback(mCallback); mVisualStabilityManager.setPanelExpanded(false); - verify(mCallback).onReorderingAllowed(); + verify(mCallback).onChangeAllowed(); } @Test @@ -130,7 +130,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.setScreenOn(false); mVisualStabilityManager.setScreenOn(true); mVisualStabilityManager.setScreenOn(false); - verify(mCallback).onReorderingAllowed(); + verify(mCallback).onChangeAllowed(); } @Test @@ -190,7 +190,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.setPulsing(true); mVisualStabilityManager.addReorderingAllowedCallback(mCallback); mVisualStabilityManager.setPulsing(false); - verify(mCallback).onReorderingAllowed(); + verify(mCallback).onChangeAllowed(); } @Test @@ -204,7 +204,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.temporarilyAllowReordering(); // THEN callbacks are notified that reordering is allowed - verify(mCallback).onReorderingAllowed(); + verify(mCallback).onChangeAllowed(); assertTrue(mVisualStabilityManager.isReorderingAllowed()); } @@ -218,7 +218,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.temporarilyAllowReordering(); // THEN reordering is still not allowed - verify(mCallback, never()).onReorderingAllowed(); + verify(mCallback, never()).onChangeAllowed(); assertFalse(mVisualStabilityManager.isReorderingAllowed()); } 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/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt new file mode 100644 index 000000000000..dfc627e14d8c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -0,0 +1,77 @@ +/* + * 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.coordinator + +import android.app.NotificationChannel +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class ConversationCoordinatorTest : SysuiTestCase() { + + private var coordinator: ConversationCoordinator = ConversationCoordinator() + + // captured listeners and pluggables: + private var promoter: NotifPromoter? = null + + @Mock + private val pipeline: NotifPipeline? = null + @Mock + private val channel: NotificationChannel? = null + private var entry: NotificationEntry? = null + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(channel!!.isImportantConversation).thenReturn(true) + + coordinator.attach(pipeline!!) + + // capture arguments: + val notifPromoterCaptor = ArgumentCaptor.forClass(NotifPromoter::class.java) + verify(pipeline).addPromoter(notifPromoterCaptor.capture()) + promoter = notifPromoterCaptor.value + + entry = NotificationEntryBuilder().setChannel(channel).build() + } + + @Test + fun testPromotesCurrentHUN() { + + // only promote important conversations + assertTrue(promoter!!.shouldPromoteToTopLevel(entry)) + assertFalse(promoter!!.shouldPromoteToTopLevel(NotificationEntryBuilder().build())) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java index 0c109c498dd7..f3038ce051cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -37,6 +39,9 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; +import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; @@ -63,6 +68,8 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { @Mock private NotifPipeline mNotifPipeline; @Mock private HeadsUpManager mHeadsUpManager; + @Mock private HeadsUpViewBinder mHeadsUpViewBinder; + @Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider; @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private RemoteInputController mRemoteInputController; @Mock private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension; @@ -76,6 +83,8 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { mCoordinator = new HeadsUpCoordinator( mHeadsUpManager, + mHeadsUpViewBinder, + mNotificationInterruptStateProvider, mRemoteInputManager ); @@ -168,6 +177,36 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { } @Test + public void testShowHUNOnInflationFinished() { + // WHEN a notification should HUN and its inflation is finished + when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true); + + ArgumentCaptor<BindCallback> bindCallbackCaptor = + ArgumentCaptor.forClass(BindCallback.class); + mCollectionListener.onEntryAdded(mEntry); + verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), bindCallbackCaptor.capture()); + + bindCallbackCaptor.getValue().onBindFinished(mEntry); + + // THEN we tell the HeadsUpManager to show the notification + verify(mHeadsUpManager).showNotification(mEntry); + } + + @Test + public void testNoHUNOnInflationFinished() { + // WHEN a notification shouldn't HUN and its inflation is finished + when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false); + ArgumentCaptor<BindCallback> bindCallbackCaptor = + ArgumentCaptor.forClass(BindCallback.class); + mCollectionListener.onEntryAdded(mEntry); + + // THEN we never bind the heads up view or tell HeadsUpManager to show the notification + verify(mHeadsUpViewBinder, never()).bindHeadsUpView( + eq(mEntry), bindCallbackCaptor.capture()); + verify(mHeadsUpManager, never()).showNotification(mEntry); + } + + @Test public void testOnEntryRemovedRemovesHeadsUpNotification() { // GIVEN the current HUN is mEntry setCurrentHUN(mEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index 8143cf5a4673..6b9e43bcb290 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -20,10 +20,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.os.RemoteException; import android.testing.AndroidTestingRunner; @@ -41,9 +39,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager; -import com.android.systemui.statusbar.policy.HeadsUpManager; import org.junit.Before; import org.junit.Test; @@ -78,8 +74,6 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Mock private NotifPipeline mNotifPipeline; @Mock private IStatusBarService mService; @Mock private NotifInflaterImpl mNotifInflater; - @Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider; - @Mock private HeadsUpManager mHeadsUpManager; @Before public void setUp() { @@ -94,9 +88,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mNotifInflater, mErrorManager, mock(NotifViewBarn.class), - mService, - mNotificationInterruptStateProvider, - mHeadsUpManager); + mService); ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); mCoordinator.attach(mNotifPipeline); @@ -180,24 +172,4 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // THEN it isn't filtered from shade list assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0)); } - - @Test - public void testShowHUNOnInflationFinished() { - // WHEN a notification should HUN and its inflation is finished - when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true); - mCallback.onInflationFinished(mEntry); - - // THEN we tell the HeadsUpManager to show the notification - verify(mHeadsUpManager).showNotification(mEntry); - } - - @Test - public void testNoHUNOnInflationFinished() { - // WHEN a notification shouldn't HUN and its inflation is finished - when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false); - mCallback.onInflationFinished(mEntry); - - // THEN we never tell the HeadsUpManager to show the notification - verify(mHeadsUpManager, never()).showNotification(mEntry); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index cb379208eb94..43dcbe30f3c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -20,7 +20,6 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -39,6 +38,7 @@ import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.app.NotificationChannel; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.ArraySet; import android.view.NotificationHeaderView; @@ -79,7 +79,10 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mGroupRow = mNotificationTestHelper.createGroup(); mGroupRow.setHeadsUpAnimatingAwayListener( animatingAway -> mHeadsUpAnimatingAway = animatingAway); @@ -135,22 +138,13 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void testFreeContentViewWhenSafe() throws Exception { - ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL); - - row.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP); - - assertNull(row.getPrivateLayout().getHeadsUpChild()); - } - - @Test public void setNeedsRedactionFreesViewWhenFalse() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL); row.setNeedsRedaction(true); row.getPublicLayout().setVisibility(View.GONE); row.setNeedsRedaction(false); - + TestableLooper.get(this).processAllMessages(); assertNull(row.getPublicLayout().getContractedChild()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java index 6408f7a38133..bdd82fd98e0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java @@ -59,7 +59,10 @@ public class NotifBindPipelineTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); CommonNotifCollection collection = mock(CommonNotifCollection.class); - mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class)); + mBindPipeline = new NotifBindPipeline( + collection, + mock(NotifBindPipelineLogger.class), + TestableLooper.get(this).getLooper()); mBindPipeline.setStage(mStage); ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor = @@ -78,6 +81,7 @@ public class NotifBindPipelineTest extends SysuiTestCase { // WHEN content is invalidated BindCallback callback = mock(BindCallback.class); mStage.requestRebind(mEntry, callback); + TestableLooper.get(this).processAllMessages(); // WHEN stage finishes its work mStage.doWorkSynchronously(); @@ -94,6 +98,7 @@ public class NotifBindPipelineTest extends SysuiTestCase { // GIVEN an in-progress pipeline run BindCallback callback = mock(BindCallback.class); CancellationSignal signal = mStage.requestRebind(mEntry, callback); + TestableLooper.get(this).processAllMessages(); // WHEN the callback is cancelled. signal.cancel(); @@ -113,10 +118,12 @@ public class NotifBindPipelineTest extends SysuiTestCase { // WHEN the pipeline is invalidated. BindCallback callback = mock(BindCallback.class); mStage.requestRebind(mEntry, callback); + TestableLooper.get(this).processAllMessages(); // WHEN the pipeline is invalidated again before the work completes. BindCallback callback2 = mock(BindCallback.class); mStage.requestRebind(mEntry, callback2); + TestableLooper.get(this).processAllMessages(); // WHEN the stage finishes all work. mStage.doWorkSynchronously(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java index 481bac2c19c6..7c8328d59a51 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java @@ -86,7 +86,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem); mDependency.injectMockDependency(BubbleController.class); - mHelper = new NotificationTestHelper(mContext, mDependency); + mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); mBlockingHelperManager = new NotificationBlockingHelperManager( mContext, mGutsManager, mEntryManager, mock(MetricsLogger.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index 6a65269c34b0..25da74137a90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -40,6 +40,7 @@ import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.view.ViewGroup; @@ -94,8 +95,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase { .setContentTitle("Title") .setContentText("Text") .setStyle(new Notification.BigTextStyle().bigText("big text")); - ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow( - mBuilder.build()); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + ExpandableNotificationRow row = helper.createRow(mBuilder.build()); mRow = spy(row); final SmartReplyConstants smartReplyConstants = mock(SmartReplyConstants.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java index 0f268984a996..b018b59e4389 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java @@ -76,13 +76,13 @@ public class NotificationContentViewTest extends SysuiTestCase { @Test @UiThreadTest public void testShowAppOpsIcons() { - View mockContracted = mock(View.class); + View mockContracted = mock(NotificationHeaderView.class); when(mockContracted.findViewById(com.android.internal.R.id.mic)) .thenReturn(mockContracted); - View mockExpanded = mock(View.class); + View mockExpanded = mock(NotificationHeaderView.class); when(mockExpanded.findViewById(com.android.internal.R.id.mic)) .thenReturn(mockExpanded); - View mockHeadsUp = mock(View.class); + View mockHeadsUp = mock(NotificationHeaderView.class); when(mockHeadsUp.findViewById(com.android.internal.R.id.mic)) .thenReturn(mockHeadsUp); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index 6998edda3127..b6bd5e213dd4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -816,29 +816,4 @@ public class NotificationConversationInfoTest extends SysuiTestCase { verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage( anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID)); } - - @Test - public void testAdjustImportanceTemporarilyAllowsReordering() { - mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); - mConversationChannel.setImportance(IMPORTANCE_DEFAULT); - mNotificationInfo.bindNotification( - mShortcutManager, - mMockPackageManager, - mMockINotificationManager, - mVisualStabilityManager, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - mIconFactory, - true); - - mNotificationInfo.findViewById(R.id.silence).performClick(); - mNotificationInfo.findViewById(R.id.done).performClick(); - - mTestableLooper.processAllMessages(); - - verify(mVisualStabilityManager).temporarilyAllowReordering(); - } } 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..be026f7884c3 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; @@ -181,7 +182,8 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { NotifRemoteViewCache cache = new NotifRemoteViewCacheImpl(mEntryManager); NotifBindPipeline pipeline = new NotifBindPipeline( mEntryManager, - mock(NotifBindPipelineLogger.class)); + mock(NotifBindPipelineLogger.class), + TestableLooper.get(this).getLooper()); mBgExecutor = new FakeExecutor(new FakeSystemClock()); NotificationContentInflater binder = new NotificationContentInflater( cache, @@ -239,7 +241,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..ed4642344dba 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() { @@ -129,7 +131,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager); mDependency.injectMockDependency(NotificationLockscreenUserManager.class); mHandler = Handler.createAsync(mTestableLooper.getLooper()); - mHelper = new NotificationTestHelper(mContext, mDependency); + mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager, @@ -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..07f2085a1b76 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 @@ -38,6 +38,7 @@ import android.content.pm.LauncherApps; import android.graphics.drawable.Icon; import android.os.UserHandle; import android.service.notification.StatusBarNotification; +import android.testing.TestableLooper; import android.text.TextUtils; import android.view.LayoutInflater; import android.widget.RemoteViews; @@ -57,6 +58,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; @@ -91,6 +93,7 @@ public class NotificationTestHelper { private static final String APP_NAME = "appName"; private final Context mContext; + private final TestableLooper mTestLooper; private int mId; private final NotificationGroupManager mGroupManager; private ExpandableNotificationRow mRow; @@ -100,9 +103,14 @@ public class NotificationTestHelper { private final RowContentBindStage mBindStage; private final IconManager mIconManager; private StatusBarStateController mStatusBarStateController; + private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; - public NotificationTestHelper(Context context, TestableDependency dependency) { + public NotificationTestHelper( + Context context, + TestableDependency dependency, + TestableLooper testLooper) { mContext = context; + mTestLooper = testLooper; dependency.injectMockDependency(NotificationMediaManager.class); dependency.injectMockDependency(BubbleController.class); dependency.injectMockDependency(NotificationShadeWindowController.class); @@ -131,13 +139,17 @@ public class NotificationTestHelper { CommonNotifCollection collection = mock(CommonNotifCollection.class); - mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class)); + mBindPipeline = new NotifBindPipeline( + collection, + mock(NotifBindPipelineLogger.class), + mTestLooper.getLooper()); mBindPipeline.setStage(mBindStage); ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener.class); verify(collection).addCollectionListener(collectionListenerCaptor.capture()); mBindPipelineEntryListener = collectionListenerCaptor.getValue(); + mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class); } /** @@ -407,10 +419,11 @@ 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); + inflateAndWait(entry); // This would be done as part of onAsyncInflationFinished, but we skip large amounts of // the callback chain, so we need to make up for not adding it to the group manager @@ -419,10 +432,10 @@ public class NotificationTestHelper { return row; } - private static void inflateAndWait(NotificationEntry entry, RowContentBindStage stage) - throws Exception { + private void inflateAndWait(NotificationEntry entry) throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); - stage.requestRebind(entry, en -> countDownLatch.countDown()); + mBindStage.requestRebind(entry, en -> countDownLatch.countDown()); + mTestLooper.processAllMessages(); assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java index 0f2482ce9c4e..96a58e27ed0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -93,7 +93,7 @@ public class RowContentBindStageTest extends SysuiTestCase { // WHEN inflation flags are cleared and stage executed. final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; - params.freeContentViews(flags); + params.markContentViewsFreeable(flags); mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); // THEN binder unbinds flags. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java index b661b28c42fc..45f7c5a6fdc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.row.wrapper; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.widget.RemoteViews; @@ -43,7 +44,11 @@ public class NotificationCustomViewWrapperTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - mRow = new NotificationTestHelper(mContext, mDependency).createRow(); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + mRow = helper.createRow(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java index 18ea774e56f0..fbe4d7315baa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java @@ -27,6 +27,7 @@ import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.provider.Settings; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.widget.RemoteViews; @@ -97,7 +98,11 @@ public class NotificationMediaTemplateViewWrapperTest extends SysuiTestCase { mNotif = builder.build(); assertTrue(mNotif.hasMediaSession()); - mRow = new NotificationTestHelper(mContext, mDependency).createRow(mNotif); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + mRow = helper.createRow(mNotif); RemoteViews views = new RemoteViews(mContext.getPackageName(), com.android.internal.R.layout.notification_template_material_big_media); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java index 830e8d93196c..085bd900debc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.mock; import android.content.Context; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.widget.LinearLayout; @@ -48,7 +49,11 @@ public class NotificationViewWrapperTest extends SysuiTestCase { public void setup() throws Exception { allowTestableLooperAsMainThread(); mView = mock(View.class); - mRow = new NotificationTestHelper(getContext(), mDependency).createRow(); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + mRow = helper.createRow(); mNotificationViewWrapper = new TestableNotificationViewWrapper(mContext, mView, mRow); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index a2029c76bb55..703789151895 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.stack; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.NotificationHeaderView; import android.view.View; @@ -44,7 +45,10 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mGroup = mNotificationTestHelper.createGroup(); mChildrenContainer = mGroup.getChildrenContainer(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java index ba2b94677814..d795cbac700f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; @@ -66,7 +67,10 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { mBypassController, new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext)); allowTestableLooperAsMainThread(); - NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency); + NotificationTestHelper testHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mFirst = testHelper.createRow(); mFirst.setHeadsUpAnimatingAwayListener(animatingAway -> mRoundnessManager.onHeadsupAnimatingAwayChanged(mFirst, animatingAway)); @@ -146,7 +150,10 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, mSecond), createSection(null, null) }); - NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency); + NotificationTestHelper testHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); ExpandableNotificationRow row = testHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.getRow()).thenReturn(row); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index a74657e561aa..e546dff8abf6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.widget.TextView; @@ -69,7 +70,10 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency); + NotificationTestHelper testHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mFirst = testHelper.createRow(); mDependency.injectTestDependency(DarkIconDispatcher.class, mDarkIconDispatcher); mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index f6a099da3282..67f941301e5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -26,7 +26,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -236,8 +235,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture()); callbackCaptor.getValue().onBindFinished(childEntry); - verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager - .getContentFlag()); + assertTrue((params.getContentViews() & FLAG_CONTENT_VIEW_HEADS_UP) == 0); assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey())); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index b9c5b7c02b1c..dd28687e749c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -152,7 +152,10 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1)); when(mContentIntent.getIntent()).thenReturn(mContentIntentInner); - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); // Create standard notification with contentIntent mNotificationRow = mNotificationTestHelper.createRow(); 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/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 86add98ab929..e88b514ef238 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -105,8 +105,11 @@ public class RemoteInputViewTest extends SysuiTestCase { @Test public void testSendRemoteInput_intentContainsResultsAndSource() throws Exception { - ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency) - .createRow(); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + ExpandableNotificationRow row = helper.createRow(); RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); setTestPendingIntent(view); @@ -127,7 +130,11 @@ public class RemoteInputViewTest extends SysuiTestCase { private UserHandle getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser) throws Exception { - ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow( + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + ExpandableNotificationRow row = helper.createRow( DUMMY_MESSAGE_APP_PKG, UserHandle.getUid(fromUser.getIdentifier(), DUMMY_MESSAGE_APP_ID), toUser); @@ -169,8 +176,11 @@ public class RemoteInputViewTest extends SysuiTestCase { @Test public void testNoCrashWithoutVisibilityListener() throws Exception { - ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency) - .createRow(); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + ExpandableNotificationRow row = helper.createRow(); RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); view.setOnVisibilityChangedListener(null); 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/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index e942b274dc53..55a92966eb29 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -645,33 +645,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Returns whether inline suggestions are enabled for Autofill. + * Returns whether inline suggestions are supported by Autofill provider (not augmented + * Autofill provider). */ - private boolean isInlineSuggestionsEnabledLocked() { - return mService.isInlineSuggestionsEnabled() - || mService.getRemoteInlineSuggestionRenderServiceLocked() != null; + private boolean isInlineSuggestionsEnabledByAutofillProviderLocked() { + return mService.isInlineSuggestionsEnabled(); } - /** - * Ask the IME to make an inline suggestions request if enabled. - */ - private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState, - int newState, int flags) { - if (isInlineSuggestionsEnabledLocked()) { - Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = - mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true); - if (inlineSuggestionsRequestConsumer != null) { - mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, - inlineSuggestionsRequestConsumer); - } - } else { - mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false); - } - requestNewFillResponseLocked(viewState, newState, flags); + private boolean isInlineSuggestionRenderServiceAvailable() { + return mService.getRemoteInlineSuggestionRenderServiceLocked() != null; } /** * Reads a new structure and then request a new fill response from the fill service. + * + * <p> Also asks the IME to make an inline suggestions request if it's enabled. */ @GuardedBy("mLock") private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState, @@ -717,6 +705,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // structure is taken. This causes only one fill request per bust of focus changes. cancelCurrentRequestLocked(); + // Only ask IME to create inline suggestions request if Autofill provider supports it and + // the render service is available. + if (isInlineSuggestionsEnabledByAutofillProviderLocked() + && isInlineSuggestionRenderServiceAvailable()) { + Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = + mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true); + if (inlineSuggestionsRequestConsumer != null) { + mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, + inlineSuggestionsRequestConsumer); + } + } else { + mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false); + } + + // Now request the assist structure data. try { final Bundle receiverExtras = new Bundle(); receiverExtras.putInt(EXTRA_REQUEST_ID, requestId); @@ -2322,8 +2325,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if ((flags & FLAG_MANUAL_REQUEST) != 0) { mForAugmentedAutofillOnly = false; if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags); - maybeRequestInlineSuggestionsRequestThenFillLocked(viewState, - ViewState.STATE_RESTARTED_SESSION, flags); + requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags); return true; } @@ -2333,8 +2335,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": " + viewState.getStateAsString()); } - maybeRequestInlineSuggestionsRequestThenFillLocked(viewState, - ViewState.STATE_STARTED_PARTITION, flags); + requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags); return true; } else { if (sVerbose) { @@ -2458,8 +2459,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // View is triggering autofill. mCurrentViewId = viewState.id; viewState.update(value, virtualBounds, flags); - maybeRequestInlineSuggestionsRequestThenFillLocked(viewState, - ViewState.STATE_STARTED_SESSION, flags); + requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags); break; case ACTION_VALUE_CHANGED: if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { @@ -3107,12 +3107,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState }, mService.getRemoteInlineSuggestionRenderServiceLocked()); }; - // There are 3 cases when augmented autofill should ask IME for a new request: - // 1. standard autofill provider is None - // 2. standard autofill provider doesn't support inline (and returns null response) - // 3. standard autofill provider supports inline, but isn't called because the field - // doesn't want autofill - if (mForAugmentedAutofillOnly || !isInlineSuggestionsEnabledLocked()) { + // When the inline suggestion render service is available, there are 2 cases when + // augmented autofill should ask IME for inline suggestion request, because standard + // autofill flow didn't: + // 1. the field is augmented autofill only (when standard autofill provider is None or + // when it returns null response) + // 2. standard autofill provider doesn't support inline suggestion + if (isInlineSuggestionRenderServiceAvailable() + && (mForAugmentedAutofillOnly + || !isInlineSuggestionsEnabledByAutofillProviderLocked())) { if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, /*requestConsumer=*/ requestAugmentedAutofill); 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/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index 7aaf9be1fdd2..e1f9a7a150d8 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -137,12 +137,12 @@ public class AdbService extends IAdbManager.Stub { @Override public File getAdbKeysFile() { - return mDebuggingManager.getUserKeyFile(); + return mDebuggingManager == null ? null : mDebuggingManager.getUserKeyFile(); } @Override public File getAdbTempKeysFile() { - return mDebuggingManager.getAdbTempKeysFile(); + return mDebuggingManager == null ? null : mDebuggingManager.getAdbTempKeysFile(); } @Override diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 689f64d01054..62d7eb1d0448 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) { @@ -18376,7 +18376,18 @@ public class ActivityManagerService extends IActivityManager.Stub } } - proc.thread.dumpHeap(managed, mallocInfo, runGc, path, fd, finishCallback); + Process.enableFreezer(false); + + final RemoteCallback intermediateCallback = new RemoteCallback( + new RemoteCallback.OnResultListener() { + @Override + public void onResult(Bundle result) { + finishCallback.sendResult(result); + Process.enableFreezer(true); + } + }, null); + + proc.thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback); fd = null; return true; } 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/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 45b93834c1e2..4431abe43136 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -17,6 +17,7 @@ package com.android.server.biometrics; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; +import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -43,6 +44,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IHwBinder; import android.os.IRemoteCallback; +import android.os.Looper; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -52,6 +54,8 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.FrameworkStatsLog; @@ -93,7 +97,22 @@ public abstract class BiometricServiceBase extends SystemService protected final Map<Integer, Long> mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>()); protected final AppOpsManager mAppOps; - protected final H mHandler = new H(); + + /** + * Handler which all subclasses should post events to. + */ + protected final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(android.os.Message msg) { + switch (msg.what) { + case MSG_USER_SWITCHING: + handleUserSwitching(msg.arg1); + break; + default: + Slog.w(getTag(), "Unknown message:" + msg.what); + } + } + }; private final IBinder mToken = new Binder(); // Used for internal enumeration private final ArrayList<UserTemplate> mUnknownHALTemplates = new ArrayList<>(); @@ -483,23 +502,6 @@ public abstract class BiometricServiceBase extends SystemService void resetLockout(byte[] token) throws RemoteException; } - /** - * Handler which all subclasses should post events to. - */ - protected final class H extends Handler { - @Override - public void handleMessage(android.os.Message msg) { - switch (msg.what) { - case MSG_USER_SWITCHING: - handleUserSwitching(msg.arg1); - break; - - default: - Slog.w(getTag(), "Unknown message:" + msg.what); - } - } - } - private final Runnable mOnTaskStackChangedRunnable = new Runnable() { @Override public void run() { @@ -647,8 +649,9 @@ public abstract class BiometricServiceBase extends SystemService mContext = context; mStatusBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString( - com.android.internal.R.string.config_keyguardComponent)).getPackageName(); + final ComponentName keyguardComponent = ComponentName.unflattenFromString( + context.getResources().getString(R.string.config_keyguardComponent)); + mKeyguardPackage = keyguardComponent != null ? keyguardComponent.getPackageName() : null; mAppOps = context.getSystemService(AppOpsManager.class); mActivityTaskManager = ((ActivityTaskManager) context.getSystemService( Context.ACTIVITY_TASK_SERVICE)).getService(); @@ -671,8 +674,8 @@ public abstract class BiometricServiceBase extends SystemService // All client lifecycle must be managed on the handler. mHandler.post(() -> { - handleError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, - 0 /*vendorCode */); + Slog.e(getTag(), "Sending BIOMETRIC_ERROR_HW_UNAVAILABLE after HAL crash"); + handleError(getHalDeviceId(), BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); }); FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, @@ -798,9 +801,10 @@ public abstract class BiometricServiceBase extends SystemService } protected void handleEnumerate(BiometricAuthenticator.Identifier identifier, int remaining) { - ClientMonitor client = getCurrentClient(); - - client.onEnumerationResult(identifier, remaining); + ClientMonitor client = mCurrentClient; + if (client != null) { + client.onEnumerationResult(identifier, remaining); + } // All templates in the HAL for this user were enumerated if (remaining == 0) { @@ -818,7 +822,7 @@ public abstract class BiometricServiceBase extends SystemService } removeClient(client); startCleanupUnknownHALTemplates(); - } else { + } else if (client != null) { removeClient(client); } } @@ -898,12 +902,16 @@ public abstract class BiometricServiceBase extends SystemService protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName, int callingUid, int callingPid, int callingUserId, boolean fromClient) { + + if (DEBUG) Slog.v(getTag(), "cancelAuthentication(" + opPackageName + ")"); if (fromClient) { // Only check this if cancel was called from the client (app). If cancel was called // from BiometricService, it means the dialog was dismissed due to user interaction. if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid, callingUserId)) { - if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName); + if (DEBUG) { + Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName); + } return; } } @@ -1059,7 +1067,8 @@ public abstract class BiometricServiceBase extends SystemService * @param newClient the new client that wants to connect * @param initiatedByClient true for authenticate, remove and enroll */ - private void startClient(ClientMonitor newClient, boolean initiatedByClient) { + @VisibleForTesting + void startClient(ClientMonitor newClient, boolean initiatedByClient) { ClientMonitor currentClient = mCurrentClient; if (currentClient != null) { if (DEBUG) Slog.v(getTag(), "request stop current client " + @@ -1122,18 +1131,27 @@ public abstract class BiometricServiceBase extends SystemService Slog.e(getTag(), "Trying to start null client!"); return; } + if (DEBUG) Slog.v(getTag(), "starting client " + mCurrentClient.getClass().getSuperclass().getSimpleName() + "(" + mCurrentClient.getOwnerString() + ")" + " targetUserId: " + mCurrentClient.getTargetUserId() + " currentUserId: " + mCurrentUserId + " cookie: " + cookie + "/" + mCurrentClient.getCookie()); + if (cookie != mCurrentClient.getCookie()) { Slog.e(getTag(), "Mismatched cookie"); return; } - notifyClientActiveCallbacks(true); - mCurrentClient.start(); + + int status = mCurrentClient.start(); + if (status == 0) { + notifyClientActiveCallbacks(true); + } else { + mCurrentClient.onError(getHalDeviceId(), BIOMETRIC_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */); + removeClient(mCurrentClient); + } } protected void removeClient(ClientMonitor client) { @@ -1145,7 +1163,7 @@ public abstract class BiometricServiceBase extends SystemService } } if (mCurrentClient != null) { - if (DEBUG) Slog.v(getTag(), "Done with client: " + client.getOwnerString()); + if (DEBUG) Slog.v(getTag(), "Done with client: " + mCurrentClient.getOwnerString()); mCurrentClient = null; } if (mPendingClient == null) { diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 63a8d7c92441..696daca79092 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -19,6 +19,7 @@ package com.android.server.display; import android.graphics.Rect; import android.hardware.display.DisplayViewport; import android.os.IBinder; +import android.view.Display; import android.view.DisplayAddress; import android.view.Surface; import android.view.SurfaceControl; @@ -78,6 +79,13 @@ abstract class DisplayDevice { } /** + * Gets the id of the display to mirror. + */ + public int getDisplayIdToMirrorLocked() { + return Display.DEFAULT_DISPLAY; + } + + /** * Gets the name of the display device. * * @return The display device name. diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index a23205124f74..3afbf661f97e 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -57,6 +57,7 @@ import android.hardware.display.DisplayedContentSamplingAttributes; import android.hardware.display.IDisplayManager; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; import android.hardware.display.WifiDisplayStatus; import android.hardware.input.InputManagerInternal; import android.media.projection.IMediaProjection; @@ -794,8 +795,8 @@ public final class DisplayManagerService extends SystemService { } private int createVirtualDisplayInternal(IVirtualDisplayCallback callback, - IMediaProjection projection, int callingUid, String packageName, String name, int width, - int height, int densityDpi, Surface surface, int flags, String uniqueId) { + IMediaProjection projection, int callingUid, String packageName, Surface surface, + int flags, VirtualDisplayConfig virtualDisplayConfig) { synchronized (mSyncRoot) { if (mVirtualDisplayAdapter == null) { Slog.w(TAG, "Rejecting request to create private virtual display " @@ -804,8 +805,8 @@ public final class DisplayManagerService extends SystemService { } DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked( - callback, projection, callingUid, packageName, name, width, height, densityDpi, - surface, flags, uniqueId); + callback, projection, callingUid, packageName, surface, flags, + virtualDisplayConfig); if (device == null) { return -1; } @@ -1480,8 +1481,8 @@ public final class DisplayManagerService extends SystemService { if (!ownContent) { if (display != null && !display.hasContentLocked()) { // If the display does not have any content of its own, then - // automatically mirror the default logical display contents. - display = null; + // automatically mirror the requested logical display contents if possible. + display = mLogicalDisplays.get(device.getDisplayIdToMirrorLocked()); } if (display == null) { display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY); @@ -1729,6 +1730,28 @@ public final class DisplayManagerService extends SystemService { } } + @VisibleForTesting + int getDisplayIdToMirrorInternal(int displayId) { + synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null) { + DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked(); + return displayDevice.getDisplayIdToMirrorLocked(); + } + return Display.INVALID_DISPLAY; + } + } + + @VisibleForTesting + Surface getVirtualDisplaySurfaceInternal(IBinder appToken) { + synchronized (mSyncRoot) { + if (mVirtualDisplayAdapter == null) { + return null; + } + return mVirtualDisplayAdapter.getVirtualDisplaySurfaceLocked(appToken); + } + } + private final class DisplayManagerHandler extends Handler { public DisplayManagerHandler(Looper looper) { super(looper, null, true /*async*/); @@ -2050,10 +2073,8 @@ public final class DisplayManagerService extends SystemService { } @Override // Binder call - public int createVirtualDisplay(IVirtualDisplayCallback callback, - IMediaProjection projection, String packageName, String name, - int width, int height, int densityDpi, Surface surface, int flags, - String uniqueId) { + public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig, + IVirtualDisplayCallback callback, IMediaProjection projection, String packageName) { final int callingUid = Binder.getCallingUid(); if (!validatePackageName(callingUid, packageName)) { throw new SecurityException("packageName must match the calling uid"); @@ -2061,13 +2082,12 @@ public final class DisplayManagerService extends SystemService { if (callback == null) { throw new IllegalArgumentException("appToken must not be null"); } - if (TextUtils.isEmpty(name)) { - throw new IllegalArgumentException("name must be non-null and non-empty"); - } - if (width <= 0 || height <= 0 || densityDpi <= 0) { - throw new IllegalArgumentException("width, height, and densityDpi must be " - + "greater than 0"); + if (virtualDisplayConfig == null) { + throw new IllegalArgumentException("virtualDisplayConfig must not be null"); } + final Surface surface = virtualDisplayConfig.getSurface(); + int flags = virtualDisplayConfig.getFlags(); + if (surface != null && surface.isSingleBuffered()) { throw new IllegalArgumentException("Surface can't be single-buffered"); } @@ -2128,7 +2148,7 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { return createVirtualDisplayInternal(callback, projection, callingUid, packageName, - name, width, height, densityDpi, surface, flags, uniqueId); + surface, flags, virtualDisplayConfig); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index f4f2eadfaa8e..ccd88483593a 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -28,6 +28,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPO import android.content.Context; import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; import android.os.Handler; @@ -84,22 +85,24 @@ public class VirtualDisplayAdapter extends DisplayAdapter { } public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback, - IMediaProjection projection, int ownerUid, String ownerPackageName, String name, - int width, int height, int densityDpi, Surface surface, int flags, String uniqueId) { + IMediaProjection projection, int ownerUid, String ownerPackageName, Surface surface, + int flags, VirtualDisplayConfig virtualDisplayConfig) { + String name = virtualDisplayConfig.getName(); boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0; IBinder appToken = callback.asBinder(); IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure); final String baseUniqueId = UNIQUE_ID_PREFIX + ownerPackageName + "," + ownerUid + "," + name + ","; final int uniqueIndex = getNextUniqueIndex(baseUniqueId); + String uniqueId = virtualDisplayConfig.getUniqueId(); if (uniqueId == null) { uniqueId = baseUniqueId + uniqueIndex; } else { uniqueId = UNIQUE_ID_PREFIX + ownerPackageName + ":" + uniqueId; } VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken, - ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags, - new Callback(callback, mHandler), uniqueId, uniqueIndex); + ownerUid, ownerPackageName, surface, flags, new Callback(callback, mHandler), + uniqueId, uniqueIndex, virtualDisplayConfig); mVirtualDisplayDevices.put(appToken, device); @@ -127,6 +130,14 @@ public class VirtualDisplayAdapter extends DisplayAdapter { } } + @VisibleForTesting + Surface getVirtualDisplaySurfaceLocked(IBinder appToken) { + VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken); + if (device != null) { + return device.getSurfaceLocked(); + } + return null; + } public void setVirtualDisplaySurfaceLocked(IBinder appToken, Surface surface) { VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken); @@ -214,20 +225,21 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private int mUniqueIndex; private Display.Mode mMode; private boolean mIsDisplayOn; + private int mDisplayIdToMirror; public VirtualDisplayDevice(IBinder displayToken, IBinder appToken, - int ownerUid, String ownerPackageName, - String name, int width, int height, int densityDpi, Surface surface, int flags, - Callback callback, String uniqueId, int uniqueIndex) { + int ownerUid, String ownerPackageName, Surface surface, int flags, + Callback callback, String uniqueId, int uniqueIndex, + VirtualDisplayConfig virtualDisplayConfig) { super(VirtualDisplayAdapter.this, displayToken, uniqueId); mAppToken = appToken; mOwnerUid = ownerUid; mOwnerPackageName = ownerPackageName; - mName = name; - mWidth = width; - mHeight = height; - mMode = createMode(width, height, REFRESH_RATE); - mDensityDpi = densityDpi; + mName = virtualDisplayConfig.getName(); + mWidth = virtualDisplayConfig.getWidth(); + mHeight = virtualDisplayConfig.getHeight(); + mMode = createMode(mWidth, mHeight, REFRESH_RATE); + mDensityDpi = virtualDisplayConfig.getDensityDpi(); mSurface = surface; mFlags = flags; mCallback = callback; @@ -235,6 +247,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mPendingChanges |= PENDING_SURFACE_CHANGE; mUniqueIndex = uniqueIndex; mIsDisplayOn = surface != null; + mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror(); } @Override @@ -260,6 +273,16 @@ public class VirtualDisplayAdapter extends DisplayAdapter { } @Override + public int getDisplayIdToMirrorLocked() { + return mDisplayIdToMirror; + } + + @VisibleForTesting + Surface getSurfaceLocked() { + return mSurface; + } + + @Override public boolean hasStableUniqueId() { return false; } @@ -332,6 +355,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { pw.println("mFlags=" + mFlags); pw.println("mDisplayState=" + Display.stateToString(mDisplayState)); pw.println("mStopped=" + mStopped); + pw.println("mDisplayIdToMirror=" + mDisplayIdToMirror); } 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..ed3b9f1fc265 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)) { @@ -6212,10 +6227,13 @@ public class NotificationManagerService extends SystemService { cancelNotificationLocked( r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName); cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName, - mSendDelete, childrenFlagChecker); + mSendDelete, childrenFlagChecker, mReason); 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); @@ -6667,7 +6687,7 @@ public class NotificationManagerService extends SystemService { // notification was a summary and its group key changed. if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) { cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */, - null); + null, REASON_APP_CANCEL); } } @@ -7872,7 +7892,7 @@ public class NotificationManagerService extends SystemService { final int M = canceledNotifications.size(); for (int i = 0; i < M; i++) { cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid, - listenerName, false /* sendDelete */, flagChecker); + listenerName, false /* sendDelete */, flagChecker, reason); } updateLightsLocked(); } @@ -7943,7 +7963,7 @@ public class NotificationManagerService extends SystemService { // Warning: The caller is responsible for invoking updateLightsLocked(). @GuardedBy("mNotificationLock") private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid, - String listenerName, boolean sendDelete, FlagChecker flagChecker) { + String listenerName, boolean sendDelete, FlagChecker flagChecker, int reason) { Notification n = r.getNotification(); if (!n.isGroupSummary()) { return; @@ -7957,30 +7977,33 @@ public class NotificationManagerService extends SystemService { } cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName, - sendDelete, true, flagChecker); + sendDelete, true, flagChecker, reason); cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid, - listenerName, sendDelete, false, flagChecker); + listenerName, sendDelete, false, flagChecker, reason); } @GuardedBy("mNotificationLock") private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList, NotificationRecord parentNotification, int callingUid, int callingPid, - String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker) { + String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker, + int reason) { final String pkg = parentNotification.getSbn().getPackageName(); final int userId = parentNotification.getUserId(); - final int reason = REASON_GROUP_SUMMARY_CANCELED; + final int childReason = REASON_GROUP_SUMMARY_CANCELED; for (int i = notificationList.size() - 1; i >= 0; i--) { final NotificationRecord childR = notificationList.get(i); final StatusBarNotification childSbn = childR.getSbn(); if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) && childR.getGroupKey().equals(parentNotification.getGroupKey()) && (childR.getFlags() & FLAG_FOREGROUND_SERVICE) == 0 - && (flagChecker == null || flagChecker.apply(childR.getFlags()))) { + && (flagChecker == null || flagChecker.apply(childR.getFlags())) + && (!childR.getChannel().isImportantConversation() + || reason != REASON_CANCEL)) { EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(), - childSbn.getTag(), userId, 0, 0, reason, listenerName); + childSbn.getTag(), userId, 0, 0, childReason, listenerName); notificationList.remove(i); mNotificationsByKey.remove(childR.getKey()); - cancelNotificationLocked(childR, sendDelete, reason, wasPosted, listenerName); + cancelNotificationLocked(childR, sendDelete, childReason, wasPosted, listenerName); } } } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 1b271a739777..1d5c30438870 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -47,6 +47,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutQueryWrapper; import android.content.pm.ShortcutServiceInternal; import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; import android.content.pm.UserInfo; @@ -698,13 +699,19 @@ public class LauncherAppsService extends SystemService { } @Override - public ParceledListSlice getShortcuts(String callingPackage, long changedSince, - String packageName, List shortcutIds, List<LocusId> locusIds, - ComponentName componentName, int flags, UserHandle targetUser) { + public ParceledListSlice getShortcuts(@NonNull final String callingPackage, + @NonNull final ShortcutQueryWrapper query, @NonNull final UserHandle targetUser) { ensureShortcutPermission(callingPackage); if (!canAccessProfile(targetUser.getIdentifier(), "Cannot get shortcuts")) { return new ParceledListSlice<>(Collections.EMPTY_LIST); } + + final long changedSince = query.getChangedSince(); + final String packageName = query.getPackage(); + final List<String> shortcutIds = query.getShortcutIds(); + final List<LocusId> locusIds = query.getLocusIds(); + final ComponentName componentName = query.getActivity(); + final int flags = query.getQueryFlags(); if (shortcutIds != null && packageName == null) { throw new IllegalArgumentException( "To query by shortcut ID, package name must also be set"); @@ -723,16 +730,17 @@ public class LauncherAppsService extends SystemService { } @Override - public void registerShortcutChangeCallback(String callingPackage, long changedSince, - String packageName, List shortcutIds, List<LocusId> locusIds, - ComponentName componentName, int flags, IShortcutChangeCallback callback) { + public void registerShortcutChangeCallback(@NonNull final String callingPackage, + @NonNull final ShortcutQueryWrapper query, + @NonNull final IShortcutChangeCallback callback) { + ensureShortcutPermission(callingPackage); - if (shortcutIds != null && packageName == null) { + if (query.getShortcutIds() != null && query.getPackage() == null) { throw new IllegalArgumentException( "To query by shortcut ID, package name must also be set"); } - if (locusIds != null && packageName == null) { + if (query.getLocusIds() != null && query.getPackage() == null) { throw new IllegalArgumentException( "To query by locus ID, package name must also be set"); } @@ -744,10 +752,7 @@ public class LauncherAppsService extends SystemService { user = null; } - // TODO: When ShortcutQueryWrapper (ag/10323729) is available, pass that directly. - ShortcutChangeHandler.QueryInfo query = new ShortcutChangeHandler.QueryInfo( - changedSince, packageName, shortcutIds, locusIds, componentName, flags, user); - mShortcutChangeHandler.addShortcutChangeCallback(callback, query); + mShortcutChangeHandler.addShortcutChangeCallback(callback, query, user); } @Override @@ -1081,9 +1086,11 @@ public class LauncherAppsService extends SystemService { new RemoteCallbackList<>(); public synchronized void addShortcutChangeCallback(IShortcutChangeCallback callback, - QueryInfo query) { + ShortcutQueryWrapper query, UserHandle user) { mCallbacks.unregister(callback); - mCallbacks.register(callback, query); + mCallbacks.register(callback, new QueryInfo(query.getChangedSince(), + query.getPackage(), query.getShortcutIds(), query.getLocusIds(), + query.getActivity(), query.getQueryFlags(), user)); } public synchronized void removeShortcutChangeCallback( diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 27288d852fb2..161f30449a52 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -30,7 +30,6 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.AppOpsManagerInternal; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -48,7 +47,6 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManagerInternal; import android.permission.PermissionControllerManager; -import android.provider.Settings; import android.provider.Telephony; import android.telecom.TelecomManager; import android.util.ArrayMap; @@ -72,9 +70,7 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.ExecutionException; /** @@ -184,6 +180,8 @@ public final class PermissionPolicyService extends SystemService { intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); intentFilter.addDataScheme("package"); + + /* TODO ntmyren: enable receiver when test flakes are fixed getContext().registerReceiverAsUser(new BroadcastReceiver() { final List<Integer> mUserSetupUids = new ArrayList<>(200); final Map<UserHandle, PermissionControllerManager> mPermControllerManagers = @@ -234,6 +232,7 @@ public final class PermissionPolicyService extends SystemService { manager.updateUserSensitiveForApp(uid); } }, UserHandle.ALL, intentFilter, null, null); + */ } /** diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 7eb3f01798c5..d89605a9ddbd 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -542,12 +542,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { void unregisterPointerEventListener(PointerEventListener listener, int displayId); /** - * Retrieves the {@param outBounds} from the stack matching the {@param windowingMode} and - * {@param activityType}. - */ - void getStackBounds(int windowingMode, int activityType, Rect outBounds); - - /** * @return The currently active input method window. */ WindowState getInputMethodWindowLw(); diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java index 778683369de5..c2ecd41e7dbf 100644 --- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java +++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java @@ -176,8 +176,12 @@ public class TestHarnessModeService extends SystemService { private void setUpAdbFiles(PersistentData persistentData) { AdbManagerInternal adbManager = LocalServices.getService(AdbManagerInternal.class); - writeBytesToFile(persistentData.mAdbKeys, adbManager.getAdbKeysFile().toPath()); - writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath()); + if (adbManager.getAdbKeysFile() != null) { + writeBytesToFile(persistentData.mAdbKeys, adbManager.getAdbKeysFile().toPath()); + } + if (adbManager.getAdbTempKeysFile() != null) { + writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath()); + } } private void configureUser() { @@ -310,12 +314,6 @@ public class TestHarnessModeService extends SystemService { AdbManagerInternal adbManager = LocalServices.getService(AdbManagerInternal.class); File adbKeys = adbManager.getAdbKeysFile(); File adbTempKeys = adbManager.getAdbTempKeysFile(); - if (adbKeys == null && adbTempKeys == null) { - // This should only be accessible on eng builds that haven't yet set up ADB keys - getErrPrintWriter() - .println("No ADB keys stored; not enabling test harness mode"); - return 1; - } try { byte[] adbKeysBytes = getBytesFromFile(adbKeys); diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java index a16dbb726613..3f2b5c231dca 100644 --- a/services/core/java/com/android/server/vr/Vr2dDisplay.java +++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java @@ -11,6 +11,7 @@ import android.content.IntentFilter; import android.graphics.PixelFormat; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; +import android.hardware.display.VirtualDisplayConfig; import android.media.ImageReader; import android.os.Handler; import android.os.RemoteException; @@ -295,10 +296,12 @@ class Vr2dDisplay { flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi); + builder.setUniqueId(UNIQUE_DISPLAY_ID); + builder.setFlags(flags); mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */, - DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi, - null /* surface */, flags, null /* callback */, null /* handler */, - UNIQUE_DISPLAY_ID); + builder.build(), null /* callback */, null /* handler */); if (mVirtualDisplay != null) { updateDisplayId(mVirtualDisplay.getDisplay().getDisplayId()); diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 10b335e583b0..a446720c1c39 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -3553,9 +3553,8 @@ class ActivityStack extends Task { } } - void reparent(DisplayContent newParent, boolean onTop) { - // Real parent of stack is within display object, so we have to delegate re-parenting there. - newParent.moveStackToDisplay(this, onTop); + void reparent(TaskDisplayArea newParent, boolean onTop) { + reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM); } private void updateSurfaceBounds() { diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 2c7ce9104c3c..8af862473386 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -412,7 +412,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final ActivityStack stack = (ActivityStack) task; stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); if (mToDisplay.getDisplayId() != stack.getDisplayId()) { - mToDisplay.moveStackToDisplay(stack, mOnTop); + stack.reparent(mToDisplay.getDefaultTaskDisplayArea(), mOnTop); } else if (mOnTop) { mToDisplay.mTaskContainers.positionStackAtTop(stack, false /* includingParents */); @@ -566,8 +566,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } void moveRecentsStackToFront(String reason) { - final ActivityStack recentsStack = mRootWindowContainer.getDefaultDisplay().getStack( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); + final ActivityStack recentsStack = mRootWindowContainer.getDefaultTaskDisplayArea() + .getStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); if (recentsStack != null) { recentsStack.moveToFront(reason); } @@ -2613,7 +2613,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // from whatever is started from the recents activity, so move the home stack // forward. // TODO (b/115289124): Multi-display supports for recents. - mRootWindowContainer.getDefaultDisplay().mTaskContainers.moveHomeStackToFront( + mRootWindowContainer.getDefaultTaskDisplayArea().moveHomeStackToFront( "startActivityFromRecents"); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 682e991bdad2..0b1968765300 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); @@ -4280,9 +4283,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - final DisplayContent dc = mRootWindowContainer.getDefaultDisplay(); - final Task primary = dc.getRootSplitScreenPrimaryTask(); - final Task secondary = dc.getTask(t -> t.mCreatedByOrganizer && t.isRootTask() + final TaskDisplayArea tc = mRootWindowContainer.getDefaultTaskDisplayArea(); + final Task primary = tc.getRootSplitScreenPrimaryTask(); + final Task secondary = tc.getTask(t -> t.mCreatedByOrganizer && t.isRootTask() && t.inSplitScreenSecondaryWindowingMode()); if (primary == null || secondary == null) { return; @@ -4298,7 +4301,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (otherRect == null) { // Temporary estimation... again this is just for tests. otherRect = new Rect(secondary.getBounds()); - if (dc.getBounds().width() > dc.getBounds().height()) { + if (tc.getBounds().width() > tc.getBounds().height()) { otherRect.left = primaryRect.right + 6; } else { otherRect.top = primaryRect.bottom + 6; diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java index 682a14220dff..e8becfa27fac 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -54,7 +54,7 @@ import java.util.Map; * - DisplayArea.Root * - Magnification * - DisplayArea.Tokens (Wallpapers are attached here) - * - TaskContainers + * - TaskDisplayArea * - DisplayArea.Tokens (windows above Tasks up to IME are attached here) * - ImeContainers * - DisplayArea.Tokens (windows above IME up to TYPE_ACCESSIBILITY_OVERLAY attached here) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ce7e79714c39..85517a44d656 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; @@ -574,13 +575,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private RootWindowContainer mRootWindowContainer; - /** - * All of the stacks on this display. Order matters, topmost stack is in front of all other - * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls - * changing the list should also call {@link #onStackOrderChanged()}. - */ - private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>(); - /** Array of all UIDs that are present on the display. */ private IntArray mDisplayAccessUIDs = new IntArray(); @@ -822,8 +816,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 @@ -2059,23 +2055,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return (mDisplay.getFlags() & FLAG_PRIVATE) != 0; } - ActivityStack getRootHomeTask() { - return mTaskContainers.getRootHomeTask(); - } - - /** @return The primary split-screen task, and {@code null} otherwise. */ - @Nullable ActivityStack getRootSplitScreenPrimaryTask() { - return mTaskContainers.getRootSplitScreenPrimaryTask(); - } - - ActivityStack getRootPinnedTask() { - return mTaskContainers.getRootPinnedTask(); - } - - boolean hasPinnedTask() { - return mTaskContainers.getRootPinnedTask() != null; - } - /** * Returns the topmost stack on the display that is compatible with the input windowing mode and * activity type. Null is no compatible stack on the display. @@ -2092,32 +2071,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mTaskContainers.mChildren.get(index); } - int getIndexOf(ActivityStack stack) { - return mTaskContainers.getIndexOf(stack); - } - - void removeStack(ActivityStack stack) { - mTaskContainers.removeChild(stack); - } - - @VisibleForTesting - WindowList<ActivityStack> getStacks() { - return mTaskContainers.mChildren; - } - @VisibleForTesting ActivityStack getTopStack() { return mTaskContainers.getTopStack(); } - ArrayList<Task> getVisibleTasks() { - return mTaskContainers.getVisibleTasks(); - } - - SurfaceControl getSplitScreenDividerAnchor() { - return mTaskContainers.getSplitScreenDividerAnchor(); - } - /** * The value is only valid in the scope {@link #onRequestedOverrideConfigurationChanged} of the * changing hierarchy and the {@link #onConfigurationChanged} of its children. @@ -2406,8 +2364,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo out.set(mDisplayFrames.mStable); } - void moveStackToDisplay(ActivityStack stack, boolean onTop) { - stack.reparent(mTaskContainers, onTop ? POSITION_TOP: POSITION_BOTTOM); + /** + * Get the default display area on the display dedicated to app windows. This one should be used + * only as a fallback location for activity launches when no target display area is specified, + * or for cases when multi-instance is not supported yet (like Split-screen, PiP or Recents). + */ + TaskDisplayArea getDefaultTaskDisplayArea() { + return mTaskContainers; } @Override @@ -2470,7 +2433,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ Task findTaskForResizePoint(int x, int y) { final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); - return mTmpTaskForResizePointSearchResult.process(mTaskContainers, x, y, delta); + return mTmpTaskForResizePointSearchResult.process(getDefaultTaskDisplayArea(), x, y, delta); } void updateTouchExcludeRegion() { @@ -2509,8 +2472,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION); } amendWindowTapExcludeRegion(mTouchExcludeRegion); - // TODO(multi-display): Support docked stacks on secondary displays. - if (mDisplayId == DEFAULT_DISPLAY && mTaskContainers.isSplitScreenModeActivated()) { + // TODO(multi-display): Support docked stacks on secondary displays & task containers. + if (mDisplayId == DEFAULT_DISPLAY + && getDefaultTaskDisplayArea().isSplitScreenModeActivated()) { mDividerControllerLocked.getTouchRegion(mTmpRect); mTmpRegion.set(mTmpRect); mTouchExcludeRegion.op(mTmpRegion, Op.UNION); @@ -2905,20 +2869,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.println(); // Dump stack references - final ActivityStack homeStack = getRootHomeTask(); + final ActivityStack homeStack = getDefaultTaskDisplayArea().getRootHomeTask(); if (homeStack != null) { pw.println(prefix + "homeStack=" + homeStack.getName()); } - final ActivityStack pinnedStack = getRootPinnedTask(); + final ActivityStack pinnedStack = getDefaultTaskDisplayArea().getRootPinnedTask(); if (pinnedStack != null) { pw.println(prefix + "pinnedStack=" + pinnedStack.getName()); } - final ActivityStack splitScreenPrimaryStack = getRootSplitScreenPrimaryTask(); + final ActivityStack splitScreenPrimaryStack = getDefaultTaskDisplayArea() + .getRootSplitScreenPrimaryTask(); if (splitScreenPrimaryStack != null) { pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName()); } - final ActivityStack recentsStack = - getStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); + // TODO: Support recents on non-default task containers + final ActivityStack recentsStack = getDefaultTaskDisplayArea().getStack( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); if (recentsStack != null) { pw.println(prefix + "recentsStack=" + recentsStack.getName()); } @@ -2952,12 +2918,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\""; } - /** Returns true if the stack in the windowing mode is visible. */ - boolean isStackVisible(int windowingMode) { - final ActivityStack stack = mTaskContainers.getTopStackInWindowingMode(windowingMode); - return stack != null && stack.isVisible(); - } - /** Find the visible, touch-deliverable window under the given point */ WindowState getTouchableWinAtPointLocked(float xf, float yf) { final int x = (int) xf; @@ -4364,7 +4324,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // We skip IME windows so they're processed just above their target, except // in split-screen mode where we process the IME containers above the docked divider. return dc.mInputMethodTarget != null - && !dc.mTaskContainers.isSplitScreenModeActivated(); + && !dc.getDefaultTaskDisplayArea().isSplitScreenModeActivated(); } /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */ @@ -5259,7 +5219,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // released (no more ActivityStack). But, we cannot release it at that moment or the // related WindowContainer will also be removed. So, we set display as removed after // reparenting stack finished. - final DisplayContent toDisplay = mRootWindowContainer.getDefaultDisplay(); + final TaskDisplayArea toTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); mRootWindowContainer.mStackSupervisor.beginDeferResume(); try { int numStacks = getStackCount(); @@ -5273,10 +5233,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // If default display is in split-window mode, set windowing mode of the stack // to split-screen secondary. Otherwise, set the windowing mode to undefined by // default to let stack inherited the windowing mode from the new display. - final int windowingMode = toDisplay.mTaskContainers.isSplitScreenModeActivated() + final int windowingMode = toTaskDisplayArea.isSplitScreenModeActivated() ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_UNDEFINED; - stack.reparent(toDisplay, true /* onTop */); + stack.reparent(toTaskDisplayArea, true /* onTop */); stack.setWindowingMode(windowingMode); lastReparentedStack = stack; } @@ -5389,34 +5349,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mSleeping = asleep; } - /** - * Adds a listener to be notified whenever the stack order in the display changes. Currently - * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the - * current animation when the system state changes. - */ - void registerStackOrderChangedListener(OnStackOrderChangedListener listener) { - if (!mStackOrderChangedCallbacks.contains(listener)) { - mStackOrderChangedCallbacks.add(listener); - } - } - - /** - * Removes a previously registered stack order change listener. - */ - void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) { - mStackOrderChangedCallbacks.remove(listener); - } - - /** - * Notifies of a stack order change - * @param stack The stack which triggered the order change - */ - void onStackOrderChanged(ActivityStack stack) { - for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) { - mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack); - } - } - void setDisplayToSingleTaskInstance() { final int childCount = getStackCount(); if (childCount > 1) { @@ -5445,22 +5377,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } /** - * Callback for when the order of the stacks in the display changes. - */ - interface OnStackOrderChangedListener { - void onStackOrderChanged(ActivityStack stack); - } - - public void dumpStacks(PrintWriter pw) { - for (int i = getStackCount() - 1; i >= 0; --i) { - pw.print(getStackAt(i).getRootTaskId()); - if (i > 0) { - pw.print(","); - } - } - } - - /** * Similar to {@link RootWindowContainer#isAnyNonToastWindowVisibleForUid(int)}, but * used for pid. */ diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 367151cf0f79..264da9fc681b 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2653,8 +2653,8 @@ public class DisplayPolicy { if (mStatusBarController.setBarShowingLw(true)) { changes |= FINISH_LAYOUT_REDO_LAYOUT; } - } else if (topIsFullscreen - && !mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { + } else if (topIsFullscreen && !mDisplayContent.getDefaultTaskDisplayArea() + .isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar"); if (mStatusBarController.setBarShowingLw(false)) { changes |= FINISH_LAYOUT_REDO_LAYOUT; @@ -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); @@ -3457,10 +3462,10 @@ public class DisplayPolicy { } private Pair<Integer, WindowState> updateSystemBarsLw(WindowState win, int oldVis, int vis) { - final boolean dockedStackVisible = - mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - final boolean freeformStackVisible = - mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM); + final boolean dockedStackVisible = mDisplayContent.getDefaultTaskDisplayArea() + .isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + final boolean freeformStackVisible = mDisplayContent.getDefaultTaskDisplayArea() + .isStackVisible(WINDOWING_MODE_FREEFORM); final boolean resizing = mDisplayContent.getDockedDividerController().isResizing(); // We need to force system bars when the docked stack is visible, when the freeform stack diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index af89a05bfa62..bef80f0a230a 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -594,7 +594,8 @@ public class DisplayRotation { // In the presence of the PINNED stack or System Alert windows we unfortunately can not // seamlessly rotate. - if (mDisplayContent.hasPinnedTask() || mDisplayContent.hasAlertWindowSurfaces()) { + if (mDisplayContent.getDefaultTaskDisplayArea().hasPinnedTask() + || mDisplayContent.hasAlertWindowSurfaces()) { return false; } diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java index a9a9df8458e2..a6066684d850 100644 --- a/services/core/java/com/android/server/wm/InputConsumerImpl.java +++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java @@ -153,10 +153,11 @@ class InputConsumerImpl implements IBinder.DeathRecipient { t.reparent(mInputSurface, wc.getSurfaceControl()); } - void disposeChannelsLw() { + void disposeChannelsLw(SurfaceControl.Transaction t) { mService.mInputManager.unregisterInputChannel(mServerChannel); mClientChannel.dispose(); mServerChannel.dispose(); + t.remove(mInputSurface); unlinkFromDeathRecipient(); } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 18332b9484c0..8b34b9b8dd8f 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -112,8 +112,9 @@ final class InputMonitor { @Override public void dispose() { synchronized (mService.mGlobalLock) { - disposeChannelsLw(); + disposeChannelsLw(mInputMonitor.mInputTransaction); mInputEventReceiver.dispose(); + mInputMonitor.updateInputWindowsLw(true /* force */); } } } @@ -195,8 +196,7 @@ final class InputMonitor { private boolean disposeInputConsumer(InputConsumerImpl consumer) { if (consumer != null) { - consumer.disposeChannelsLw(); - consumer.hide(mInputTransaction); + consumer.disposeChannelsLw(mInputTransaction); return true; } return false; diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 007af249c580..e4e57168efe7 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -295,10 +295,10 @@ class InsetsPolicy { } private boolean forceShowsSystemBarsForWindowingMode() { - final boolean isDockedStackVisible = - mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - final boolean isFreeformStackVisible = - mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM); + final boolean isDockedStackVisible = mDisplayContent.getDefaultTaskDisplayArea() + .isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + final boolean isFreeformStackVisible = mDisplayContent.getDefaultTaskDisplayArea() + .isStackVisible(WINDOWING_MODE_FREEFORM); final boolean isResizing = mDisplayContent.getDockedDividerController().isResizing(); // We need to force system bars when the docked stack is visible, when the freeform stack diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index a936e74bf694..57a54d0e84ef 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -406,11 +406,12 @@ class KeyguardController { // show on top of the lock screen. In this can we want to dismiss the docked // stack since it will be complicated/risky to try to put the activity on top // of the lock screen in the right fullscreen configuration. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - if (!display.mTaskContainers.isSplitScreenModeActivated()) { + final TaskDisplayArea taskDisplayArea = mRootWindowContainer + .getDefaultTaskDisplayArea(); + if (!taskDisplayArea.isSplitScreenModeActivated()) { return; } - display.mTaskContainers.onSplitScreenModeDismissed(); + taskDisplayArea.onSplitScreenModeDismissed(); } } diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index 66dbfd5f2a84..02a27410dc38 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -286,7 +286,8 @@ class PinnedStackController { } try { final Rect animatingBounds = new Rect(); - final ActivityStack pinnedStack = mDisplayContent.getRootPinnedTask(); + final ActivityStack pinnedStack = mDisplayContent.getDefaultTaskDisplayArea() + .getRootPinnedTask(); if (pinnedStack != null) { pinnedStack.getAnimationOrCurrentBounds(animatingBounds); } diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index 08b0abf5e9be..a031fe82d48b 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -52,14 +52,14 @@ import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallback * cleanup. See {@link com.android.server.wm.RecentsAnimationController}. */ class RecentsAnimation implements RecentsAnimationCallbacks, - DisplayContent.OnStackOrderChangedListener { + TaskDisplayArea.OnStackOrderChangedListener { private static final String TAG = RecentsAnimation.class.getSimpleName(); private final ActivityTaskManagerService mService; private final ActivityStackSupervisor mStackSupervisor; private final ActivityStartController mActivityStartController; private final WindowManagerService mWindowManager; - private final DisplayContent mDefaultDisplay; + private final TaskDisplayArea mDefaultTaskDisplayArea; private final Intent mTargetIntent; private final ComponentName mRecentsComponent; private final @Nullable String mRecentsFeatureId; @@ -84,7 +84,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, int recentsUid, @Nullable WindowProcessController caller) { mService = atm; mStackSupervisor = stackSupervisor; - mDefaultDisplay = mService.mRootWindowContainer.getDefaultDisplay(); + mDefaultTaskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea(); mActivityStartController = activityStartController; mWindowManager = wm; mTargetIntent = targetIntent; @@ -107,7 +107,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, void preloadRecentsActivity() { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Preload recents with %s", mTargetIntent); - ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, + ActivityStack targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType); ActivityRecord targetActivity = getTargetActivity(targetStack); if (targetActivity != null) { @@ -128,7 +128,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // Create the activity record. Because the activity is invisible, this doesn't really // start the client. startRecentsActivityInBackground("preloadRecents"); - targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType); + targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED, + mTargetActivityType); targetActivity = getTargetActivity(targetStack); if (targetActivity == null) { Slog.w(TAG, "Cannot start " + mTargetIntent); @@ -176,12 +177,11 @@ class RecentsAnimation implements RecentsAnimationCallbacks, } // If the activity is associated with the recents stack, then try and get that first - ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, + ActivityStack targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType); ActivityRecord targetActivity = getTargetActivity(targetStack); final boolean hasExistingActivity = targetActivity != null; if (hasExistingActivity) { - final TaskDisplayArea taskDisplayArea = targetActivity.getDisplayArea(); mRestoreTargetBehindStack = getStackAbove(targetStack); if (mRestoreTargetBehindStack == null) { notifyAnimationCancelBeforeStart(recentsAnimationRunner); @@ -209,7 +209,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, try { if (hasExistingActivity) { // Move the recents activity into place for the animation if it is not top most - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(targetStack); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(targetStack); ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved stack=%s behind stack=%s", targetStack, getStackAbove(targetStack)); @@ -225,10 +225,10 @@ class RecentsAnimation implements RecentsAnimationCallbacks, startRecentsActivityInBackground("startRecentsActivity_noTargetActivity"); // Move the recents activity into place for the animation - targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, + targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType); targetActivity = getTargetActivity(targetStack); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(targetStack); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(targetStack); ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved stack=%s behind stack=%s", targetStack, getStackAbove(targetStack)); @@ -251,7 +251,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, mWindowManager.cancelRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity"); mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner, - this, mDefaultDisplay.getDisplayId(), + this, mDefaultTaskDisplayArea.getDisplayId(), mStackSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity); // If we updated the launch-behind state, update the visibility of the activities after @@ -262,7 +262,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, START_TASK_TO_FRONT, targetActivity); // Register for stack order changes - mDefaultDisplay.registerStackOrderChangedListener(this); + mDefaultTaskDisplayArea.registerStackOrderChangedListener(this); } catch (Exception e) { Slog.e(TAG, "Failed to start recents activity", e); throw e; @@ -280,7 +280,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, mWindowManager.getRecentsAnimationController(), reorderMode); // Unregister for stack order changes - mDefaultDisplay.unregisterStackOrderChangedListener(this); + mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(this); final RecentsAnimationController controller = mWindowManager.getRecentsAnimationController(); @@ -308,7 +308,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, try { mWindowManager.cleanupRecentsAnimation(reorderMode); - final ActivityStack targetStack = mDefaultDisplay.getStack( + final ActivityStack targetStack = mDefaultTaskDisplayArea.getStack( WINDOWING_MODE_UNDEFINED, mTargetActivityType); // Prefer to use the original target activity instead of top activity because // we may have moved another task to top (starting 3p launcher). @@ -422,7 +422,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, @Override public void onStackOrderChanged(ActivityStack stack) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "onStackOrderChanged(): stack=%s", stack); - if (mDefaultDisplay.getIndexOf(stack) == -1 || !stack.shouldBeVisible(null)) { + if (mDefaultTaskDisplayArea.getIndexOf(stack) == -1 || !stack.shouldBeVisible(null)) { // The stack is not visible, so ignore this change return; } @@ -480,8 +480,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, * @return The top stack that is not always-on-top. */ private ActivityStack getTopNonAlwaysOnTopStack() { - for (int i = mDefaultDisplay.getStackCount() - 1; i >= 0; i--) { - final ActivityStack s = mDefaultDisplay.getStackAt(i); + for (int i = mDefaultTaskDisplayArea.getStackCount() - 1; i >= 0; i--) { + final ActivityStack s = mDefaultTaskDisplayArea.getStackAt(i); if (s.getWindowConfiguration().isAlwaysOnTop()) { continue; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index a30b70de267d..84229f003032 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -340,9 +340,11 @@ public class RecentsAnimationController implements DeathRecipient { // Make leashes for each of the visible/target tasks and add it to the recents animation to // be started - final ArrayList<Task> visibleTasks = mDisplayContent.getVisibleTasks(); - final ActivityStack targetStack = mDisplayContent.getStack(WINDOWING_MODE_UNDEFINED, - targetActivityType); + // TODO(multi-display-area): Support Recents on multiple task display areas + final ArrayList<Task> visibleTasks = mDisplayContent.getDefaultTaskDisplayArea() + .getVisibleTasks(); + final ActivityStack targetStack = mDisplayContent.getDefaultTaskDisplayArea() + .getStack(WINDOWING_MODE_UNDEFINED, targetActivityType); if (targetStack != null) { final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) -> { if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class), @@ -385,7 +387,8 @@ public class RecentsAnimationController implements DeathRecipient { } // Save the minimized home height - mMinimizedHomeBounds = mDisplayContent.getRootHomeTask().getBounds(); + mMinimizedHomeBounds = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask() + .getBounds(); mService.mWindowPlacerLocked.performSurfacePlacement(); diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java index 770c08889ab9..32de699eaae9 100644 --- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java +++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java @@ -232,10 +232,11 @@ class ResetTargetTaskHelper { } final ActivityTaskManagerService atmService = mTargetStack.mAtmService; - DisplayContent display = mTargetStack.getDisplay(); - final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance(); + TaskDisplayArea taskDisplayArea = mTargetStack.getDisplayArea(); + final boolean singleTaskInstanceDisplay = + taskDisplayArea.mDisplayContent.isSingleTaskInstance(); if (singleTaskInstanceDisplay) { - display = atmService.mRootWindowContainer.getDefaultDisplay(); + taskDisplayArea = atmService.mRootWindowContainer.getDefaultTaskDisplayArea(); } final int windowingMode = mTargetStack.getWindowingMode(); @@ -246,7 +247,7 @@ class ResetTargetTaskHelper { final boolean alwaysCreateTask = DisplayContent.alwaysCreateStack(windowingMode, activityType); final Task task = alwaysCreateTask - ? display.getBottomMostTask() : mTargetStack.getBottomMostTask(); + ? taskDisplayArea.getBottomMostTask() : mTargetStack.getBottomMostTask(); Task targetTask = null; if (task != null && r.taskAffinity.equals(task.affinity)) { // If the activity currently at the bottom has the same task affinity as @@ -257,7 +258,7 @@ class ResetTargetTaskHelper { } if (targetTask == null) { if (alwaysCreateTask) { - targetTask = display.mTaskContainers.getOrCreateStack(windowingMode, + targetTask = taskDisplayArea.getOrCreateStack(windowingMode, activityType, false /* onTop */); } else { targetTask = mTargetStack.reuseOrCreateTask(r.info, null /*intent*/, diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2764b121cc00..2eeda4dcfeed 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1369,11 +1369,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } calculateDefaultMinimalSizeOfResizeableTasks(); - final DisplayContent defaultDisplay = getDefaultDisplay(); - - defaultDisplay.mTaskContainers.getOrCreateStack(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_HOME, ON_TOP); - positionChildAt(POSITION_TOP, defaultDisplay, false /* includingParents */); + final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea(); + defaultTaskDisplayArea.getOrCreateStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, + ON_TOP); + positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent, + false /* includingParents */); } // TODO(multi-display): Look at all callpoints to make sure they make sense in multi-display. @@ -1382,6 +1382,16 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } /** + * Get the default display area on the device dedicated to app windows. This one should be used + * only as a fallback location for activity launches when no target display area is specified, + * or for cases when multi-instance is not supported yet (like Split-screen, Freeform, PiP or + * Recents). + */ + TaskDisplayArea getDefaultTaskDisplayArea() { + return mDefaultDisplay.getDefaultTaskDisplayArea(); + } + + /** * Get an existing instance of {@link DisplayContent} that has the given uniqueId. Unique ID is * defined in {@link DisplayInfo#uniqueId}. * @@ -1436,12 +1446,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return displayContent; } - ActivityRecord getDefaultDisplayHomeActivity() { - return getDefaultDisplayHomeActivityForUser(mCurrentUser); - } - ActivityRecord getDefaultDisplayHomeActivityForUser(int userId) { - return getDisplayContent(DEFAULT_DISPLAY).mTaskContainers.getHomeActivityForUser(userId); + return getDefaultTaskDisplayArea().getHomeActivityForUser(userId); } boolean startHomeOnAllDisplays(int userId, String reason) { @@ -1972,8 +1978,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final int focusStackId = topFocusedStack != null ? topFocusedStack.getRootTaskId() : INVALID_TASK_ID; // We dismiss the docked stack whenever we switch users. - if (getDefaultDisplay().mTaskContainers.isSplitScreenModeActivated()) { - getDefaultDisplay().mTaskContainers.onSplitScreenModeDismissed(); + if (getDefaultTaskDisplayArea().isSplitScreenModeActivated()) { + getDefaultTaskDisplayArea().onSplitScreenModeDismissed(); } // Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will // also cause all tasks to be moved to the fullscreen stack at a position that is @@ -1995,7 +2001,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final int restoreStackId = mUserStackInFront.get(userId); ActivityStack stack = getStack(restoreStackId); if (stack == null) { - stack = getDefaultDisplay().mTaskContainers.getOrCreateRootHomeTask(); + stack = getDefaultTaskDisplayArea().getOrCreateRootHomeTask(); } final boolean homeInFront = stack.isActivityTypeHome(); if (stack.isOnHomeDisplay()) { @@ -2018,7 +2024,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void updateUserStack(int userId, ActivityStack stack) { if (userId != mCurrentUser) { if (stack == null) { - stack = getDefaultDisplay().mTaskContainers.getOrCreateRootHomeTask(); + stack = getDefaultTaskDisplayArea().getOrCreateRootHomeTask(); } mUserStackInFront.put(userId, stack.getRootTaskId()); @@ -2061,7 +2067,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return; } - stack.reparent(displayContent.mDisplayContent, onTop); + stack.reparent(displayContent.getDefaultTaskDisplayArea(), onTop); // TODO(multi-display): resize stacks properly if moved from split-screen. } @@ -2155,8 +2161,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Looking up task on preferred display first final DisplayContent preferredDisplay = getDisplayContent(preferredDisplayId); if (preferredDisplay != null) { - preferredDisplay.mTaskContainers.findTaskLocked(r, true /* isPreferredDisplay */, - mTmpFindTaskResult); + preferredDisplay.getDefaultTaskDisplayArea().findTaskLocked(r, + true /* isPreferredDisplay */, mTmpFindTaskResult); if (mTmpFindTaskResult.mIdealMatch) { return mTmpFindTaskResult.mRecord; } @@ -2168,7 +2174,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> continue; } - display.mTaskContainers.findTaskLocked(r, false /* isPreferredDisplay */, + display.getDefaultTaskDisplayArea().findTaskLocked(r, false /* isPreferredDisplay */, mTmpFindTaskResult); if (mTmpFindTaskResult.mIdealMatch) { return mTmpFindTaskResult.mRecord; @@ -2788,8 +2794,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } final DisplayContent display = getDisplayContentOrCreate(displayId); if (display != null) { - stack = display.mTaskContainers.getOrCreateStack(r, options, candidateTask, - activityType, onTop); + // Falling back to default task container + final TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); + stack = taskDisplayArea.getOrCreateStack(r, options, candidateTask, activityType, + onTop); if (stack != null) { return stack; } @@ -2835,7 +2843,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (container == null || !canLaunchOnDisplay(r, container.mDisplayContent.mDisplayId)) { - container = getDefaultDisplay().mTaskContainers; + container = getDefaultTaskDisplayArea(); if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) { windowingMode = container.resolveWindowingMode(r, options, candidateTask, activityType); @@ -2887,7 +2895,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // it to the target display. if (candidateTask.isRootTask()) { final ActivityStack stack = candidateTask.getStack(); - displayContent.moveStackToDisplay(stack, true /* onTop */); + stack.reparent(displayContent.getDefaultTaskDisplayArea(), true /* onTop */); return stack; } } @@ -2918,7 +2926,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final int activityType = options != null && options.getLaunchActivityType() != ACTIVITY_TYPE_UNDEFINED ? options.getLaunchActivityType() : r.getActivityType(); - return displayContent.createStack(windowingMode, activityType, true /*onTop*/); + final TaskDisplayArea taskDisplayArea = displayContent.getDefaultTaskDisplayArea(); + return taskDisplayArea.createStack(windowingMode, activityType, true /*onTop*/); } return null; @@ -2989,7 +2998,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (preferredDisplayArea == null) { // Stack is currently detached because it is being removed. Use the previous display it // was on. - preferredDisplayArea = getDisplayContent(currentFocus.mPrevDisplayId).mTaskContainers; + preferredDisplayArea = getDisplayContent(currentFocus.mPrevDisplayId) + .getDefaultTaskDisplayArea(); } final ActivityStack preferredFocusableStack = preferredDisplayArea.getNextFocusableStack( currentFocus, ignoreCurrent); @@ -3010,7 +3020,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // We've already checked this one continue; } - final ActivityStack nextFocusableStack = display.mTaskContainers + final ActivityStack nextFocusableStack = display.getDefaultTaskDisplayArea() .getNextFocusableStack(currentFocus, ignoreCurrent); if (nextFocusableStack != null) { return nextFocusableStack; @@ -3020,31 +3030,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return null; } - /** - * Get next valid stack for launching provided activity in the system. This will search across - * displays and stacks in last-focused order for a focusable and visible stack, except those - * that are on a currently focused display. - * - * @param r The activity that is being launched. - * @param currentFocus The display that previously had focus and thus needs to be ignored when - * searching for the next candidate. - * @return Next valid {@link ActivityStack}, null if not found. - */ - ActivityStack getNextValidLaunchStack(@NonNull ActivityRecord r, int currentFocus) { - for (int i = getChildCount() - 1; i >= 0; --i) { - final DisplayContent display = getChildAt(i); - if (display.mDisplayId == currentFocus) { - continue; - } - final ActivityStack stack = getValidLaunchStackOnDisplay(display.mDisplayId, r, - null /* options */, null /* launchParams */); - if (stack != null) { - return stack; - } - } - return null; - } - boolean handleAppDied(WindowProcessController app) { boolean hasVisibleActivities = false; for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) { 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..13e4d8b038b1 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -127,6 +127,12 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { * the new stack becomes resumed after which it will be set to current focused stack. */ ActivityStack mLastFocusedStack; + /** + * All of the stacks on this display. Order matters, topmost stack is in front of all other + * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls + * changing the list should also call {@link #onStackOrderChanged()}. + */ + private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>(); TaskDisplayArea(DisplayContent displayContent, WindowManagerService service) { super(service, Type.ANY, "TaskContainers", FEATURE_TASK_CONTAINER); @@ -336,9 +342,9 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { // Since a stack could be repositioned while being one of the child, return // current index if that's the same stack we are positioning and it is always on // top. - final boolean sameStack = mDisplayContent.getStacks().get(i) == stack; + final boolean sameStack = mChildren.get(i) == stack; if ((sameStack && stack.isAlwaysOnTop()) - || (!sameStack && !mDisplayContent.getStacks().get(i).isAlwaysOnTop())) { + || (!sameStack && !mChildren.get(i).isAlwaysOnTop())) { belowAlwaysOnTopPosition = i; break; } @@ -352,7 +358,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { if (stack.isAlwaysOnTop()) { if (hasPinnedTask()) { // Always-on-top stacks go below the pinned stack. - maxPosition = mDisplayContent.getStacks().indexOf(mRootPinnedTask) - 1; + maxPosition = mChildren.indexOf(mRootPinnedTask) - 1; } // Always-on-top stacks need to be above all other stacks. minPosition = belowAlwaysOnTopPosition @@ -375,7 +381,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { targetPosition = Math.min(targetPosition, maxPosition); targetPosition = Math.max(targetPosition, minPosition); - int prevPosition = mDisplayContent.getStacks().indexOf(stack); + int prevPosition = mChildren.indexOf(stack); // The positions we calculated above (maxPosition, minPosition) do not take into // consideration the following edge cases. // 1) We need to adjust the position depending on the value "adding". @@ -471,7 +477,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { @Override int getOrientation(int candidate) { - if (mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { + if (isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { // Apps and their containers are not allowed to specify an orientation while using // root tasks...except for the home stack if it is not resizable and currently // visible (top of) its root task. @@ -637,7 +643,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { mPreferredTopFocusableStack = null; } mDisplayContent.releaseSelfIfNeeded(); - mDisplayContent.onStackOrderChanged(stack); + onStackOrderChanged(stack); } void positionStackAt(int position, ActivityStack child, boolean includingParents) { @@ -716,7 +722,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } } - mDisplayContent.onStackOrderChanged(stack); + onStackOrderChanged(stack); } ActivityStack getStack(int rootTaskId) { @@ -732,11 +738,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 +754,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,19 +763,19 @@ 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()) { + } else if (stack.getDisplayArea() != this || !stack.isRootTask()) { if (stack.getParent() == null) { addStack(stack, position); } else { - stack.reparent(mDisplayContent, onTop); + stack.reparent(this, onTop); } } // Update windowing mode if necessary, e.g. moving a pinned task to fullscreen. @@ -779,7 +785,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { return stack; } return createStack(windowingMode, activityType, onTop, null /*info*/, intent, - createdByOrganizer); + false /* createdByOrganizer */); } /** @@ -798,7 +804,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 @@ -832,7 +838,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { // Create stack on default display instead since this display can only contain 1 stack. // TODO: Kinda a hack, but better that having the decision at each call point. Hoping // this goes away once ActivityView is no longer using virtual displays. - return mRootWindowContainer.getDefaultDisplay().mTaskContainers.createStack( + return mRootWindowContainer.getDefaultTaskDisplayArea().createStack( windowingMode, activityType, onTop, info, intent, createdByOrganizer); } @@ -1551,6 +1557,16 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { return (index < wc.mChildren.size()) ? (ActivityStack) wc.mChildren.get(index) : null; } + /** Returns true if the stack in the windowing mode is visible. */ + boolean isStackVisible(int windowingMode) { + final ActivityStack stack = getTopStackInWindowingMode(windowingMode); + return stack != null && stack.isVisible(); + } + + void removeStack(ActivityStack stack) { + removeChild(stack); + } + int getDisplayId() { return mDisplayContent.getDisplayId(); } @@ -1558,4 +1574,39 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { boolean isRemoved() { return mDisplayContent.isRemoved(); } + + /** + * Adds a listener to be notified whenever the stack order in the display changes. Currently + * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the + * current animation when the system state changes. + */ + void registerStackOrderChangedListener(OnStackOrderChangedListener listener) { + if (!mStackOrderChangedCallbacks.contains(listener)) { + mStackOrderChangedCallbacks.add(listener); + } + } + + /** + * Removes a previously registered stack order change listener. + */ + void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) { + mStackOrderChangedCallbacks.remove(listener); + } + + /** + * Notifies of a stack order change + * @param stack The stack which triggered the order change + */ + void onStackOrderChanged(ActivityStack stack) { + for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) { + mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack); + } + } + + /** + * Callback for when the order of the stacks in the display changes. + */ + interface OnStackOrderChangedListener { + void onStackOrderChanged(ActivityStack stack); + } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 872f2543edb8..9ffd8d244c75 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.getDefaultTaskDisplayArea().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..b9b6c0858031 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); } } @@ -510,7 +510,7 @@ class WallpaperController { private void findWallpaperTarget() { mFindResults.reset(); - if (mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM)) { + if (mDisplayContent.getDefaultTaskDisplayArea().isStackVisible(WINDOWING_MODE_FREEFORM)) { // In freeform mode we set the wallpaper as its own target, so we don't need an // additional window to make it visible. mFindResults.setUseTopWallpaperAsTarget(true); 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..687af64e9d4f 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); @@ -7321,8 +7318,9 @@ public class WindowManagerService extends IWindowManager.Stub @Override public boolean isStackVisibleLw(int windowingMode) { - final DisplayContent dc = getDefaultDisplayContentLocked(); - return dc.isStackVisible(windowingMode); + // TODO(multi-display-area): Support multiple task display areas & displays + final TaskDisplayArea tc = mRoot.getDefaultTaskDisplayArea(); + return tc.isStackVisible(windowingMode); } @Override diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a332b6966291..3e2e9be24c4f 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -234,7 +234,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (task.getParent() != newParent) { if (newParent == null) { // Re-parent task to display as a root task. - dc.moveStackToDisplay(as, hop.getToTop()); + as.reparent(dc.getDefaultTaskDisplayArea(), hop.getToTop()); } else if (newParent.inMultiWindowMode() && !task.isResizeable() && task.isLeafTask()) { Slog.w(TAG, "Can't support task that doesn't support multi-window mode in" diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 7dcf37557692..8e7585ae4bfa 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 @@ -1518,7 +1516,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Some system windows (e.g. "Power off" dialog) don't have a task, but we would still // associate them with some stack to enable dimming. final DisplayContent dc = getDisplayContent(); - return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null ? dc.getRootHomeTask() : null; + return mAttrs.type >= FIRST_SYSTEM_WINDOW + && dc != null ? dc.getDefaultTaskDisplayArea().getRootHomeTask() : null; } /** diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 563710b9e41b..d4470f8334ea 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1066,14 +1066,15 @@ class WindowStateAnimator { if (!w.mSeamlesslyRotated) { + // Wallpaper is already updated above when calling setWallpaperPositionAndScale so + // we only need to consider the non-wallpaper case here. if (!mIsWallpaper) { applyCrop(clipRect, recoveringMemory); - mSurfaceController.setMatrixInTransaction(mDsDx * w.mHScale * mExtraHScale, + mSurfaceController.setMatrixInTransaction( + mDsDx * w.mHScale * mExtraHScale, mDtDx * w.mVScale * mExtraVScale, mDtDy * w.mHScale * mExtraHScale, mDsDy * w.mVScale * mExtraVScale, recoveringMemory); - } else { - setWallpaperPositionAndScale(mXOffset, mYOffset, mWallpaperScale, recoveringMemory); } } @@ -1152,13 +1153,20 @@ class WindowStateAnimator { mSurfaceController, mShownAlpha, mDsDx, w.mHScale, mDtDx, w.mVScale, mDtDy, w.mHScale, mDsDy, w.mVScale, w); - boolean prepared = - mSurfaceController.prepareToShowInTransaction(mShownAlpha, + boolean prepared = true; + + if (mIsWallpaper) { + setWallpaperPositionAndScale( + mXOffset, mYOffset, mWallpaperScale, recoveringMemory); + } else { + prepared = + mSurfaceController.prepareToShowInTransaction(mShownAlpha, mDsDx * w.mHScale * mExtraHScale, mDtDx * w.mVScale * mExtraVScale, mDtDy * w.mHScale * mExtraHScale, mDsDy * w.mVScale * mExtraVScale, recoveringMemory); + } if (prepared && mDrawState == HAS_DRAWN) { if (mLastHidden) { @@ -1381,7 +1389,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 +1444,11 @@ class WindowStateAnimator { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); mAnimationIsEntrance = isEntrance; } - } else { + } else if (!isImeWindow) { mWin.cancelAnimation(); } - if (!isEntrance && mWin.mAttrs.type == TYPE_INPUT_METHOD) { + if (!isEntrance && isImeWindow) { mWin.getDisplayContent().adjustForImeIfNeeded(); } diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index d7c97b922d2d..f7ea953e5a01 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -127,6 +127,7 @@ class WindowSurfaceController { mBLASTSurfaceControl = win.makeSurface() .setParent(mSurfaceControl) .setName("BLAST Adapter Layer") + .setHidden(false) .setBLASTLayer() .build(); } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 3c2b6ec9711d..7457a1d05335 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -397,7 +397,8 @@ class WindowToken extends WindowContainer<WindowState> { void assignLayer(SurfaceControl.Transaction t, int layer) { if (windowType == TYPE_DOCK_DIVIDER) { // See {@link DisplayContent#mSplitScreenDividerAnchor} - super.assignRelativeLayer(t, mDisplayContent.getSplitScreenDividerAnchor(), 1); + super.assignRelativeLayer(t, + mDisplayContent.getDefaultTaskDisplayArea().getSplitScreenDividerAnchor(), 1); } else if (mRoundedCornerOverlay) { super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1); } else { 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/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 5e3c337da11e..7349cf673cc4 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -35,6 +35,7 @@ #include <uuid/uuid.h> #include <zlib.h> +#include <charconv> #include <ctime> #include <filesystem> #include <iterator> @@ -275,12 +276,15 @@ void IncrementalService::onDump(int fd) { const IncFsMount& mnt = *ifs.get(); dprintf(fd, "\t[%d]:\n", id); dprintf(fd, "\t\tmountId: %d\n", mnt.mountId); + dprintf(fd, "\t\troot: %s\n", mnt.root.c_str()); dprintf(fd, "\t\tnextStorageDirNo: %d\n", mnt.nextStorageDirNo.load()); dprintf(fd, "\t\tdataLoaderStatus: %d\n", mnt.dataLoaderStatus.load()); dprintf(fd, "\t\tconnectionLostTime: %s\n", toString(mnt.connectionLostTime)); - if (mnt.savedDataLoaderParams) { + dprintf(fd, "\t\tsavedDataLoaderParams:\n"); + if (!mnt.savedDataLoaderParams) { + dprintf(fd, "\t\t\tnone\n"); + } else { const auto& params = mnt.savedDataLoaderParams.value(); - dprintf(fd, "\t\tsavedDataLoaderParams:\n"); dprintf(fd, "\t\t\ttype: %s\n", toString(params.type).c_str()); dprintf(fd, "\t\t\tpackageName: %s\n", params.packageName.c_str()); dprintf(fd, "\t\t\tclassName: %s\n", params.className.c_str()); @@ -987,13 +991,13 @@ void IncrementalService::mountExistingImages() { continue; } const auto root = path::join(mIncrementalDir, name); - if (!mountExistingImage(root, name)) { + if (!mountExistingImage(root)) { IncFsMount::cleanupFilesystem(path); } } } -bool IncrementalService::mountExistingImage(std::string_view root, std::string_view key) { +bool IncrementalService::mountExistingImage(std::string_view root) { auto mountTarget = path::join(root, constants().mount); const auto backing = path::join(root, constants().backing); @@ -1044,16 +1048,24 @@ bool IncrementalService::mountExistingImage(std::string_view root, std::string_v } auto name = std::string_view(e->d_name); if (name.starts_with(constants().storagePrefix)) { - auto md = parseFromIncfs<metadata::Storage>(mIncFs.get(), ifs->control, - path::join(mountTarget, name)); - auto [_, inserted] = mMounts.try_emplace(md.id(), ifs); + int storageId; + const auto res = std::from_chars(name.data() + constants().storagePrefix.size() + 1, + name.data() + name.size(), storageId); + if (res.ec != std::errc{} || *res.ptr != '_') { + LOG(WARNING) << "Ignoring storage with invalid name '" << name << "' for mount " + << root; + continue; + } + auto [_, inserted] = mMounts.try_emplace(storageId, ifs); if (!inserted) { - LOG(WARNING) << "Ignoring storage with duplicate id " << md.id() + LOG(WARNING) << "Ignoring storage with duplicate id " << storageId << " for mount " << root; continue; } - ifs->storages.insert_or_assign(md.id(), IncFsMount::Storage{std::string(name)}); - mNextId = std::max(mNextId, md.id() + 1); + ifs->storages.insert_or_assign(storageId, + IncFsMount::Storage{ + path::join(root, constants().mount, name)}); + mNextId = std::max(mNextId, storageId + 1); } } } diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index 90d58a7adcf0..3615314c7162 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -196,7 +196,7 @@ private: using BindPathMap = std::map<std::string, IncFsMount::BindMap::iterator, path::PathLess>; void mountExistingImages(); - bool mountExistingImage(std::string_view root, std::string_view key); + bool mountExistingImage(std::string_view root); IfsMountPtr getIfs(StorageId storage) const; const IfsMountPtr& getIfsLocked(StorageId storage) const; 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/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java index 5480b6ced4e1..c225d3feb063 100644 --- a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java +++ b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java @@ -108,7 +108,8 @@ final class RemoteSystemCaptionsManagerService { } mBinding = true; - int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; + int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE + | Context.BIND_INCLUDE_CAPABILITIES; boolean willBind = mContext.bindServiceAsUser(mIntent, mServiceConnection, flags, mHandler, new UserHandle(mUserId)); if (!willBind) { 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/biometrics/BiometricServiceBaseTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceBaseTest.java new file mode 100644 index 000000000000..4fe94583c8b3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceBaseTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.hardware.biometrics.BiometricAuthenticator; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +@SmallTest +public class BiometricServiceBaseTest { + private static class TestableBiometricServiceBase extends BiometricServiceBase { + TestableBiometricServiceBase(Context context) { + super(context); + } + + @Override + protected String getTag() { + return null; + } + + @Override + protected DaemonWrapper getDaemonWrapper() { + return null; + } + + @Override + protected BiometricUtils getBiometricUtils() { + return null; + } + + @Override + protected Constants getConstants() { + return null; + } + + @Override + protected boolean hasReachedEnrollmentLimit(int userId) { + return false; + } + + @Override + protected void updateActiveGroup(int userId, String clientPackage) { + } + + @Override + protected String getLockoutResetIntent() { + return null; + } + + @Override + protected String getLockoutBroadcastPermission() { + return null; + } + + @Override + protected long getHalDeviceId() { + return 0; + } + + @Override + protected boolean hasEnrolledBiometrics(int userId) { + return false; + } + + @Override + protected String getManageBiometricPermission() { + return null; + } + + @Override + protected void checkUseBiometricPermission() { + } + + @Override + protected boolean checkAppOps(int uid, String opPackageName) { + return false; + } + + @Override + protected List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates( + int userId) { + return null; + } + + @Override + protected int statsModality() { + return 0; + } + + @Override + protected int getLockoutMode() { + return 0; + } + } + + private static final int CLIENT_COOKIE = 0xc00c1e; + + private BiometricServiceBase mBiometricServiceBase; + + @Mock + private Context mContext; + @Mock + private Resources mResources; + @Mock + private BiometricAuthenticator.Identifier mIdentifier; + @Mock + private ClientMonitor mClient; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getString(anyInt())).thenReturn(""); + when(mClient.getCookie()).thenReturn(CLIENT_COOKIE); + + mBiometricServiceBase = new TestableBiometricServiceBase(mContext); + } + + @Test + public void testHandleEnumerate_doesNotCrash_withNullClient() { + mBiometricServiceBase.handleEnumerate(mIdentifier, 0 /* remaining */); + } + + @Test + public void testStartClient_sendsErrorAndRemovesClient_onNonzeroErrorCode() { + when(mClient.start()).thenReturn(1); + + mBiometricServiceBase.startClient(mClient, false /* initiatedByClient */); + + verify(mClient).onError(anyLong(), anyInt(), anyInt()); + verify(mClient).destroy(); + } +} 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/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 702718569871..7ad39b440e14 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -34,14 +34,17 @@ import android.hardware.display.DisplayViewport; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; import android.hardware.input.InputManagerInternal; import android.os.Handler; import android.os.IBinder; import android.view.Display; import android.view.DisplayInfo; +import android.view.Surface; import android.view.SurfaceControl; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -67,6 +70,8 @@ import java.util.stream.LongStream; public class DisplayManagerServiceTest { private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1; private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10; + private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display"; + private static final String PACKAGE_NAME = "com.android.frameworks.servicestests"; private Context mContext; @@ -97,6 +102,7 @@ public class DisplayManagerServiceTest { @Mock InputManagerInternal mMockInputManagerInternal; @Mock IVirtualDisplayCallback.Stub mMockAppToken; + @Mock IVirtualDisplayCallback.Stub mMockAppToken2; @Mock WindowManagerInternal mMockWindowManagerInternal; @Mock LightsManager mMockLightsManager; @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter; @@ -135,10 +141,12 @@ public class DisplayManagerServiceTest { int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); - int displayId = bs.createVirtualDisplay(mMockAppToken /* callback */, - null /* projection */, "com.android.frameworks.servicestests", - "Test Virtual Display", width, height, dpi, null /* surface */, flags /* flags */, - uniqueId); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setUniqueId(uniqueId); + builder.setFlags(flags); + int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */, + null /* projection */, PACKAGE_NAME); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); @@ -241,10 +249,12 @@ public class DisplayManagerServiceTest { int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT; when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); - int displayId = bs.createVirtualDisplay(mMockAppToken /* callback */, - null /* projection */, "com.android.frameworks.servicestests", - "Test Virtual Display", width, height, dpi, null /* surface */, flags /* flags */, - uniqueId); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setFlags(flags); + builder.setUniqueId(uniqueId); + int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */, + null /* projection */, PACKAGE_NAME); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); @@ -409,6 +419,87 @@ public class DisplayManagerServiceTest { assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels); } + /** + * Tests that the virtual display is created with + * {@link VirtualDisplayConfig.Builder#setDisplayIdToMirror(int)} + */ + @Test + @FlakyTest(bugId = 127687569) + public void testCreateVirtualDisplay_displayIdToMirror() throws Exception { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService binderService = displayManager.new BinderService(); + + final String uniqueId = "uniqueId --- displayIdToMirrorTest"; + final int width = 600; + final int height = 800; + final int dpi = 320; + + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setUniqueId(uniqueId); + final int firstDisplayId = binderService.createVirtualDisplay(builder.build(), + mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + + // The second virtual display requests to mirror the first virtual display. + final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2"; + when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2); + final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi).setUniqueId(uniqueId2); + builder2.setUniqueId(uniqueId2); + builder2.setDisplayIdToMirror(firstDisplayId); + final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(), + mMockAppToken2 /* callback */, null /* projection */, PACKAGE_NAME); + displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); + + // flush the handler + displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */); + + // The displayId to mirror should be a default display if there is none initially. + assertEquals(displayManager.getDisplayIdToMirrorInternal(firstDisplayId), + Display.DEFAULT_DISPLAY); + assertEquals(displayManager.getDisplayIdToMirrorInternal(secondDisplayId), + firstDisplayId); + } + + /** + * Tests that the virtual display is created with + * {@link VirtualDisplayConfig.Builder#setSurface(Surface)} + */ + @Test + @FlakyTest(bugId = 127687569) + public void testCreateVirtualDisplay_setSurface() throws Exception { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService binderService = displayManager.new BinderService(); + + final String uniqueId = "uniqueId --- setSurface"; + final int width = 600; + final int height = 800; + final int dpi = 320; + final Surface surface = new Surface(); + + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setSurface(surface); + builder.setUniqueId(uniqueId); + final int displayId = binderService.createVirtualDisplay(builder.build(), + mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + + displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); + + // flush the handler + displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */); + + assertEquals(displayManager.getVirtualDisplaySurfaceInternal(mMockAppToken), surface); + } + private void registerDefaultDisplays(DisplayManagerService displayManager) { Handler handler = displayManager.getDisplayHandler(); // Would prefer to call displayManager.onStart() directly here but it performs binderService 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/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java index 05604b2b9aeb..1debd8c9ffb1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java @@ -61,19 +61,20 @@ public class ActivityDisplayTests extends ActivityTestsBase { @Test public void testLastFocusedStackIsUpdatedWhenMovingStack() { // Create a stack at bottom. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); + final TaskDisplayArea taskDisplayAreas = + mRootWindowContainer.getDefaultDisplay().getDefaultTaskDisplayArea(); final ActivityStack stack = new StackBuilder(mRootWindowContainer).setOnTop(!ON_TOP).build(); - final ActivityStack prevFocusedStack = display.getFocusedStack(); + final ActivityStack prevFocusedStack = taskDisplayAreas.getFocusedStack(); stack.moveToFront("moveStackToFront"); // After moving the stack to front, the previous focused should be the last focused. assertTrue(stack.isFocusedStackOnDisplay()); - assertEquals(prevFocusedStack, display.mTaskContainers.getLastFocusedStack()); + assertEquals(prevFocusedStack, taskDisplayAreas.getLastFocusedStack()); stack.moveToBack("moveStackToBack", null /* task */); // After moving the stack to back, the stack should be the last focused. - assertEquals(stack, display.mTaskContainers.getLastFocusedStack()); + assertEquals(stack, taskDisplayAreas.getLastFocusedStack()); } /** @@ -83,8 +84,8 @@ public class ActivityDisplayTests extends ActivityTestsBase { @Test public void testFullscreenStackCanBeFocusedWhenFocusablePinnedStackExists() { // Create a pinned stack and move to front. - final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP); + final ActivityStack pinnedStack = mRootWindowContainer.getDefaultTaskDisplayArea() + .createStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP); final Task pinnedTask = new TaskBuilder(mService.mStackSupervisor) .setStack(pinnedStack).build(); new ActivityBuilder(mService).setActivityFlags(FLAG_ALWAYS_FOCUSABLE) @@ -104,8 +105,8 @@ public class ActivityDisplayTests extends ActivityTestsBase { } /** - * Test {@link DisplayContent#mPreferredTopFocusableStack} will be cleared when the stack is - * removed or moved to back, and the focused stack will be according to z-order. + * Test {@link TaskDisplayArea#mPreferredTopFocusableStack} will be cleared when + * the stack is removed or moved to back, and the focused stack will be according to z-order. */ @Test public void testStackShouldNotBeFocusedAfterMovingToBackOrRemoving() { @@ -124,7 +125,7 @@ public class ActivityDisplayTests extends ActivityTestsBase { assertTrue(stack1.isFocusedStackOnDisplay()); // Stack2 should be focused after removing stack1. - display.removeStack(stack1); + stack1.getDisplayArea().removeStack(stack1); assertTrue(stack2.isFocusedStackOnDisplay()); } @@ -156,7 +157,7 @@ public class ActivityDisplayTests extends ActivityTestsBase { } private ActivityStack createFullscreenStackWithSimpleActivityAt(DisplayContent display) { - final ActivityStack fullscreenStack = display.createStack( + final ActivityStack fullscreenStack = display.getDefaultTaskDisplayArea().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); final Task fullscreenTask = new TaskBuilder(mService.mStackSupervisor) .setStack(fullscreenStack).build(); @@ -219,58 +220,56 @@ public class ActivityDisplayTests extends ActivityTestsBase { */ @Test public void testAlwaysOnTopStackLocation() { - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack alwaysOnTopStack = display.createStack(WINDOWING_MODE_FREEFORM, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack alwaysOnTopStack = taskDisplayArea.createStack(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) .setStack(alwaysOnTopStack).build(); alwaysOnTopStack.setAlwaysOnTop(true); - display.mTaskContainers.positionStackAtTop(alwaysOnTopStack, false /* includingParents */); + taskDisplayArea.positionStackAtTop(alwaysOnTopStack, false /* includingParents */); assertTrue(alwaysOnTopStack.isAlwaysOnTop()); // Ensure always on top state is synced to the children of the stack. assertTrue(alwaysOnTopStack.getTopNonFinishingActivity().isAlwaysOnTop()); - assertEquals(alwaysOnTopStack, display.getTopStack()); + assertEquals(alwaysOnTopStack, taskDisplayArea.getTopStack()); - final ActivityStack pinnedStack = display.createStack( + final ActivityStack pinnedStack = taskDisplayArea.createStack( WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - assertEquals(pinnedStack, display.getRootPinnedTask()); - assertEquals(pinnedStack, display.getTopStack()); + assertEquals(pinnedStack, taskDisplayArea.getRootPinnedTask()); + assertEquals(pinnedStack, taskDisplayArea.getTopStack()); - final ActivityStack anotherAlwaysOnTopStack = display.createStack( + final ActivityStack anotherAlwaysOnTopStack = taskDisplayArea.createStack( WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); anotherAlwaysOnTopStack.setAlwaysOnTop(true); - display.mTaskContainers.positionStackAtTop(anotherAlwaysOnTopStack, - false /* includingParents */); + taskDisplayArea.positionStackAtTop(anotherAlwaysOnTopStack, false /* includingParents */); assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop()); - int topPosition = display.getStackCount() - 1; + int topPosition = taskDisplayArea.getStackCount() - 1; // Ensure the new alwaysOnTop stack is put below the pinned stack, but on top of the // existing alwaysOnTop stack. - assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 1)); + assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 1)); - final ActivityStack nonAlwaysOnTopStack = display.createStack( + final ActivityStack nonAlwaysOnTopStack = taskDisplayArea.createStack( WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); - assertEquals(display, nonAlwaysOnTopStack.getDisplay()); - topPosition = display.getStackCount() - 1; + assertEquals(taskDisplayArea, nonAlwaysOnTopStack.getDisplayArea()); + topPosition = taskDisplayArea.getStackCount() - 1; // Ensure the non-alwaysOnTop stack is put below the three alwaysOnTop stacks, but above the // existing other non-alwaysOnTop stacks. - assertEquals(nonAlwaysOnTopStack, display.getStackAt(topPosition - 3)); + assertEquals(nonAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 3)); anotherAlwaysOnTopStack.setAlwaysOnTop(false); - display.mTaskContainers.positionStackAtTop(anotherAlwaysOnTopStack, - false /* includingParents */); + taskDisplayArea.positionStackAtTop(anotherAlwaysOnTopStack, false /* includingParents */); assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop()); // Ensure, when always on top is turned off for a stack, the stack is put just below all // other always on top stacks. - assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 2)); + assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 2)); anotherAlwaysOnTopStack.setAlwaysOnTop(true); // Ensure always on top state changes properly when windowing mode changes. anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop()); - assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 2)); + assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 2)); anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FREEFORM); assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop()); - assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 1)); + assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 1)); } @Test @@ -286,14 +285,14 @@ public class ActivityDisplayTests extends ActivityTestsBase { } private void removeStackTests(Runnable runnable) { - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack stack1 = display.createStack(WINDOWING_MODE_FULLSCREEN, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack stack1 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); - final ActivityStack stack2 = display.createStack(WINDOWING_MODE_FULLSCREEN, + final ActivityStack stack2 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); - final ActivityStack stack3 = display.createStack(WINDOWING_MODE_FULLSCREEN, + final ActivityStack stack3 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); - final ActivityStack stack4 = display.createStack(WINDOWING_MODE_FULLSCREEN, + final ActivityStack stack4 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); final Task task1 = new TaskBuilder(mService.mStackSupervisor).setStack(stack1).build(); final Task task2 = new TaskBuilder(mService.mStackSupervisor).setStack(stack2).build(); @@ -302,13 +301,13 @@ public class ActivityDisplayTests extends ActivityTestsBase { // Reordering stacks while removing stacks. doAnswer(invocation -> { - display.mTaskContainers.positionStackAtTop(stack3, false); + taskDisplayArea.positionStackAtTop(stack3, false); return true; }).when(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any()); // Removing stacks from the display while removing stacks. doAnswer(invocation -> { - display.removeStack(stack2); + taskDisplayArea.removeStack(stack2); return true; }).when(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 747ae949c97e..08f6409cb902 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -82,6 +82,7 @@ import android.os.PersistableBundle; import android.platform.test.annotations.Presubmit; import android.util.MergedConfiguration; import android.util.MutableBoolean; +import android.view.DisplayInfo; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner.Stub; import android.view.RemoteAnimationAdapter; @@ -399,6 +400,16 @@ public class ActivityRecordTests extends ActivityTestsBase { mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); final Rect stableRect = new Rect(); mStack.getDisplay().mDisplayContent.getStableRect(stableRect); + + // Carve out non-decor insets from stableRect + final Rect insets = new Rect(); + final DisplayInfo displayInfo = mStack.getDisplay().getDisplayInfo(); + final DisplayPolicy policy = mStack.getDisplay().getDisplayPolicy(); + policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth, + displayInfo.logicalHeight, displayInfo.displayCutout, insets); + policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation); + Task.intersectWithInsetsIfFits(stableRect, stableRect, insets); + final boolean isScreenPortrait = stableRect.width() <= stableRect.height(); final Rect bounds = new Rect(stableRect); if (isScreenPortrait) { @@ -427,7 +438,17 @@ public class ActivityRecordTests extends ActivityTestsBase { public void ignoreRequestedOrientationInSplitWindows() { mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); final Rect stableRect = new Rect(); - mStack.getDisplay().mDisplayContent.getStableRect(stableRect); + mStack.getDisplay().getStableRect(stableRect); + + // Carve out non-decor insets from stableRect + final Rect insets = new Rect(); + final DisplayInfo displayInfo = mStack.getDisplay().getDisplayInfo(); + final DisplayPolicy policy = mStack.getDisplay().getDisplayPolicy(); + policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth, + displayInfo.logicalHeight, displayInfo.displayCutout, insets); + policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation); + Task.intersectWithInsetsIfFits(stableRect, stableRect, insets); + final boolean isScreenPortrait = stableRect.width() <= stableRect.height(); final Rect bounds = new Rect(stableRect); if (isScreenPortrait) { @@ -1379,8 +1400,8 @@ public class ActivityRecordTests extends ActivityTestsBase { display = new TestDisplayContent.Builder(mService, 2000, 1000).setDensityDpi(300) .setPosition(DisplayContent.POSITION_TOP).build(); } - final ActivityStack stack = display.createStack(WINDOWING_MODE_UNDEFINED, - ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack stack = display.getDefaultTaskDisplayArea() + .createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); final Task task = new TaskBuilder(mSupervisor).setStack(stack).build(); return new ActivityBuilder(mService).setTask(task).setUseProcess(process).build(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java index e8c0362c9f32..22d7fcbb4162 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java @@ -60,7 +60,7 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { @Before public void setUp() throws Exception { - mFullscreenStack = mRootWindowContainer.getDefaultDisplay().createStack( + mFullscreenStack = mRootWindowContainer.getDefaultTaskDisplayArea().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index af5afff4bed1..3d15401cdfb9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -82,15 +82,15 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(WindowTestRunner.class) public class ActivityStackTests extends ActivityTestsBase { - private DisplayContent mDefaultDisplay; + private TaskDisplayArea mDefaultTaskDisplayArea; private ActivityStack mStack; private Task mTask; @Before public void setUp() throws Exception { - mDefaultDisplay = mRootWindowContainer.getDefaultDisplay(); - mStack = mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, - true /* onTop */); + mDefaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + mStack = mDefaultTaskDisplayArea.createStack(WINDOWING_MODE_UNDEFINED, + ACTIVITY_TYPE_STANDARD, true /* onTop */); spyOn(mStack); mTask = new TaskBuilder(mSupervisor).setStack(mStack).build(); } @@ -112,7 +112,7 @@ public class ActivityStackTests extends ActivityTestsBase { r.setState(RESUMED, "testResumedActivityFromTaskReparenting"); assertEquals(r, mStack.getResumedActivity()); - final ActivityStack destStack = mRootWindowContainer.getDefaultDisplay().createStack( + final ActivityStack destStack = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); mTask.reparent(destStack, true /* toTop */, Task.REPARENT_KEEP_STACK_AT_FRONT, @@ -130,7 +130,7 @@ public class ActivityStackTests extends ActivityTestsBase { r.setState(RESUMED, "testResumedActivityFromActivityReparenting"); assertEquals(r, mStack.getResumedActivity()); - final ActivityStack destStack = mRootWindowContainer.getDefaultDisplay().createStack( + final ActivityStack destStack = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); mTask.reparent(destStack, true /*toTop*/, REPARENT_MOVE_STACK_TO_FRONT, false, false, "testResumedActivityFromActivityReparenting"); @@ -143,7 +143,7 @@ public class ActivityStackTests extends ActivityTestsBase { public void testPrimarySplitScreenRestoresWhenMovedToBack() { // Create primary splitscreen stack. This will create secondary stacks and places the // existing fullscreen stack on the bottom. - final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Assert windowing mode. @@ -154,7 +154,7 @@ public class ActivityStackTests extends ActivityTestsBase { null /* task */); // Assert that stack is at the bottom. - assertEquals(0, mDefaultDisplay.getIndexOf(primarySplitScreen)); + assertEquals(0, mDefaultTaskDisplayArea.getIndexOf(primarySplitScreen)); // Ensure no longer in splitscreen. assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); @@ -167,7 +167,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testPrimarySplitScreenRestoresPreviousWhenMovedToBack() { // This time, start with a fullscreen activitystack - final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); primarySplitScreen.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); @@ -180,7 +180,7 @@ public class ActivityStackTests extends ActivityTestsBase { null /* task */); // Assert that stack is at the bottom. - assertEquals(0, mDefaultDisplay.getIndexOf(primarySplitScreen)); + assertEquals(0, mDefaultTaskDisplayArea.getIndexOf(primarySplitScreen)); // Ensure that the override mode is restored to what it was (fullscreen) assertEquals(WINDOWING_MODE_FULLSCREEN, @@ -189,14 +189,14 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testStackInheritsDisplayWindowingMode() { - final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); assertEquals(WINDOWING_MODE_UNDEFINED, primarySplitScreen.getRequestedOverrideWindowingMode()); - mDefaultDisplay.setWindowingMode(WINDOWING_MODE_FREEFORM); + mDefaultTaskDisplayArea.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(WINDOWING_MODE_FREEFORM, primarySplitScreen.getWindowingMode()); assertEquals(WINDOWING_MODE_UNDEFINED, primarySplitScreen.getRequestedOverrideWindowingMode()); @@ -204,7 +204,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testStackOverridesDisplayWindowingMode() { - final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); @@ -216,7 +216,7 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getRequestedOverrideWindowingMode()); - mDefaultDisplay.setWindowingMode(WINDOWING_MODE_FREEFORM); + mDefaultTaskDisplayArea.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); } @@ -283,10 +283,11 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testMoveStackToBackIncludingParent() { - final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP); - final ActivityStack stack1 = createStackForShouldBeVisibleTest(display, + final TaskDisplayArea taskDisplayArea = addNewDisplayContentAt(DisplayContent.POSITION_TOP) + .getDefaultTaskDisplayArea(); + final ActivityStack stack1 = createStackForShouldBeVisibleTest(taskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack stack2 = createStackForShouldBeVisibleTest(display, + final ActivityStack stack2 = createStackForShouldBeVisibleTest(taskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Do not move display to back because there is still another stack. @@ -294,16 +295,16 @@ public class ActivityStackTests extends ActivityTestsBase { verify(stack2).positionChildAtBottom(any(), eq(false) /* includingParents */); // Also move display to back because there is only one stack left. - display.removeStack(stack1); + taskDisplayArea.removeStack(stack1); stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.getTopMostTask()); verify(stack2).positionChildAtBottom(any(), eq(true) /* includingParents */); } @Test public void testShouldBeVisible_Fullscreen() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Add an activity to the pinned stack so it isn't considered empty for visibility check. final ActivityRecord pinnedActivity = new ActivityBuilder(mService) @@ -314,8 +315,9 @@ public class ActivityStackTests extends ActivityTestsBase { assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); - final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack // should be visible since it is always on-top. doReturn(false).when(fullscreenStack).isTranslucent(any()); @@ -331,15 +333,15 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testShouldBeVisible_SplitScreen() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); // Home stack should always be fullscreen for this test. doReturn(false).when(homeStack).supportsSplitScreenWindowingMode(); final ActivityStack splitScreenPrimary = - createStackForShouldBeVisibleTest(mDefaultDisplay, + createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack splitScreenSecondary = - createStackForShouldBeVisibleTest(mDefaultDisplay, + createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Home stack shouldn't be visible if both halves of split-screen are opaque. @@ -367,7 +369,7 @@ public class ActivityStackTests extends ActivityTestsBase { splitScreenSecondary.getVisibility(null /* starting */)); final ActivityStack splitScreenSecondary2 = - createStackForShouldBeVisibleTest(mDefaultDisplay, + createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // First split-screen secondary shouldn't be visible behind another opaque split-split // secondary. @@ -389,8 +391,9 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(STACK_VISIBILITY_VISIBLE, splitScreenSecondary2.getVisibility(null /* starting */)); - final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); + final ActivityStack assistantStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, + true /* onTop */); // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack. doReturn(false).when(assistantStack).isTranslucent(any()); @@ -530,7 +533,7 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack translucentStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, @@ -547,7 +550,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testShouldBeVisible_Finishing() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity(); if (topRunningHomeActivity == null) { @@ -558,7 +561,7 @@ public class ActivityStackTests extends ActivityTestsBase { } final ActivityStack translucentStack = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); doReturn(true).when(translucentStack).isTranslucent(any()); @@ -580,7 +583,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testShouldBeVisible_FullscreenBehindTranslucentInHomeStack() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); final ActivityRecord firstActivity = new ActivityBuilder(mService) @@ -601,74 +604,77 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); doReturn(false).when(fullscreenStack).isTranslucent(any()); // Ensure that we don't move the home stack if it is already behind the top fullscreen stack - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); + int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack); assertEquals(fullscreenStack, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack); - assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack)); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack); + assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack)); } @Test public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); doReturn(true).when(fullscreenStack).isTranslucent(any()); // Ensure that we don't move the home stack if it is already behind the top fullscreen stack - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); + int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack); assertEquals(fullscreenStack, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack); - assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack)); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack); + assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack)); } @Test public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); - final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); doReturn(false).when(fullscreenStack).isTranslucent(any()); // Ensure we don't move the home stack if it is already on top - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); + int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack); assertNull(getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack); - assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack)); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack); + assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack)); } @Test public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); @@ -678,22 +684,22 @@ public class ActivityStackTests extends ActivityTestsBase { // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the // pinned stack assertEquals(fullscreenStack1, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack); assertEquals(fullscreenStack2, getStackAbove(homeStack)); } @Test public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); @@ -703,21 +709,21 @@ public class ActivityStackTests extends ActivityTestsBase { // Ensure that we move the home stack behind the bottom most non-translucent fullscreen // stack assertEquals(fullscreenStack1, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack); assertEquals(fullscreenStack1, getStackAbove(homeStack)); } @Test public void testMoveHomeStackBehindStack_BehindHomeStack() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); @@ -725,50 +731,50 @@ public class ActivityStackTests extends ActivityTestsBase { doReturn(false).when(fullscreenStack2).isTranslucent(any()); // Ensure we don't move the home stack behind itself - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); - mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, homeStack); - assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack)); + int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack); + mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, homeStack); + assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack)); } @Test public void testMoveHomeStackBehindStack() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack3 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack4 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack1); + mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack1); assertEquals(fullscreenStack1, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack2); + mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack2); assertEquals(fullscreenStack2, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack4); + mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack4); assertEquals(fullscreenStack4, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack2); + mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack2); assertEquals(fullscreenStack2, getStackAbove(homeStack)); } @Test public void testSetAlwaysOnTop() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(pinnedStack, getStackAbove(homeStack)); final ActivityStack alwaysOnTopStack = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); alwaysOnTopStack.setAlwaysOnTop(true); assertTrue(alwaysOnTopStack.isAlwaysOnTop()); @@ -776,13 +782,13 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(pinnedStack, getStackAbove(alwaysOnTopStack)); final ActivityStack nonAlwaysOnTopStack = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Ensure non always on top stack is put below always on top stacks. assertEquals(alwaysOnTopStack, getStackAbove(nonAlwaysOnTopStack)); final ActivityStack alwaysOnTopStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); alwaysOnTopStack2.setAlwaysOnTop(true); assertTrue(alwaysOnTopStack2.isAlwaysOnTop()); @@ -807,13 +813,14 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testSplitScreenMoveToFront() { final ActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, - true /* onTop */); + mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, + ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, + ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack assistantStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); - final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); doReturn(false).when(splitScreenPrimary).isTranslucent(any()); doReturn(false).when(splitScreenSecondary).isTranslucent(any()); @@ -832,7 +839,7 @@ public class ActivityStackTests extends ActivityTestsBase { private ActivityStack createStandardStackForVisibilityTest(int windowingMode, boolean translucent) { - final ActivityStack stack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack stack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */); doReturn(translucent).when(stack).isTranslucent(any()); return stack; @@ -840,20 +847,20 @@ public class ActivityStackTests extends ActivityTestsBase { @SuppressWarnings("TypeParameterUnusedInFormals") private ActivityStack createStackForShouldBeVisibleTest( - DisplayContent display, int windowingMode, int activityType, boolean onTop) { + TaskDisplayArea taskDisplayArea, int windowingMode, int activityType, boolean onTop) { final ActivityStack stack; if (activityType == ACTIVITY_TYPE_HOME) { // Home stack and activity are created in ActivityTestsBase#setupActivityManagerService - stack = mDefaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); + stack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); if (onTop) { - mDefaultDisplay.mTaskContainers.positionStackAtTop(stack, + mDefaultTaskDisplayArea.positionStackAtTop(stack, false /* includingParents */); } else { - mDefaultDisplay.mTaskContainers.positionStackAtBottom(stack); + mDefaultTaskDisplayArea.positionStackAtBottom(stack); } } else { stack = new StackBuilder(mRootWindowContainer) - .setDisplay(display) + .setTaskDisplayArea(taskDisplayArea) .setWindowingMode(windowingMode) .setActivityType(activityType) .setOnTop(onTop) @@ -1005,7 +1012,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testWontFinishHomeStackImmediately() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); ActivityRecord activity = homeStack.topRunningActivity(); @@ -1025,9 +1032,11 @@ public class ActivityStackTests extends ActivityTestsBase { public void testFinishCurrentActivity() { // Create 2 activities on a new display. final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP); - final ActivityStack stack1 = createStackForShouldBeVisibleTest(display, + final ActivityStack stack1 = createStackForShouldBeVisibleTest( + display.getDefaultTaskDisplayArea(), WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack stack2 = createStackForShouldBeVisibleTest(display, + final ActivityStack stack2 = createStackForShouldBeVisibleTest( + display.getDefaultTaskDisplayArea(), WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // There is still an activity1 in stack1 so the activity2 should be added to finishing list @@ -1075,26 +1084,26 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testStackOrderChangedOnRemoveStack() { StackOrderChangedListener listener = new StackOrderChangedListener(); - mDefaultDisplay.registerStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener); try { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); } finally { - mDefaultDisplay.unregisterStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener); } assertTrue(listener.mChanged); } @Test public void testStackOrderChangedOnAddPositionStack() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); StackOrderChangedListener listener = new StackOrderChangedListener(); - mDefaultDisplay.registerStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener); try { mStack.mReparenting = true; - mDefaultDisplay.mTaskContainers.addStack(mStack, 0); + mDefaultTaskDisplayArea.addStack(mStack, 0); } finally { - mDefaultDisplay.unregisterStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener); } assertTrue(listener.mChanged); } @@ -1104,12 +1113,12 @@ public class ActivityStackTests extends ActivityTestsBase { StackOrderChangedListener listener = new StackOrderChangedListener(); try { final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - mDefaultDisplay.registerStackOrderChangedListener(listener); - mDefaultDisplay.mTaskContainers.positionStackAtBottom(fullscreenStack1); + mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.positionStackAtBottom(fullscreenStack1); } finally { - mDefaultDisplay.unregisterStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener); } assertTrue(listener.mChanged); } @@ -1189,7 +1198,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testClearUnknownAppVisibilityBehindFullscreenActivity() { final UnknownAppVisibilityController unknownAppVisibilityController = - mDefaultDisplay.mDisplayContent.mUnknownAppVisibilityController; + mDefaultTaskDisplayArea.mDisplayContent.mUnknownAppVisibilityController; final KeyguardController keyguardController = mSupervisor.getKeyguardController(); doReturn(true).when(keyguardController).isKeyguardLocked(); @@ -1254,7 +1263,7 @@ public class ActivityStackTests extends ActivityTestsBase { } private static class StackOrderChangedListener - implements DisplayContent.OnStackOrderChangedListener { + implements TaskDisplayArea.OnStackOrderChangedListener { public boolean mChanged = false; @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java index 76a761ce0e10..27782f5b3683 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java @@ -77,8 +77,8 @@ public class ActivityStartControllerTests extends ActivityTestsBase { .setCreateTask(true) .build(); final int startFlags = random.nextInt(); - final ActivityStack stack = mService.mRootWindowContainer.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack stack = mService.mRootWindowContainer.getDefaultTaskDisplayArea() + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final WindowProcessController wpc = new WindowProcessController(mService, mService.mContext.getApplicationInfo(), "name", 12345, UserHandle.getUserId(12345), mock(Object.class), diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 1d952bfcef2a..1cca207d5336 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -326,8 +326,9 @@ public class ActivityStarterTests extends ActivityTestsBase { if (mockGetLaunchStack) { // Instrument the stack and task used. - final ActivityStack stack = mRootWindowContainer.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack stack = mRootWindowContainer.getDefaultTaskDisplayArea() + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); // Direct starter to use spy stack. doReturn(stack).when(mRootWindowContainer) @@ -742,7 +743,7 @@ public class ActivityStarterTests extends ActivityTestsBase { final TestDisplayContent secondaryDisplay = new TestDisplayContent.Builder(mService, 1000, 1500) .setPosition(POSITION_BOTTOM).build(); - final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.mTaskContainers; + final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea(); final ActivityStack stack = secondaryTaskContainer.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); @@ -783,7 +784,7 @@ public class ActivityStarterTests extends ActivityTestsBase { new TestDisplayContent.Builder(mService, 1000, 1500).build(); mRootWindowContainer.positionChildAt(POSITION_TOP, secondaryDisplay, false /* includingParents */); - final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.mTaskContainers; + final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea(); final ActivityRecord singleTaskActivity = createSingleTaskActivityOn( secondaryTaskContainer.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */)); @@ -835,7 +836,7 @@ public class ActivityStarterTests extends ActivityTestsBase { // Create a secondary display at bottom. final TestDisplayContent secondaryDisplay = addNewDisplayContentAt(POSITION_BOTTOM); - final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.mTaskContainers; + final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea(); secondaryTaskContainer.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); @@ -951,7 +952,7 @@ public class ActivityStarterTests extends ActivityTestsBase { final ActivityStarter starter = prepareStarter(0 /* flags */); starter.mStartActivity = new ActivityBuilder(mService).build(); final Task task = new TaskBuilder(mService.mStackSupervisor) - .setStack(mService.mRootWindowContainer.getDefaultDisplay().createStack( + .setStack(mService.mRootWindowContainer.getDefaultTaskDisplayArea().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */)) .setUserId(10) .build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 9240b2222cd6..67d4769522b0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -372,7 +372,7 @@ class ActivityTestsBase extends SystemServiceTestsBase { Task build() { if (mStack == null && mCreateStack) { - mStack = mSupervisor.mRootWindowContainer.getDefaultDisplay().createStack( + mStack = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); spyOn(mStack); } @@ -408,6 +408,7 @@ class ActivityTestsBase extends SystemServiceTestsBase { static class StackBuilder { private final RootWindowContainer mRootWindowContainer; private DisplayContent mDisplay; + private TaskDisplayArea mTaskDisplayArea; private int mStackId = -1; private int mWindowingMode = WINDOWING_MODE_UNDEFINED; private int mActivityType = ACTIVITY_TYPE_STANDARD; @@ -419,6 +420,7 @@ class ActivityTestsBase extends SystemServiceTestsBase { StackBuilder(RootWindowContainer root) { mRootWindowContainer = root; mDisplay = mRootWindowContainer.getDefaultDisplay(); + mTaskDisplayArea = mDisplay.getDefaultTaskDisplayArea(); } StackBuilder setWindowingMode(int windowingMode) { @@ -436,8 +438,20 @@ class ActivityTestsBase extends SystemServiceTestsBase { return this; } + /** + * Set the parent {@link DisplayContent} and use the default task display area. Overrides + * the task display area, if was set before. + */ StackBuilder setDisplay(DisplayContent display) { mDisplay = display; + mTaskDisplayArea = mDisplay.getDefaultTaskDisplayArea(); + return this; + } + + /** Set the parent {@link TaskDisplayArea}. Overrides the display, if was set before. */ + StackBuilder setTaskDisplayArea(TaskDisplayArea taskDisplayArea) { + mTaskDisplayArea = taskDisplayArea; + mDisplay = mTaskDisplayArea.mDisplayContent; return this; } @@ -462,9 +476,8 @@ class ActivityTestsBase extends SystemServiceTestsBase { } ActivityStack build() { - final int stackId = mStackId >= 0 ? mStackId - : mDisplay.mTaskContainers.getNextStackId(); - final ActivityStack stack = mDisplay.mTaskContainers.createStackUnchecked( + final int stackId = mStackId >= 0 ? mStackId : mTaskDisplayArea.getNextStackId(); + final ActivityStack stack = mTaskDisplayArea.createStackUnchecked( mWindowingMode, mActivityType, stackId, mOnTop, mInfo, mIntent, false /* createdByOrganizer */); final ActivityStackSupervisor supervisor = mRootWindowContainer.mStackSupervisor; diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java index 4cb50c7a9e4d..9b7ffd69da15 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java @@ -147,7 +147,7 @@ public class AppChangeTransitionTests extends WindowTestsBase { // Reparenting to a display with different windowing mode may trigger // a change transition internally, but it should be cleaned-up once // the display change is complete. - mStack.reparent(mDisplayContent, true); + mStack.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true); assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode()); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index 8b91c7e5d5f3..8c8fd0516623 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -169,7 +169,7 @@ public class AppTransitionTests extends WindowTestsBase { assertTrue(dc1.mOpeningApps.size() > 0); // Move stack to another display. - stack1.reparent(dc2, true); + stack1.reparent(dc2.getDefaultTaskDisplayArea(), true); // Verify if token are cleared from both pending transition list in former display. assertFalse(dc1.mOpeningApps.contains(activity1)); 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..a901d1ebd890 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -281,7 +281,7 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(dc, activity.getDisplayContent()); // Move stack to first display. - mDisplayContent.moveStackToDisplay(stack, true /* onTop */); + stack.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true /* onTop */); assertEquals(mDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId()); assertEquals(mDisplayContent, stack.getDisplayContent()); assertEquals(mDisplayContent, task.getDisplayContent()); @@ -753,7 +753,7 @@ public class DisplayContentTests extends WindowTestsBase { doReturn(true).when(freeformStack).isVisible(); freeformStack.getTopChild().setBounds(100, 100, 300, 400); - assertTrue(dc.isStackVisible(WINDOWING_MODE_FREEFORM)); + assertTrue(dc.getDefaultTaskDisplayArea().isStackVisible(WINDOWING_MODE_FREEFORM)); freeformStack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE); stack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT); @@ -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. @@ -1089,8 +1096,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testGetOrCreateRootHomeTask_defaultDisplay() { - DisplayContent defaultDisplay = mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY); - TaskDisplayArea defaultTaskDisplayArea = defaultDisplay.mTaskContainers; + TaskDisplayArea defaultTaskDisplayArea = mWm.mRoot.getDefaultTaskDisplayArea(); // Remove the current home stack if it exists so a new one can be created below. ActivityStack homeTask = defaultTaskDisplayArea.getRootHomeTask(); @@ -1109,7 +1115,7 @@ public class DisplayContentTests extends WindowTestsBase { doReturn(false).when(display).isUntrustedVirtualDisplay(); // Remove the current home stack if it exists so a new one can be created below. - TaskDisplayArea taskDisplayArea = display.mTaskContainers; + TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); ActivityStack homeTask = taskDisplayArea.getRootHomeTask(); if (homeTask != null) { taskDisplayArea.removeChild(homeTask); @@ -1122,7 +1128,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() { DisplayContent display = createNewDisplay(); - TaskDisplayArea taskDisplayArea = display.mTaskContainers; + TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); doReturn(false).when(display).supportsSystemDecorations(); assertNull(taskDisplayArea.getRootHomeTask()); @@ -1132,7 +1138,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testGetOrCreateRootHomeTask_untrustedVirtualDisplay() { DisplayContent display = createNewDisplay(); - TaskDisplayArea taskDisplayArea = display.mTaskContainers; + TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); doReturn(true).when(display).isUntrustedVirtualDisplay(); assertNull(taskDisplayArea.getRootHomeTask()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index cf7411e67135..9b2a2db5d3a8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -860,6 +860,8 @@ public class DisplayRotationTests { mMockDisplayContent.isDefaultDisplay = mIsDefaultDisplay; when(mMockDisplayContent.calculateDisplayCutoutForRotation(anyInt())) .thenReturn(WmDisplayCutout.NO_CUTOUT); + when(mMockDisplayContent.getDefaultTaskDisplayArea()) + .thenReturn(mock(TaskDisplayArea.class)); mMockDisplayPolicy = mock(DisplayPolicy.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index ae467c0c811d..6a71a7dd24dd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -114,8 +114,8 @@ public class LaunchParamsPersisterTests extends ActivityTestsBase { when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId))) .thenReturn(mTestDisplay); - ActivityStack stack = mTestDisplay.createStack(TEST_WINDOWING_MODE, - ACTIVITY_TYPE_STANDARD, /* onTop */ true); + ActivityStack stack = mTestDisplay.getDefaultTaskDisplayArea() + .createStack(TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true); mTestTask = new TaskBuilder(mSupervisor).setComponent(TEST_COMPONENT).setStack(stack) .build(); mTestTask.mUserId = TEST_USER_ID; @@ -337,8 +337,8 @@ public class LaunchParamsPersisterTests extends ActivityTestsBase { public void testClearsRecordsOfTheUserOnUserCleanUp() { mTarget.saveTask(mTestTask); - ActivityStack stack = mTestDisplay.createStack(TEST_WINDOWING_MODE, - ACTIVITY_TYPE_STANDARD, /* onTop */ true); + ActivityStack stack = mTestDisplay.getDefaultTaskDisplayArea().createStack( + TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true); final Task anotherTaskOfTheSameUser = new TaskBuilder(mSupervisor) .setComponent(ALTERNATIVE_COMPONENT) .setUserId(TEST_USER_ID) @@ -349,7 +349,7 @@ public class LaunchParamsPersisterTests extends ActivityTestsBase { anotherTaskOfTheSameUser.setHasBeenVisible(true); mTarget.saveTask(anotherTaskOfTheSameUser); - stack = mTestDisplay.createStack(TEST_WINDOWING_MODE, + stack = mTestDisplay.getDefaultTaskDisplayArea().createStack(TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true); final Task anotherTaskOfDifferentUser = new TaskBuilder(mSupervisor) .setComponent(TEST_COMPONENT) diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 071386fa9cbd..d9c3ace4589d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -28,7 +28,6 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.view.Display.DEFAULT_DISPLAY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -119,7 +118,7 @@ public class RecentTasksTest extends ActivityTestsBase { public void setUp() throws Exception { mTaskPersister = new TestTaskPersister(mContext.getFilesDir()); spyOn(mTaskPersister); - mTaskContainer = mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).mTaskContainers; + mTaskContainer = mRootWindowContainer.getDefaultTaskDisplayArea(); // Set the recent tasks we should use for testing in this class. mRecentTasks = new TestRecentTasks(mService, mTaskPersister); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index da07baca3ce1..6d2b7b1e86fe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -105,7 +105,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mDefaultDisplay = mWm.mRoot.getDefaultDisplay(); mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks, DEFAULT_DISPLAY)); - mRootHomeTask = mDefaultDisplay.getRootHomeTask(); + mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask(); assertNotNull(mRootHomeTask); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 6810f6442c66..881561f5750b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -88,7 +88,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testRecentsActivityVisiblility() { - TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers; + TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); ActivityStack recentsStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */); ActivityRecord recentActivity = new ActivityBuilder(mService) @@ -116,8 +116,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testPreloadRecentsActivity() { - TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultDisplay() - .mTaskContainers; + TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); final ActivityStack homeStack = defaultTaskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); defaultTaskDisplayArea.positionStackAtTop(homeStack, false /* includingParents */); @@ -178,8 +177,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testRestartRecentsActivity() throws Exception { // Have a recents activity that is not attached to its process (ActivityRecord.app = null). - TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultDisplay() - .mTaskContainers; + TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); ActivityStack recentsStack = defaultTaskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */); ActivityRecord recentActivity = new ActivityBuilder(mService).setComponent( @@ -208,7 +206,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testSetLaunchTaskBehindOfTargetActivity() { - TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers; + TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); ActivityStack homeStack = taskDisplayArea.getRootHomeTask(); // Assume the home activity support recents. ActivityRecord targetActivity = homeStack.getTopNonFinishingActivity(); @@ -253,7 +251,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testCancelAnimationOnVisibleStackOrderChange() { - TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers; + TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); ActivityStack fullscreenStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); new ActivityBuilder(mService) @@ -298,7 +296,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testKeepAnimationOnHiddenStackOrderChange() { - TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers; + TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); ActivityStack fullscreenStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); new ActivityBuilder(mService) @@ -334,7 +332,8 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testMultipleUserHomeActivity_findUserHomeTask() { - TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers; + TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay() + .getDefaultTaskDisplayArea(); ActivityStack homeStack = taskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME); ActivityRecord otherUserHomeActivity = new ActivityBuilder(mService) diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index 836310496d0b..48d4e705ff4b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -89,7 +89,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Before public void setUp() throws Exception { - mFullscreenStack = mRootWindowContainer.getDefaultDisplay().createStack( + mFullscreenStack = mRootWindowContainer.getDefaultTaskDisplayArea().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); doNothing().when(mService).updateSleepIfNeededLocked(); } @@ -129,8 +129,8 @@ public class RootActivityContainerTests extends ActivityTestsBase { mRootWindowContainer.moveActivityToPinnedStack(firstActivity, sourceBounds, 0f /*aspectRatio*/, "initialMove"); - final DisplayContent display = mFullscreenStack.getDisplay(); - ActivityStack pinnedStack = display.getRootPinnedTask(); + final TaskDisplayArea taskDisplayArea = mFullscreenStack.getDisplayArea(); + ActivityStack pinnedStack = taskDisplayArea.getRootPinnedTask(); // Ensure a task has moved over. ensureStackPlacement(pinnedStack, firstActivity); ensureStackPlacement(mFullscreenStack, secondActivity); @@ -140,8 +140,9 @@ public class RootActivityContainerTests extends ActivityTestsBase { 0f /*aspectRatio*/, "secondMove"); // Need to get stacks again as a new instance might have been created. - pinnedStack = display.getRootPinnedTask(); - mFullscreenStack = display.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + pinnedStack = taskDisplayArea.getRootPinnedTask(); + mFullscreenStack = taskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); // Ensure stacks have swapped tasks. ensureStackPlacement(pinnedStack, secondActivity); ensureStackPlacement(mFullscreenStack, firstActivity); @@ -215,6 +216,8 @@ public class RootActivityContainerTests extends ActivityTestsBase { doReturn(isFocusedStack).when(stack).isFocusedStackOnDisplay(); doReturn(isFocusedStack ? stack : null).when(display).getFocusedStack(); + TaskDisplayArea defaultTaskDisplayArea = display.getDefaultTaskDisplayArea(); + doReturn(isFocusedStack ? stack : null).when(defaultTaskDisplayArea).getFocusedStack(); mRootWindowContainer.applySleepTokens(true); verify(stack, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked(); verify(stack, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked( @@ -226,26 +229,29 @@ public class RootActivityContainerTests extends ActivityTestsBase { */ @Test public void testRemovingStackOnAppCrash() { - final DisplayContent defaultDisplay = mRootWindowContainer.getDefaultDisplay(); - final int originalStackCount = defaultDisplay.getStackCount(); - final ActivityStack stack = mRootWindowContainer.getDefaultDisplay().createStack( + final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer + .getDefaultTaskDisplayArea(); + final int originalStackCount = defaultTaskDisplayArea.getStackCount(); + final ActivityStack stack = defaultTaskDisplayArea.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) .setStack(stack).build(); - assertEquals(originalStackCount + 1, defaultDisplay.getStackCount()); + assertEquals(originalStackCount + 1, defaultTaskDisplayArea.getStackCount()); // Let's pretend that the app has crashed. firstActivity.app.setThread(null); mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test"); // Verify that the stack was removed. - assertEquals(originalStackCount, defaultDisplay.getStackCount()); + assertEquals(originalStackCount, defaultTaskDisplayArea.getStackCount()); } @Test public void testFocusability() { - final ActivityStack stack = mRootWindowContainer.getDefaultDisplay().createStack( + final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer + .getDefaultTaskDisplayArea(); + final ActivityStack stack = defaultTaskDisplayArea.createStack( WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) .setStack(stack).build(); @@ -259,7 +265,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { assertFalse(stack.isTopActivityFocusable()); assertFalse(activity.isFocusable()); - final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack( + final ActivityStack pinnedStack = defaultTaskDisplayArea.createStack( WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityRecord pinnedActivity = new ActivityBuilder(mService).setCreateTask(true) .setStack(pinnedStack).build(); @@ -288,7 +294,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testSplitScreenPrimaryChosenWhenTopActivityLaunchedToSecondary() { // Create primary split-screen stack with a task and an activity. - final ActivityStack primaryStack = mRootWindowContainer.getDefaultDisplay() + final ActivityStack primaryStack = mRootWindowContainer.getDefaultTaskDisplayArea() .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); final Task task = new TaskBuilder(mSupervisor).setStack(primaryStack).build(); @@ -311,7 +317,6 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testFindTaskToMoveToFrontWhenRecentsOnTop() { // Create stack/task on default display. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); final ActivityStack targetStack = new StackBuilder(mRootWindowContainer) .setOnTop(false) .build(); @@ -325,7 +330,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason, false); - final TaskDisplayArea taskDisplayArea = display.mTaskContainers; + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); verify(taskDisplayArea).moveHomeStackToFront(contains(reason)); } @@ -336,8 +341,8 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testFindTaskToMoveToFrontWhenRecentsOnOtherDisplay() { // Create stack/task on default display. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack targetStack = display.createStack(WINDOWING_MODE_FULLSCREEN, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack targetStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); final Task targetTask = new TaskBuilder(mSupervisor).setStack(targetStack).build(); @@ -353,7 +358,6 @@ public class RootActivityContainerTests extends ActivityTestsBase { mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason, false); - final TaskDisplayArea taskDisplayArea = display.mTaskContainers; verify(taskDisplayArea, never()).moveHomeStackToFront(contains(reason)); } @@ -364,12 +368,12 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testResumeActivityWhenNonTopmostStackIsTopFocused() { // Create a stack at bottom. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */)); final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build(); final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build(); - display.mTaskContainers.positionStackAtBottom(targetStack); + taskDisplayArea.positionStackAtBottom(targetStack); // Assume the stack is not at the topmost position (e.g. behind always-on-top stacks) but it // is the current top focused stack. @@ -392,10 +396,9 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testResumeFocusedStacksStartsHomeActivity_NoActivities() { mFullscreenStack.removeIfPossible(); - mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).getRootHomeTask() - .removeIfPossible(); - mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY) - .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + taskDisplayArea.getRootHomeTask().removeIfPossible(); + taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), anyInt()); @@ -415,16 +418,15 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen() { mFullscreenStack.removeIfPossible(); - mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).getRootHomeTask() - .removeIfPossible(); - mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY) - .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + taskDisplayArea.getRootHomeTask().removeIfPossible(); + taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); // Create an activity on secondary display. final TestDisplayContent secondDisplay = addNewDisplayContentAt( DisplayContent.POSITION_TOP); - final ActivityStack stack = secondDisplay.createStack(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack stack = secondDisplay.getDefaultTaskDisplayArea() + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final Task task = new TaskBuilder(mSupervisor).setStack(stack).build(); new ActivityBuilder(mService).setTask(task).build(); @@ -446,8 +448,8 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testResumeActivityLingeringTransition() { // Create a stack at top. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */)); final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build(); final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build(); @@ -466,13 +468,13 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testResumeActivityLingeringTransition_notExecuted() { // Create a stack at bottom. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */)); final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build(); final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build(); activity.setState(ActivityState.RESUMED, "test"); - display.mTaskContainers.positionStackAtBottom(targetStack); + taskDisplayArea.positionStackAtBottom(targetStack); // Assume the stack is at the topmost position assertFalse(targetStack.isTopStackInDisplayArea()); @@ -809,20 +811,20 @@ public class RootActivityContainerTests extends ActivityTestsBase { public void testSwitchUser_missingHomeRootTask() { doReturn(mFullscreenStack).when(mRootWindowContainer).getTopDisplayFocusedStack(); - DisplayContent defaultDisplay = mRootWindowContainer.getDefaultDisplay(); - ActivityStack homeStack = defaultDisplay.getRootHomeTask(); + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + ActivityStack homeStack = taskDisplayArea.getRootHomeTask(); if (homeStack != null) { homeStack.removeImmediately(); } - assertNull(defaultDisplay.getRootHomeTask()); + assertNull(taskDisplayArea.getRootHomeTask()); int currentUser = mRootWindowContainer.mCurrentUser; int otherUser = currentUser + 1; mRootWindowContainer.switchUser(otherUser, null); - assertNotNull(defaultDisplay.getRootHomeTask()); - assertEquals(defaultDisplay.getTopStack(), defaultDisplay.getRootHomeTask()); + assertNotNull(taskDisplayArea.getRootHomeTask()); + assertEquals(taskDisplayArea.getTopStack(), taskDisplayArea.getRootHomeTask()); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java index d6a67abc9e76..3d3a0f148db5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java @@ -72,7 +72,8 @@ public class RunningTasksTest extends ActivityTestsBase { final int numTasks = 10; int activeTime = 0; for (int i = 0; i < numTasks; i++) { - createTask(display.getStackAt(i % numStacks), ".Task" + i, i, activeTime++, null); + createTask(display.getDefaultTaskDisplayArea().getStackAt(i % numStacks), + ".Task" + i, i, activeTime++, null); } // Ensure that the latest tasks were returned in order of decreasing last active time, diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 893a14541c48..673469474709 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -189,7 +189,7 @@ public class SizeCompatTests extends ActivityTestsBase { final int originalDpi = mActivity.getConfiguration().densityDpi; // Move the non-resizable activity to the new display. - mStack.reparent(newDisplay.mDisplayContent, true /* onTop */); + mStack.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */); assertEquals(originalBounds.width(), mActivity.getBounds().width()); assertEquals(originalBounds.height(), mActivity.getBounds().height()); @@ -257,7 +257,7 @@ public class SizeCompatTests extends ActivityTestsBase { .setCanRotate(false).setNotch(notchHeight).build(); // Move the non-resizable activity to the new display. - mStack.reparent(newDisplay, true /* onTop */); + mStack.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */); // The configuration bounds should keep the same. assertEquals(origWidth, configBounds.width()); assertEquals(origHeight, configBounds.height()); diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index af76e7fc0b76..af3ec38631ae 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -316,7 +316,7 @@ public class SystemServicesTestRule implements TestRule { // that the default display is in fullscreen mode. display.setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN); spyOn(display); - final TaskDisplayArea taskDisplayArea = display.mTaskContainers; + final TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); spyOn(taskDisplayArea); final ActivityStack homeStack = taskDisplayArea.getStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index a3446d16d9f3..1a38ff283477 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -1312,14 +1312,14 @@ public class TaskLaunchParamsModifierTests extends ActivityTestsBase { } private ActivityRecord createSourceActivity(TestDisplayContent display) { - final ActivityStack stack = display.createStack(display.getWindowingMode(), - ACTIVITY_TYPE_STANDARD, true); + final ActivityStack stack = display.getDefaultTaskDisplayArea() + .createStack(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true); return new ActivityBuilder(mService).setStack(stack).setCreateTask(true).build(); } private void addFreeformTaskTo(TestDisplayContent display, Rect bounds) { - final ActivityStack stack = display.createStack(display.getWindowingMode(), - ACTIVITY_TYPE_STANDARD, true); + final ActivityStack stack = display.getDefaultTaskDisplayArea() + .createStack(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true); stack.setWindowingMode(WINDOWING_MODE_FREEFORM); final Task task = new TaskBuilder(mSupervisor).setStack(stack).build(); // Just work around the unnecessary adjustments for bounds. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index f76809b06510..50584c61cf92 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -203,9 +203,9 @@ public class TaskRecordTests extends ActivityTestsBase { @Test public void testFitWithinBounds() { final Rect parentBounds = new Rect(10, 10, 200, 200); - DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay(); - ActivityStack stack = display.createStack(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, - true /* onTop */); + TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea(); + ActivityStack stack = taskDisplayArea.createStack(WINDOWING_MODE_FREEFORM, + ACTIVITY_TYPE_STANDARD, true /* onTop */); Task task = new TaskBuilder(mSupervisor).setStack(stack).build(); final Configuration parentConfig = stack.getConfiguration(); parentConfig.windowConfiguration.setBounds(parentBounds); @@ -438,9 +438,9 @@ public class TaskRecordTests extends ActivityTestsBase { @Test public void testInsetDisregardedWhenFreeformOverlapsNavBar() { - DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay(); - ActivityStack stack = display.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); + TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea(); + ActivityStack stack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, true /* onTop */); DisplayInfo displayInfo = new DisplayInfo(); mService.mContext.getDisplay().getDisplayInfo(displayInfo); final int displayHeight = displayInfo.logicalHeight; @@ -959,8 +959,8 @@ public class TaskRecordTests extends ActivityTestsBase { private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds, Rect expectedConfigBounds) { - DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay(); - ActivityStack stack = display.createStack(windowingMode, ACTIVITY_TYPE_STANDARD, + TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea(); + ActivityStack stack = taskDisplayArea.createStack(windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */); Task task = new TaskBuilder(mSupervisor).setStack(stack).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java index 6387a3b7c474..d48e82723295 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java @@ -171,7 +171,7 @@ public class TaskStackTests extends WindowTestsBase { // Reparent clearInvocations(task1); // reset the number of onDisplayChanged for task. - stack1.reparent(dc, true /* onTop */); + stack1.reparent(dc.getDefaultTaskDisplayArea(), true /* onTop */); assertEquals(dc, stack1.getDisplayContent()); final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1); final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2); diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 900f014a0218..a4f1487dde1e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -254,7 +254,7 @@ public class WallpaperControllerTests extends WindowTestsBase { private WindowState createWallpaperTargetWindow(DisplayContent dc) { final ActivityRecord homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) - .setStack(dc.getRootHomeTask()) + .setStack(dc.getDefaultTaskDisplayArea().getRootHomeTask()) .setCreateTask(true) .build(); homeActivity.setVisibility(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 27ea37dfeb19..118c2e4db208 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -780,8 +780,8 @@ public class WindowContainerTests extends WindowTestsBase { WindowTestUtils.createActivityRecordInTask(mDisplayContent, task); final DisplayContent newDc = createNewDisplay(); - mDisplayContent.removeStack(stack); - newDc.mTaskContainers.addChild(stack, POSITION_TOP); + stack.getDisplayArea().removeStack(stack); + newDc.getDefaultTaskDisplayArea().addChild(stack, POSITION_TOP); verify(stack).onDisplayChanged(newDc); verify(task).onDisplayChanged(newDc); 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/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java index c37735c729a2..13a8273d64d4 100644 --- a/telephony/java/android/telephony/CellIdentityLte.java +++ b/telephony/java/android/telephony/CellIdentityLte.java @@ -153,8 +153,10 @@ public final class CellIdentityLte extends CellIdentity { cid.bands.stream().mapToInt(Integer::intValue).toArray(), cid.base.bandwidth, cid.base.base.mcc, cid.base.base.mnc, cid.base.operatorNames.alphaLong, cid.base.operatorNames.alphaShort, cid.additionalPlmns, - cid.optionalCsgInfo.csgInfo() != null - ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null); + cid.optionalCsgInfo.getDiscriminator() + == android.hardware.radio.V1_5.OptionalCsgInfo.hidl_discriminator.csgInfo + ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) + : null); } private CellIdentityLte(@NonNull CellIdentityLte cid) { diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java index 3f95596a076e..6dffe922ffd1 100644 --- a/telephony/java/android/telephony/CellIdentityTdscdma.java +++ b/telephony/java/android/telephony/CellIdentityTdscdma.java @@ -128,8 +128,11 @@ public final class CellIdentityTdscdma extends CellIdentity { this(cid.base.base.mcc, cid.base.base.mnc, cid.base.base.lac, cid.base.base.cid, cid.base.base.cpid, cid.base.uarfcn, cid.base.operatorNames.alphaLong, cid.base.operatorNames.alphaShort, - cid.additionalPlmns, cid.optionalCsgInfo.csgInfo() != null - ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null); + cid.additionalPlmns, + cid.optionalCsgInfo.getDiscriminator() + == android.hardware.radio.V1_5.OptionalCsgInfo.hidl_discriminator.csgInfo + ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) + : null); } /** @hide */ diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java index 38c4ed482e14..eab174ade3b7 100644 --- a/telephony/java/android/telephony/CellIdentityWcdma.java +++ b/telephony/java/android/telephony/CellIdentityWcdma.java @@ -123,8 +123,10 @@ public final class CellIdentityWcdma extends CellIdentity { this(cid.base.base.lac, cid.base.base.cid, cid.base.base.psc, cid.base.base.uarfcn, cid.base.base.mcc, cid.base.base.mnc, cid.base.operatorNames.alphaLong, cid.base.operatorNames.alphaShort, cid.additionalPlmns, - cid.optionalCsgInfo.csgInfo() != null - ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null); + cid.optionalCsgInfo.getDiscriminator() + == android.hardware.radio.V1_5.OptionalCsgInfo.hidl_discriminator.csgInfo + ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) + : null); } private CellIdentityWcdma(@NonNull CellIdentityWcdma cid) { 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), diff --git a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java index de2f5d9a3fe4..6c2e6ddf5dd2 100644 --- a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java +++ b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java @@ -42,7 +42,7 @@ public abstract class EasyConnectStatusCallback { public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0; /** - * East Connect R2 Success event: Configuration applied by Enrollee (Configurator mode). + * Easy Connect R2 Success event: Configuration applied by Enrollee (Configurator mode). * This is the last and final Easy Connect event when both the local device and remote device * implement R2. If either the local device or remote device implement R1, this event will never * be received, and the {@link #EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT} will be received. |