diff options
198 files changed, 5213 insertions, 1753 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 2834ab14f28d..94e5d0b2591b 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -1098,8 +1098,7 @@ public class AppStandbyController implements AppStandbyInternal { if (mAppWidgetManager != null && mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) { - // TODO: consider lowering to ACTIVE - return STANDBY_BUCKET_EXEMPTED; + return STANDBY_BUCKET_ACTIVE; } if (isDeviceProvisioningPackage(packageName)) { diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index 5cf5e0b1d182..cbc8ed636ff2 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -662,14 +662,19 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { return; } + // Cleann up from previous statsd - cancel any alarms that had been set. Do this here + // instead of in binder death because statsd can come back and set different alarms, or not + // want to set an alarm when it had been set. This guarantees that when we get a new statsd, + // we cancel any alarms before it is able to set them. + cancelAnomalyAlarm(); + cancelPullingAlarm(); + cancelAlarmForSubscriberTriggering(); + if (DEBUG) Log.d(TAG, "Saying hi to statsd"); mStatsManagerService.statsdReady(statsd); try { statsd.statsCompanionReady(); - cancelAnomalyAlarm(); - cancelPullingAlarm(); - BroadcastReceiver appUpdateReceiver = new AppUpdateReceiver(); BroadcastReceiver userUpdateReceiver = new UserUpdateReceiver(); BroadcastReceiver shutdownEventReceiver = new ShutdownEventReceiver(); diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index ffd83ba978f4..7090bd46635d 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -307,9 +307,6 @@ private: FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition); FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets); @@ -328,6 +325,7 @@ private: FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); + FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); FRIEND_TEST(CountMetricE2eTest, TestSlicedState); FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); @@ -345,6 +343,10 @@ private: FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); + FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index f00a35d10819..8ba52ef06c44 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -440,6 +440,7 @@ message Atom { 273 [(module) = "permissioncontroller"]; EvsUsageStatsReported evs_usage_stats_reported = 274 [(module) = "evs"]; AudioPowerUsageDataReported audio_power_usage_data_reported = 275; + TvTunerStateChanged tv_tuner_state_changed = 276 [(module) = "framework"]; SdkExtensionStatus sdk_extension_status = 354; // StatsdStats tracks platform atoms with ids upto 500. @@ -447,7 +448,7 @@ message Atom { } // Pulled events will start at field 10000. - // Next: 10082 + // Next: 10084 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000 [(module) = "framework"]; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001 [(module) = "framework"]; @@ -539,6 +540,9 @@ message Atom { SupportedRadioAccessFamily supported_radio_access_family = 10079 [(module) = "telephony"]; SettingSnapshot setting_snapshot = 10080 [(module) = "framework"]; DisplayWakeReason display_wake_reason = 10081 [(module) = "framework"]; + DataUsageBytesTransfer data_usage_bytes_transfer = 10082 [(module) = "framework"]; + BytesTransferByTagAndMetered bytes_transfer_by_tag_and_metered = + 10083 [(module) = "framework"]; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -4940,6 +4944,52 @@ message MobileBytesTransferByFgBg { } /** + * Used for pull network statistics via mobile|wifi networks, and sliced by interesting dimensions. + * Note that data is expected to be sliced into more dimensions in future. In other words, + * the caller must not assume the data is unique when filtering with a set of matching conditions. + * Thus, as the dimension grows, the caller will not be affected. + * + * Pulled from: + * StatsPullAtomService (using NetworkStatsService to get NetworkStats) + */ +message DataUsageBytesTransfer { + // State of this record. Should be NetworkStats#SET_DEFAULT or NetworkStats#SET_FOREGROUND to + // indicate the foreground state, or NetworkStats#SET_ALL to indicate the record is for all + // states combined, not including debug states. See NetworkStats#SET_*. + optional int32 state = 1; + + optional int64 rx_bytes = 2; + + optional int64 rx_packets = 3; + + optional int64 tx_bytes = 4; + + optional int64 tx_packets = 5; + + // Radio Access Technology (RAT) type of this record, should be one of + // TelephonyManager#NETWORK_TYPE_* constants, or NetworkTemplate#NETWORK_TYPE_ALL to indicate + // the record is for all rat types combined. + optional int32 rat_type = 6; + + // Mcc/Mnc read from sim if the record is for a specific subscription, null indicates the + // record is combined regardless of subscription. + optional string sim_mcc = 7; + optional string sim_mnc = 8; + + // Enumeration of opportunistic states with an additional ALL state indicates the record is + // combined regardless of the boolean value in its field. + enum DataSubscriptionState { + ALL = 1; + OPPORTUNISTIC = 2; + NOT_OPPORTUNISTIC = 3; + } + // Mark whether the subscription is an opportunistic data subscription, and ALL indicates the + // record is combined regardless of opportunistic data subscription. + // See {@link SubscriptionManager#setOpportunistic}. + optional DataSubscriptionState opportunistic_data_sub = 9; +} + +/** * Pulls bytes transferred via bluetooth. It is pulled from Bluetooth controller. * * Pulled from: @@ -5982,6 +6032,12 @@ message PackageNotificationChannelPreferences { optional int32 user_locked_fields = 6; // Indicates if the channel was deleted by the app. optional bool is_deleted = 7; + // Indicates if the channel was marked as a conversation by the app. + optional bool is_conversation = 8; + // Indicates if the channel is a conversation that was demoted by the user. + optional bool is_demoted_conversation = 9; + // Indicates if the channel is a conversation that was marked as important by the user. + optional bool is_important_conversation = 10; } /** @@ -9121,6 +9177,28 @@ message SdkExtensionStatus { } /** + * Logs when a tune occurs through device's Frontend. + * This is atom ID 276. + * + * Logged from: + * frameworks/base/media/java/android/media/tv/tuner/Tuner.java + */ +message TvTunerStateChanged { + enum State { + UNKNOWN = 0; + TUNING = 1; // Signal is tuned + LOCKED = 2; // the signal is locked + NOT_LOCKED = 3; // the signal isn’t locked. + SIGNAL_LOST = 4; // the signal was locked, but is lost now. + SCANNING = 5; // the signal is scanned + SCAN_STOPPED = 6; // the scan is stopped. + } + // The uid of the application that sent this custom atom. + optional int32 uid = 1 [(is_uid) = true]; + // new state + optional State state = 2; +} +/** * Logs when an app is frozen or unfrozen. * * Logged from: @@ -9756,3 +9834,25 @@ message AudioPowerUsageDataReported { } optional AudioType type = 4; } + +/** + * Pulls bytes transferred over WiFi and mobile networks sliced by uid, is_metered, and tag. + * + * Pulled from: + * StatsPullAtomService, which uses NetworkStatsService to query NetworkStats. + */ +message BytesTransferByTagAndMetered { + optional int32 uid = 1 [(is_uid) = true]; + + optional bool is_metered = 2; + + optional int32 tag = 3; + + optional int64 rx_bytes = 4; + + optional int64 rx_packets = 5; + + optional int64 tx_bytes = 6; + + optional int64 tx_packets = 7; +} diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 21ffff32f539..d865c2176c1e 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -122,11 +122,11 @@ CountMetricProducer::~CountMetricProducer() { } void CountMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, int oldState, - int newState) { + const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState) { VLOG("CountMetric %lld onStateChanged time %lld, State%d, key %s, %d -> %d", (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(), - oldState, newState); + oldState.mValue.int_value, newState.mValue.int_value); } void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index f9a8842efc3d..26b3d3cc6722 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -53,8 +53,8 @@ public: virtual ~CountMetricProducer(); void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, int oldState, - int newState) override; + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) override; protected: void onMatchedLogEventInternalLocked( diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 0de92f3d9f47..663365924829 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -161,13 +161,12 @@ sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker( 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); + const FieldValue& oldState, + const FieldValue& newState) { // Check if this metric has a StateMap. If so, map the new state value to // the correct state group id. - mapStateValue(atomId, &value); + FieldValue newStateCopy = newState; + mapStateValue(atomId, &newStateCopy); flushIfNeededLocked(eventTimeNs); @@ -185,7 +184,7 @@ void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) { continue; } - whatIt.second->onStateChanged(eventTimeNs, atomId, value); + whatIt.second->onStateChanged(eventTimeNs, atomId, newStateCopy); } } diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 6f84076ee6b5..53f0f28c3386 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -55,8 +55,8 @@ public: 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; + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) override; protected: void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override; diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 28563ad4b0f5..e86fdf06e836 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -182,8 +182,8 @@ public: }; void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, const int32_t oldState, - const int32_t newState){}; + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState){}; // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp. // This method clears all the past buckets. @@ -459,6 +459,7 @@ protected: FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields); + FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets); @@ -488,6 +489,7 @@ protected: FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); + FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index c30532a39244..ad30a88c5d19 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -292,9 +292,6 @@ private: FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition); FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets); @@ -322,6 +319,7 @@ private: TestActivationOnBootMultipleActivationsDifferentActivationTypes); FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); + FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); FRIEND_TEST(CountMetricE2eTest, TestSlicedState); FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); @@ -339,6 +337,10 @@ private: FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); + FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index bf636a4f048d..f03ce4550bc4 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -182,15 +182,26 @@ ValueMetricProducer::~ValueMetricProducer() { } void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId, - const HashableDimensionKey& primaryKey, int oldState, - int newState) { + const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState) { VLOG("ValueMetric %lld onStateChanged time %lld, State %d, key %s, %d -> %d", (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(), - oldState, newState); + oldState.mValue.int_value, newState.mValue.int_value); // If condition is not true, we do not need to pull for this state change. if (mCondition != ConditionState::kTrue) { return; } + + // If old and new states are in the same StateGroup, then we do not need to + // pull for this state change. + FieldValue oldStateCopy = oldState; + FieldValue newStateCopy = newState; + mapStateValue(atomId, &oldStateCopy); + mapStateValue(atomId, &newStateCopy); + if (oldStateCopy == newStateCopy) { + return; + } + bool isEventLate = eventTimeNs < mCurrentBucketStartTimeNs; if (isEventLate) { VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index c8dc8cc290c4..751fef2bf2b1 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -90,7 +90,7 @@ public: }; void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey, - int oldState, int newState) override; + const FieldValue& oldState, const FieldValue& newState) override; protected: void onMatchedLogEventInternalLocked( diff --git a/cmds/statsd/src/state/StateListener.h b/cmds/statsd/src/state/StateListener.h index d1af1968ac38..63880017ca18 100644 --- a/cmds/statsd/src/state/StateListener.h +++ b/cmds/statsd/src/state/StateListener.h @@ -45,8 +45,8 @@ public: * [newState]: Current state value after state change */ virtual void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, int oldState, - int newState) = 0; + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) = 0; }; } // namespace statsd diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp index b63713b64c5d..41e525c343ba 100644 --- a/cmds/statsd/src/state/StateTracker.cpp +++ b/cmds/statsd/src/state/StateTracker.cpp @@ -35,31 +35,30 @@ void StateTracker::onLogEvent(const LogEvent& event) { HashableDimensionKey primaryKey; filterPrimaryKey(event.getValues(), &primaryKey); - FieldValue stateValue; - if (!getStateFieldValueFromLogEvent(event, &stateValue)) { + FieldValue newState; + if (!getStateFieldValueFromLogEvent(event, &newState)) { ALOGE("StateTracker error extracting state from log event. Missing exclusive state field."); clearStateForPrimaryKey(eventTimeNs, primaryKey); return; } - mField.setField(stateValue.mField.getField()); + mField.setField(newState.mField.getField()); - if (stateValue.mValue.getType() != INT) { + if (newState.mValue.getType() != INT) { ALOGE("StateTracker error extracting state from log event. Type: %d", - stateValue.mValue.getType()); + newState.mValue.getType()); clearStateForPrimaryKey(eventTimeNs, primaryKey); return; } - const int32_t resetState = event.getResetState(); - if (resetState != -1) { + if (int resetState = event.getResetState(); resetState != -1) { VLOG("StateTracker new reset state: %d", resetState); - handleReset(eventTimeNs, resetState); + const FieldValue resetStateFieldValue(mField, Value(resetState)); + handleReset(eventTimeNs, resetStateFieldValue); return; } - const int32_t newState = stateValue.mValue.int_value; - const bool nested = stateValue.mAnnotations.isNested(); + const bool nested = newState.mAnnotations.isNested(); StateValueInfo* stateValueInfo = &mStateMap[primaryKey]; updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, nested, stateValueInfo); } @@ -85,7 +84,7 @@ bool StateTracker::getStateValue(const HashableDimensionKey& queryKey, FieldValu return false; } -void StateTracker::handleReset(const int64_t eventTimeNs, const int32_t newState) { +void StateTracker::handleReset(const int64_t eventTimeNs, const FieldValue& newState) { VLOG("StateTracker handle reset"); for (auto& [primaryKey, stateValueInfo] : mStateMap) { updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, @@ -102,8 +101,9 @@ void StateTracker::clearStateForPrimaryKey(const int64_t eventTimeNs, // If there is no entry for the primaryKey in mStateMap, then the state is already // kStateUnknown. + const FieldValue state(mField, Value(kStateUnknown)); if (it != mStateMap.end()) { - updateStateForPrimaryKey(eventTimeNs, primaryKey, kStateUnknown, + updateStateForPrimaryKey(eventTimeNs, primaryKey, state, false /* nested; treat this state change as not nested */, &it->second); } @@ -111,22 +111,26 @@ void StateTracker::clearStateForPrimaryKey(const int64_t eventTimeNs, void StateTracker::updateStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey, - const int32_t newState, const bool nested, + const FieldValue& newState, const bool nested, StateValueInfo* stateValueInfo) { - const int32_t oldState = stateValueInfo->state; + FieldValue oldState; + oldState.mField = mField; + oldState.mValue.setInt(stateValueInfo->state); + const int32_t oldStateValue = stateValueInfo->state; + const int32_t newStateValue = newState.mValue.int_value; - if (kStateUnknown == newState) { + if (kStateUnknown == newStateValue) { mStateMap.erase(primaryKey); } // Update state map for non-nested counting case. // Every state event triggers a state overwrite. if (!nested) { - stateValueInfo->state = newState; + stateValueInfo->state = newStateValue; stateValueInfo->count = 1; // Notify listeners if state has changed. - if (oldState != newState) { + if (oldStateValue != newStateValue) { notifyListeners(eventTimeNs, primaryKey, oldState, newState); } return; @@ -142,26 +146,26 @@ void StateTracker::updateStateForPrimaryKey(const int64_t eventTimeNs, // In atoms.proto, a state atom with nested counting enabled // must only have 2 states. There is no enforcemnt here of this requirement. // The atom must be logged correctly. - if (kStateUnknown == newState) { - if (kStateUnknown != oldState) { + if (kStateUnknown == newStateValue) { + if (kStateUnknown != oldStateValue) { notifyListeners(eventTimeNs, primaryKey, oldState, newState); } - } else if (oldState == kStateUnknown) { - stateValueInfo->state = newState; + } else if (oldStateValue == kStateUnknown) { + stateValueInfo->state = newStateValue; stateValueInfo->count = 1; notifyListeners(eventTimeNs, primaryKey, oldState, newState); - } else if (oldState == newState) { + } else if (oldStateValue == newStateValue) { stateValueInfo->count++; } else if (--stateValueInfo->count == 0) { - stateValueInfo->state = newState; + stateValueInfo->state = newStateValue; stateValueInfo->count = 1; notifyListeners(eventTimeNs, primaryKey, oldState, newState); } } void StateTracker::notifyListeners(const int64_t eventTimeNs, - const HashableDimensionKey& primaryKey, const int32_t oldState, - const int32_t newState) { + const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState) { for (auto l : mListeners) { auto sl = l.promote(); if (sl != nullptr) { diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h index c5f6315fc992..abd579e7e302 100644 --- a/cmds/statsd/src/state/StateTracker.h +++ b/cmds/statsd/src/state/StateTracker.h @@ -72,19 +72,19 @@ private: std::set<wp<StateListener>> mListeners; // Reset all state values in map to the given state. - void handleReset(const int64_t eventTimeNs, const int32_t newState); + void handleReset(const int64_t eventTimeNs, const FieldValue& newState); // Clears the state value mapped to the given primary key by setting it to kStateUnknown. void clearStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey); // Update the StateMap based on the received state value. void updateStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey, - const int32_t newState, const bool nested, + const FieldValue& newState, const bool nested, StateValueInfo* stateValueInfo); // Notify registered state listeners of state change. void notifyListeners(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey, - const int32_t oldState, const int32_t newState); + const FieldValue& oldState, const FieldValue& newState); }; bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output); diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 7c0057d87ca9..2e6043df0e64 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -131,7 +131,7 @@ message SimplePredicate { UNKNOWN = 0; FALSE = 1; } - optional InitialValue initial_value = 5 [default = FALSE]; + optional InitialValue initial_value = 5 [default = UNKNOWN]; optional FieldMatcher dimensions = 6; } diff --git a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp index 1a7cd5584f3d..04eb40080631 100644 --- a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp @@ -28,6 +28,92 @@ namespace statsd { #ifdef __ANDROID__ /** + * Tests the initial condition and condition after the first log events for + * count metrics with either a combination condition or simple condition. + * + * Metrics should be initialized with condition kUnknown (given that the + * predicate is using the default InitialValue of UNKNOWN). The condition should + * be updated to either kFalse or kTrue if a condition event is logged for all + * children conditions. + */ +TEST(CountMetricE2eTest, TestInitialConditionChanges) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + auto syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); + + auto screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + + auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = deviceUnpluggedPredicate; + + auto screenOnOnBatteryPredicate = config.add_predicate(); + screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate")); + screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate); + addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate); + + // CountSyncStartWhileScreenOnOnBattery (CombinationCondition) + CountMetric* countMetric1 = config.add_count_metric(); + countMetric1->set_id(StringToId("CountSyncStartWhileScreenOnOnBattery")); + countMetric1->set_what(syncStartMatcher.id()); + countMetric1->set_condition(screenOnOnBatteryPredicate->id()); + countMetric1->set_bucket(FIVE_MINUTES); + + // CountSyncStartWhileOnBattery (SimpleCondition) + CountMetric* countMetric2 = config.add_count_metric(); + countMetric2->set_id(StringToId("CountSyncStartWhileOnBatterySliceScreen")); + countMetric2->set_what(syncStartMatcher.id()); + countMetric2->set_condition(deviceUnpluggedPredicate.id()); + countMetric2->set_bucket(FIVE_MINUTES); + + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + 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(2, metricsManager->mAllMetricProducers.size()); + + sp<MetricProducer> metricProducer1 = metricsManager->mAllMetricProducers[0]; + sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[1]; + + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto screenOnEvent = + CreateScreenStateChangedEvent(bucketStartTimeNs + 30, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto pluggedUsbEvent = CreateBatteryStateChangedEvent( + bucketStartTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); + processor->OnLogEvent(pluggedUsbEvent.get()); + EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition); + + auto pluggedNoneEvent = CreateBatteryStateChangedEvent( + bucketStartTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); + processor->OnLogEvent(pluggedNoneEvent.get()); + EXPECT_EQ(ConditionState::kTrue, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition); +} + +/** * Test a count metric that has one slice_by_state with no primary fields. * * Once the CountMetricProducer is initialized, it has one atom id in @@ -85,7 +171,7 @@ TEST(CountMetricE2eTest, TestSlicedState) { x x x x x x (syncStartEvents) | | (ScreenIsOnEvent) | | (ScreenIsOffEvent) - | (ScreenUnknownEvent) + | (ScreenDozeEvent) */ // Initialize log events - first bucket. std::vector<int> attributionUids1 = {123}; @@ -243,9 +329,8 @@ TEST(CountMetricE2eTest, TestSlicedStateWithMap) { |-----------------------------|-----------------------------|-- x x x x x x x x x (syncStartEvents) -----------------------------------------------------------SCREEN_OFF events - | (ScreenStateUnknownEvent = 0) | | (ScreenStateOffEvent = 1) - | (ScreenStateDozeEvent = 3) + | | (ScreenStateDozeEvent = 3) | (ScreenStateDozeSuspendEvent = 4) -----------------------------------------------------------SCREEN_ON events @@ -262,7 +347,7 @@ TEST(CountMetricE2eTest, TestSlicedStateWithMap) { attributionTags1, "sync_name")); // 0:30 events.push_back(CreateScreenStateChangedEvent( bucketStartTimeNs + 30 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN)); // 0:40 + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40 events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 60 * NS_PER_SEC, attributionUids1, attributionTags1, "sync_name")); // 1:10 events.push_back(CreateScreenStateChangedEvent( @@ -625,9 +710,8 @@ TEST(CountMetricE2eTest, TestMultipleSlicedStates) { |------------------------|------------------------|-- 1 1 1 1 1 2 1 1 2 (AppCrashEvents) ---------------------------------------------------SCREEN_OFF events - | (ScreenUnknownEvent = 0) | | (ScreenOffEvent = 1) - | (ScreenDozeEvent = 3) + | | (ScreenDozeEvent = 3) ---------------------------------------------------SCREEN_ON events | | (ScreenOnEvent = 2) | (ScreenOnSuspendEvent = 6) @@ -660,7 +744,7 @@ TEST(CountMetricE2eTest, TestMultipleSlicedStates) { CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /*uid*/)); // 0:30 events.push_back(CreateScreenStateChangedEvent( bucketStartTimeNs + 30 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN)); // 0:40 + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40 events.push_back( CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, 1 /*uid*/)); // 1:10 events.push_back(CreateUidProcessStateChangedEvent( diff --git a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp index e595f290ffdf..4d3928277527 100644 --- a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp @@ -63,8 +63,143 @@ StatsdConfig CreateStatsdConfig(bool useCondition = true) { return config; } +StatsdConfig CreateStatsdConfigWithStates() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + auto pulledAtomMatcher = CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = pulledAtomMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); + + auto screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + + auto screenOffPredicate = CreateScreenIsOffPredicate(); + *config.add_predicate() = screenOffPredicate; + + auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = deviceUnpluggedPredicate; + + auto screenOnOnBatteryPredicate = config.add_predicate(); + screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate")); + screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate); + addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate); + + auto screenOffOnBatteryPredicate = config.add_predicate(); + screenOffOnBatteryPredicate->set_id(StringToId("ScreenOffOnBattery")); + screenOffOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenOffPredicate, screenOffOnBatteryPredicate); + addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOffOnBatteryPredicate); + + const State screenState = + CreateScreenStateWithSimpleOnOffMap(/*screen on id=*/321, /*screen off id=*/123); + *config.add_state() = screenState; + + // ValueMetricSubsystemSleepWhileScreenOnOnBattery + auto valueMetric1 = config.add_value_metric(); + valueMetric1->set_id(metricId); + valueMetric1->set_what(pulledAtomMatcher.id()); + valueMetric1->set_condition(screenOnOnBatteryPredicate->id()); + *valueMetric1->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + valueMetric1->set_bucket(FIVE_MINUTES); + valueMetric1->set_use_absolute_value_on_reset(true); + valueMetric1->set_skip_zero_diff_output(false); + valueMetric1->set_max_pull_delay_sec(INT_MAX); + + // ValueMetricSubsystemSleepWhileScreenOffOnBattery + ValueMetric* valueMetric2 = config.add_value_metric(); + valueMetric2->set_id(StringToId("ValueMetricSubsystemSleepWhileScreenOffOnBattery")); + valueMetric2->set_what(pulledAtomMatcher.id()); + valueMetric2->set_condition(screenOffOnBatteryPredicate->id()); + *valueMetric2->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + valueMetric2->set_bucket(FIVE_MINUTES); + valueMetric2->set_use_absolute_value_on_reset(true); + valueMetric2->set_skip_zero_diff_output(false); + valueMetric2->set_max_pull_delay_sec(INT_MAX); + + // ValueMetricSubsystemSleepWhileOnBatterySliceScreen + ValueMetric* valueMetric3 = config.add_value_metric(); + valueMetric3->set_id(StringToId("ValueMetricSubsystemSleepWhileOnBatterySliceScreen")); + valueMetric3->set_what(pulledAtomMatcher.id()); + valueMetric3->set_condition(deviceUnpluggedPredicate.id()); + *valueMetric3->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + valueMetric3->add_slice_by_state(screenState.id()); + valueMetric3->set_bucket(FIVE_MINUTES); + valueMetric3->set_use_absolute_value_on_reset(true); + valueMetric3->set_skip_zero_diff_output(false); + valueMetric3->set_max_pull_delay_sec(INT_MAX); + return config; +} + } // namespace +/** + * Tests the initial condition and condition after the first log events for + * value metrics with either a combination condition or simple condition. + * + * Metrics should be initialized with condition kUnknown (given that the + * predicate is using the default InitialValue of UNKNOWN). The condition should + * be updated to either kFalse or kTrue if a condition event is logged for all + * children conditions. + */ +TEST(ValueMetricE2eTest, TestInitialConditionChanges) { + StatsdConfig config = CreateStatsdConfigWithStates(); + int64_t baseTimeNs = getElapsedRealtimeNs(); + int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + int32_t tagId = util::SUBSYSTEM_SLEEP_STATE; + auto processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make<FakeSubsystemSleepCallback>(), tagId); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(3, metricsManager->mAllMetricProducers.size()); + + // Combination condition metric - screen on and device unplugged + sp<MetricProducer> metricProducer1 = metricsManager->mAllMetricProducers[0]; + // Simple condition metric - device unplugged + sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[2]; + + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto screenOnEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 30, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 40, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto pluggedUsbEvent = CreateBatteryStateChangedEvent( + configAddedTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); + processor->OnLogEvent(pluggedUsbEvent.get()); + EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition); + + auto pluggedNoneEvent = CreateBatteryStateChangedEvent( + configAddedTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); + processor->OnLogEvent(pluggedNoneEvent.get()); + EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition); +} + TEST(ValueMetricE2eTest, TestPulledEvents) { auto config = CreateStatsdConfig(); int64_t baseTimeNs = getElapsedRealtimeNs(); diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index b6e1075bcb72..474aa2234837 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -3898,14 +3898,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5)); return true; })) - // Screen state change to VR. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector<std::shared_ptr<LogEvent>>* data, bool) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 9)); - return true; - })) + // Screen state change to VR has no pull because it is in the same + // state group as ON. + + // Screen state change to ON has no pull because it is in the same + // state group as VR. + // Screen state change to OFF. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data, bool) { @@ -3969,23 +3967,33 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { EXPECT_EQ(true, it->second[0].hasValue); EXPECT_EQ(2, it->second[0].value.long_value); - // Bucket status after screen state change ON->VR (also ON). + // Bucket status after screen state change ON->VR. + // Both ON and VR are in the same state group, so the base should not change. screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, android::view::DisplayStateEnum::DISPLAY_STATE_VR); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); EXPECT_EQ(true, itBase->second[0].hasBase); - EXPECT_EQ(9, itBase->second[0].base.long_value); - // Value for dimension, state key {{}, ON GROUP} - EXPECT_EQ(screenOnGroup.group_id(), - it->first.getStateValuesKey().getValues()[0].mValue.long_value); + EXPECT_EQ(5, itBase->second[0].base.long_value); + // Value for dimension, state key {{}, kStateUnknown} EXPECT_EQ(true, it->second[0].hasValue); - EXPECT_EQ(4, it->second[0].value.long_value); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Bucket status after screen state change VR->ON. + // Both ON and VR are in the same state group, so the base should not change. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_EQ(true, itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); // Value for dimension, state key {{}, kStateUnknown} - it++; EXPECT_EQ(true, it->second[0].hasValue); EXPECT_EQ(2, it->second[0].value.long_value); diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp index 13e8f5c343f2..530ac5e01f3e 100644 --- a/cmds/statsd/tests/state/StateTracker_test.cpp +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -50,8 +50,9 @@ public: std::vector<Update> updates; void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, int oldState, int newState) { - updates.emplace_back(primaryKey, newState); + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) { + updates.emplace_back(primaryKey, newState.mValue.int_value); } }; diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 6a7ad1faddea..582df0c1a2a3 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -169,7 +169,6 @@ AtomMatcher CreateScreenStateChangedAtomMatcher( return atom_matcher; } - AtomMatcher CreateScreenTurnedOnAtomMatcher() { return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", android::view::DisplayStateEnum::DISPLAY_STATE_ON); @@ -335,22 +334,46 @@ State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId) { return state; } +State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) { + State state; + state.set_id(StringToId("ScreenStateSimpleOnOff")); + state.set_atom_id(util::SCREEN_STATE_CHANGED); + + auto map = CreateScreenStateSimpleOnOffMap(screenOnId, screenOffId); + *state.mutable_map() = map; + + return state; +} + StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId) { StateMap_StateGroup group; group.set_group_id(screenOnId); - group.add_value(2); - group.add_value(5); - group.add_value(6); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_VR); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND); return group; } StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId) { StateMap_StateGroup group; group.set_group_id(screenOffId); - group.add_value(0); - group.add_value(1); - group.add_value(3); - group.add_value(4); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND); + return group; +} + +StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId) { + StateMap_StateGroup group; + group.set_group_id(screenOnId); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON); + return group; +} + +StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId) { + StateMap_StateGroup group; + group.set_group_id(screenOffId); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF); return group; } @@ -361,6 +384,13 @@ StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId) { return map; } +StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) { + StateMap map; + *map.add_group() = CreateScreenStateSimpleOnGroup(screenOnId); + *map.add_group() = CreateScreenStateSimpleOffGroup(screenOffId); + return map; +} + void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combinationPredicate) { combinationPredicate->mutable_combination()->add_predicate(predicate.id()); diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index dc012c5381eb..6a5d5da2895c 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -149,17 +149,30 @@ State CreateUidProcessState(); // Create State proto for overlay state atom. State CreateOverlayState(); +// Create State proto for screen state atom with on/off map. State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId); +// Create State proto for screen state atom with simple on/off map. +State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId); + // Create StateGroup proto for ScreenState ON group StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId); // Create StateGroup proto for ScreenState OFF group StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId); +// Create StateGroup proto for simple ScreenState ON group +StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId); + +// Create StateGroup proto for simple ScreenState OFF group +StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId); + // Create StateMap proto for ScreenState ON/OFF map StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId); +// Create StateMap proto for simple ScreenState ON/OFF map +StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId); + // Add a predicate to the predicate combination. void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index eea1d69b6326..8e43ca3c6739 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1538,6 +1538,12 @@ public final class ActivityThread extends ClientTransactionHandler { IoUtils.closeQuietly(pfd); } + @Override + public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) { + PropertyInvalidatedCache.dumpCacheInfo(pfd.getFileDescriptor(), args); + IoUtils.closeQuietly(pfd); + } + private File getDatabasesDir(Context context) { // There's no simple way to get the databases/ path, so do it this way. return context.getDatabasePath("a").getParentFile(); @@ -5902,6 +5908,12 @@ public final class ActivityThread extends ClientTransactionHandler { } } + /** + * Sets the supplied {@code overrideConfig} as pending for the {@code activityToken}. Calling + * this method prevents any calls to + * {@link #handleActivityConfigurationChanged(IBinder, Configuration, int, boolean)} from + * processing any configurations older than {@code overrideConfig}. + */ @Override public void updatePendingActivityConfiguration(IBinder activityToken, Configuration overrideConfig) { @@ -5918,13 +5930,22 @@ public final class ActivityThread extends ClientTransactionHandler { } synchronized (r) { + if (r.mPendingOverrideConfig != null + && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) { + if (DEBUG_CONFIGURATION) { + Slog.v(TAG, "Activity has newer configuration pending so drop this" + + " transaction. overrideConfig=" + overrideConfig + + " r.mPendingOverrideConfig=" + r.mPendingOverrideConfig); + } + return; + } r.mPendingOverrideConfig = overrideConfig; } } @Override public void handleActivityConfigurationChanged(IBinder activityToken, - Configuration overrideConfig, int displayId) { + @NonNull Configuration overrideConfig, int displayId) { handleActivityConfigurationChanged(activityToken, overrideConfig, displayId, // This is the only place that uses alwaysReportChange=true. The entry point should // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side @@ -5935,15 +5956,18 @@ public final class ActivityThread extends ClientTransactionHandler { } /** - * Handle new activity configuration and/or move to a different display. + * Handle new activity configuration and/or move to a different display. This method is a noop + * if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been called with + * a newer config than {@code overrideConfig}. + * * @param activityToken Target activity token. * @param overrideConfig Activity override config. * @param displayId Id of the display where activity was moved to, -1 if there was no move and * value didn't change. * @param alwaysReportChange If the configuration is changed, always report to activity. */ - void handleActivityConfigurationChanged(IBinder activityToken, Configuration overrideConfig, - int displayId, boolean alwaysReportChange) { + void handleActivityConfigurationChanged(IBinder activityToken, + @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) { ActivityClientRecord r = mActivities.get(activityToken); // Check input params. if (r == null || r.activity == null) { @@ -5954,9 +5978,13 @@ public final class ActivityThread extends ClientTransactionHandler { && displayId != r.activity.getDisplayId(); synchronized (r) { - if (r.mPendingOverrideConfig != null - && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) { - overrideConfig = r.mPendingOverrideConfig; + if (overrideConfig.isOtherSeqNewer(r.mPendingOverrideConfig)) { + if (DEBUG_CONFIGURATION) { + Slog.v(TAG, "Activity has newer configuration pending so drop this" + + " transaction. overrideConfig=" + overrideConfig + + " r.mPendingOverrideConfig=" + r.mPendingOverrideConfig); + } + return; } r.mPendingOverrideConfig = null; } @@ -6202,6 +6230,12 @@ public final class ActivityThread extends ClientTransactionHandler { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory"); if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level); + if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { + for (PropertyInvalidatedCache pic : PropertyInvalidatedCache.getActiveCaches()) { + pic.clear(); + } + } + ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null); final int N = callbacks.size(); diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 3ce768944e48..be1681bc7cc6 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -229,7 +229,16 @@ interface IActivityTaskManager { void unregisterTaskStackListener(in ITaskStackListener listener); void setTaskResizeable(int taskId, int resizeableMode); void toggleFreeformWindowingMode(in IBinder token); - void resizeTask(int taskId, in Rect bounds, int resizeMode); + + /** + * Resize the task with given bounds + * + * @param taskId The id of the task to set the bounds for. + * @param bounds The new bounds. + * @param resizeMode Resize mode defined as {@code ActivityTaskManager#RESIZE_MODE_*} constants. + * @return Return true on success. Otherwise false. + */ + boolean resizeTask(int taskId, in Rect bounds, int resizeMode); void moveStackToDisplay(int stackId, int displayId); void removeStack(int stackId); diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 1f6e4cac199a..6e9157e2a8c3 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -119,6 +119,7 @@ oneway interface IApplicationThread { boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable, in String[] args); void dumpGfxInfo(in ParcelFileDescriptor fd, in String[] args); + void dumpCacheInfo(in ParcelFileDescriptor fd, in String[] args); void dumpProvider(in ParcelFileDescriptor fd, IBinder servicetoken, in String[] args); void dumpDbInfo(in ParcelFileDescriptor fd, in String[] args); diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 3110e18985b0..01cf2b94a842 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -26,12 +26,20 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FastPrintWriter; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.Random; +import java.util.Set; +import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicLong; /** @@ -197,6 +205,14 @@ public abstract class PropertyInvalidatedCache<Query, Result> { @GuardedBy("sCorkLock") private static final HashMap<String, Integer> sCorks = new HashMap<>(); + /** + * Weakly references all cache objects in the current process, allowing us to iterate over + * them all for purposes like issuing debug dumps and reacting to memory pressure. + */ + @GuardedBy("sCorkLock") + private static final WeakHashMap<PropertyInvalidatedCache, Void> sCaches = + new WeakHashMap<>(); + private final Object mLock = new Object(); /** @@ -225,6 +241,11 @@ public abstract class PropertyInvalidatedCache<Query, Result> { private boolean mDisabled = false; /** + * Maximum number of entries the cache will maintain. + */ + private final int mMaxEntries; + + /** * Make a new property invalidated cache. * * @param maxEntries Maximum number of entries to cache; LRU discard @@ -232,6 +253,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { */ public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) { mPropertyName = propertyName; + mMaxEntries = maxEntries; mCache = new LinkedHashMap<Query, Result>( 2 /* start small */, 0.75f /* default load factor */, @@ -241,6 +263,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> { return size() > maxEntries; } }; + synchronized (sCorkLock) { + sCaches.put(this, null); + } } /** @@ -248,6 +273,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> { */ public final void clear() { synchronized (mLock) { + if (DEBUG) { + Log.d(TAG, "clearing cache for " + mPropertyName); + } mCache.clear(); } } @@ -710,4 +738,87 @@ public abstract class PropertyInvalidatedCache<Query, Result> { Log.d(TAG, "disabling all caches in the process"); sEnabled = false; } + + /** + * Returns a list of caches alive at the current time. + */ + public static ArrayList<PropertyInvalidatedCache> getActiveCaches() { + synchronized (sCorkLock) { + return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet()); + } + } + + /** + * Returns a list of the active corks in a process. + */ + public static ArrayList<Map.Entry<String, Integer>> getActiveCorks() { + synchronized (sCorkLock) { + return new ArrayList<Map.Entry<String, Integer>>(sCorks.entrySet()); + } + } + + private void dumpContents(PrintWriter pw, String[] args) { + synchronized (mLock) { + pw.println(String.format(" Cache Property Name: %s", cacheName())); + pw.println(String.format(" Last Observed Nonce: %d", mLastSeenNonce)); + pw.println(String.format(" Current Size: %d, Max Size: %d", + mCache.entrySet().size(), mMaxEntries)); + pw.println(String.format(" Enabled: %s", mDisabled ? "false" : "true")); + + Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet(); + if (cacheEntries.size() == 0) { + pw.println(""); + return; + } + + pw.println(""); + pw.println(" Contents:"); + for (Map.Entry<Query, Result> entry : cacheEntries) { + String key = Objects.toString(entry.getKey()); + String value = Objects.toString(entry.getValue()); + + pw.println(String.format(" Key: %s\n Value: %s\n", key, value)); + } + } + } + + /** + * Dumps contents of every cache in the process to the provided FileDescriptor. + */ + public static void dumpCacheInfo(FileDescriptor fd, String[] args) { + ArrayList<PropertyInvalidatedCache> activeCaches; + ArrayList<Map.Entry<String, Integer>> activeCorks; + + try ( + FileOutputStream fout = new FileOutputStream(fd); + PrintWriter pw = new FastPrintWriter(fout); + ) { + if (!sEnabled) { + pw.println(" Caching is disabled in this process."); + return; + } + + synchronized (sCorkLock) { + activeCaches = getActiveCaches(); + activeCorks = getActiveCorks(); + + if (activeCorks.size() > 0) { + pw.println(" Corking Status:"); + for (int i = 0; i < activeCorks.size(); i++) { + Map.Entry<String, Integer> entry = activeCorks.get(i); + pw.println(String.format(" Property Name: %s Count: %d", + entry.getKey(), entry.getValue())); + } + } + } + + for (int i = 0; i < activeCaches.size(); i++) { + PropertyInvalidatedCache currentCache = activeCaches.get(i); + currentCache.dumpContents(pw, args); + pw.flush(); + } + } catch (IOException e) { + Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances"); + } + } } diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java index 0d4e16bbb0f3..8b52242a6b6c 100644 --- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java @@ -19,6 +19,7 @@ package android.app.servertransaction; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.Display.INVALID_DISPLAY; +import android.annotation.NonNull; import android.app.ClientTransactionHandler; import android.content.res.Configuration; import android.os.IBinder; @@ -37,6 +38,8 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem { @Override public void preExecute(android.app.ClientTransactionHandler client, IBinder token) { + // Notify the client of an upcoming change in the token configuration. This ensures that + // batches of config change items only process the newest configuration. client.updatePendingActivityConfiguration(token, mConfiguration); } @@ -55,7 +58,11 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem { private ActivityConfigurationChangeItem() {} /** Obtain an instance initialized with provided params. */ - public static ActivityConfigurationChangeItem obtain(Configuration config) { + public static ActivityConfigurationChangeItem obtain(@NonNull Configuration config) { + if (config == null) { + throw new IllegalArgumentException("Config must not be null."); + } + ActivityConfigurationChangeItem instance = ObjectPool.obtain(ActivityConfigurationChangeItem.class); if (instance == null) { @@ -68,7 +75,7 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem { @Override public void recycle() { - mConfiguration = null; + mConfiguration = Configuration.EMPTY; ObjectPool.recycle(this); } diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java index f6d3dbdf5596..9a457a3aad40 100644 --- a/core/java/android/app/servertransaction/MoveToDisplayItem.java +++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java @@ -18,6 +18,7 @@ package android.app.servertransaction; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; +import android.annotation.NonNull; import android.app.ClientTransactionHandler; import android.content.res.Configuration; import android.os.IBinder; @@ -36,6 +37,13 @@ public class MoveToDisplayItem extends ClientTransactionItem { private Configuration mConfiguration; @Override + public void preExecute(ClientTransactionHandler client, IBinder token) { + // Notify the client of an upcoming change in the token configuration. This ensures that + // batches of config change items only process the newest configuration. + client.updatePendingActivityConfiguration(token, mConfiguration); + } + + @Override public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay"); @@ -49,7 +57,12 @@ public class MoveToDisplayItem extends ClientTransactionItem { private MoveToDisplayItem() {} /** Obtain an instance initialized with provided params. */ - public static MoveToDisplayItem obtain(int targetDisplayId, Configuration configuration) { + public static MoveToDisplayItem obtain(int targetDisplayId, + @NonNull Configuration configuration) { + if (configuration == null) { + throw new IllegalArgumentException("Configuration must not be null"); + } + MoveToDisplayItem instance = ObjectPool.obtain(MoveToDisplayItem.class); if (instance == null) { instance = new MoveToDisplayItem(); @@ -63,7 +76,7 @@ public class MoveToDisplayItem extends ClientTransactionItem { @Override public void recycle() { mTargetDisplayId = 0; - mConfiguration = null; + mConfiguration = Configuration.EMPTY; ObjectPool.recycle(this); } diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 75ce0dcc1d1d..3fef92b203b6 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -625,7 +625,10 @@ public class AppWidgetHostView extends FrameLayout { } } defaultView = inflater.inflate(layoutId, this, false); - defaultView.setOnClickListener(this::onDefaultViewClicked); + if (!(defaultView instanceof AdapterView)) { + // AdapterView does not support onClickListener + defaultView.setOnClickListener(this::onDefaultViewClicked); + } } else { Log.w(TAG, "can't inflate defaultView because mInfo is missing"); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9832bc1b79d2..2f488cdc3158 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -385,6 +385,9 @@ public abstract class PackageManager { * <p> * Note: this flag may cause less information about currently installed * applications to be returned. + * <p> + * Note: use of this flag requires the android.permission.QUERY_ALL_PACKAGES + * permission to see uninstalled packages. */ public static final int MATCH_UNINSTALLED_PACKAGES = 0x00002000; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 312e98e77636..d086459080f7 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -127,7 +127,6 @@ import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; /** * Parser for package files (APKs) on disk. This supports apps packaged either @@ -239,11 +238,6 @@ public class PackageParser { public static final boolean LOG_UNSAFE_BROADCASTS = false; - /** - * Total number of packages that were read from the cache. We use it only for logging. - */ - public static final AtomicInteger sCachedPackageReadCount = new AtomicInteger(); - // Set of broadcast actions that are safe for manifest receivers public static final Set<String> SAFE_BROADCASTS = new ArraySet<>(); static { diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 9b809b86eae9..b978ae559390 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -191,6 +191,58 @@ public class DatabaseUtils { } } + /** {@hide} */ + public static long executeInsert(@NonNull SQLiteDatabase db, @NonNull String sql, + @Nullable Object[] bindArgs) throws SQLException { + try (SQLiteStatement st = db.compileStatement(sql)) { + bindArgs(st, bindArgs); + return st.executeInsert(); + } + } + + /** {@hide} */ + public static int executeUpdateDelete(@NonNull SQLiteDatabase db, @NonNull String sql, + @Nullable Object[] bindArgs) throws SQLException { + try (SQLiteStatement st = db.compileStatement(sql)) { + bindArgs(st, bindArgs); + return st.executeUpdateDelete(); + } + } + + /** {@hide} */ + private static void bindArgs(@NonNull SQLiteStatement st, @Nullable Object[] bindArgs) { + if (bindArgs == null) return; + + for (int i = 0; i < bindArgs.length; i++) { + final Object bindArg = bindArgs[i]; + switch (getTypeOfObject(bindArg)) { + case Cursor.FIELD_TYPE_NULL: + st.bindNull(i + 1); + break; + case Cursor.FIELD_TYPE_INTEGER: + st.bindLong(i + 1, ((Number) bindArg).longValue()); + break; + case Cursor.FIELD_TYPE_FLOAT: + st.bindDouble(i + 1, ((Number) bindArg).doubleValue()); + break; + case Cursor.FIELD_TYPE_BLOB: + st.bindBlob(i + 1, (byte[]) bindArg); + break; + case Cursor.FIELD_TYPE_STRING: + default: + if (bindArg instanceof Boolean) { + // Provide compatibility with legacy + // applications which may pass Boolean values in + // bind args. + st.bindLong(i + 1, ((Boolean) bindArg).booleanValue() ? 1 : 0); + } else { + st.bindString(i + 1, bindArg.toString()); + } + break; + } + } + } + /** * Binds the given Object to the given SQLiteProgram using the proper * typing. For example, bind numbers as longs/doubles, and everything else diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index 36ec67ee1471..669d0466fdf2 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -626,7 +626,7 @@ public class SQLiteQueryBuilder { Log.d(TAG, sql); } } - return db.executeSql(sql, sqlArgs); + return DatabaseUtils.executeInsert(db, sql, sqlArgs); } /** @@ -702,7 +702,7 @@ public class SQLiteQueryBuilder { Log.d(TAG, sql); } } - return db.executeSql(sql, sqlArgs); + return DatabaseUtils.executeUpdateDelete(db, sql, sqlArgs); } /** @@ -762,7 +762,7 @@ public class SQLiteQueryBuilder { Log.d(TAG, sql); } } - return db.executeSql(sql, sqlArgs); + return DatabaseUtils.executeUpdateDelete(db, sql, sqlArgs); } private void enforceStrictColumns(@Nullable String[] projection) { diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 4bed985489ef..f9ed2f851552 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -314,6 +314,92 @@ public class SoundTrigger { } @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ModuleProperties)) { + return false; + } + ModuleProperties other = (ModuleProperties) obj; + if (mId != other.mId) { + return false; + } + if (!mImplementor.equals(other.mImplementor)) { + return false; + } + if (!mDescription.equals(other.mDescription)) { + return false; + } + if (!mUuid.equals(other.mUuid)) { + return false; + } + if (mVersion != other.mVersion) { + return false; + } + if (!mSupportedModelArch.equals(other.mSupportedModelArch)) { + return false; + } + if (mMaxSoundModels != other.mMaxSoundModels) { + return false; + } + if (mMaxKeyphrases != other.mMaxKeyphrases) { + return false; + } + if (mMaxUsers != other.mMaxUsers) { + return false; + } + if (mRecognitionModes != other.mRecognitionModes) { + return false; + } + if (mSupportsCaptureTransition != other.mSupportsCaptureTransition) { + return false; + } + if (mMaxBufferMillis != other.mMaxBufferMillis) { + return false; + } + if (mSupportsConcurrentCapture != other.mSupportsConcurrentCapture) { + return false; + } + if (mPowerConsumptionMw != other.mPowerConsumptionMw) { + return false; + } + if (mReturnsTriggerInEvent != other.mReturnsTriggerInEvent) { + return false; + } + if (mAudioCapabilities != other.mAudioCapabilities) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mId; + result = prime * result + mImplementor.hashCode(); + result = prime * result + mDescription.hashCode(); + result = prime * result + mUuid.hashCode(); + result = prime * result + mVersion; + result = prime * result + mSupportedModelArch.hashCode(); + result = prime * result + mMaxSoundModels; + result = prime * result + mMaxKeyphrases; + result = prime * result + mMaxUsers; + result = prime * result + mRecognitionModes; + result = prime * result + (mSupportsCaptureTransition ? 1 : 0); + result = prime * result + mMaxBufferMillis; + result = prime * result + (mSupportsConcurrentCapture ? 1 : 0); + result = prime * result + mPowerConsumptionMw; + result = prime * result + (mReturnsTriggerInEvent ? 1 : 0); + result = prime * result + mAudioCapabilities; + return result; + } + + @Override public String toString() { return "ModuleProperties [id=" + getId() + ", implementor=" + getImplementor() + ", description=" + getDescription() + ", uuid=" + getUuid() diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java index cf7ed9b0566d..da7503d01428 100644 --- a/core/java/android/util/AtomicFile.java +++ b/core/java/android/util/AtomicFile.java @@ -29,31 +29,32 @@ import java.io.IOException; import java.util.function.Consumer; /** - * Helper class for performing atomic operations on a file by creating a - * backup file until a write has successfully completed. If you need this - * on older versions of the platform you can use - * {@link android.support.v4.util.AtomicFile} in the v4 support library. + * Helper class for performing atomic operations on a file by writing to a new file and renaming it + * into the place of the original file after the write has successfully completed. If you need this + * on older versions of the platform you can use {@link androidx.core.util.AtomicFile} in AndroidX. * <p> - * Atomic file guarantees file integrity by ensuring that a file has - * been completely written and sync'd to disk before removing its backup. - * As long as the backup file exists, the original file is considered - * to be invalid (left over from a previous attempt to write the file). - * </p><p> - * Atomic file does not confer any file locking semantics. - * Do not use this class when the file may be accessed or modified concurrently - * by multiple threads or processes. The caller is responsible for ensuring - * appropriate mutual exclusion invariants whenever it accesses the file. - * </p> + * Atomic file guarantees file integrity by ensuring that a file has been completely written and + * sync'd to disk before renaming it to the original file. Previously this is done by renaming the + * original file to a backup file beforehand, but this approach couldn't handle the case where the + * file is created for the first time. This class will also handle the backup file created by the + * old implementation properly. + * <p> + * Atomic file does not confer any file locking semantics. Do not use this class when the file may + * be accessed or modified concurrently by multiple threads or processes. The caller is responsible + * for ensuring appropriate mutual exclusion invariants whenever it accesses the file. */ public class AtomicFile { + private static final String LOG_TAG = "AtomicFile"; + private final File mBaseName; - private final File mBackupName; + private final File mNewName; + private final File mLegacyBackupName; private final String mCommitTag; private long mStartTime; /** * Create a new AtomicFile for a file located at the given File path. - * The secondary backup file will be the same file path with ".bak" appended. + * The new file created when writing will be the same file path with ".new" appended. */ public AtomicFile(File baseName) { this(baseName, null); @@ -65,7 +66,8 @@ public class AtomicFile { */ public AtomicFile(File baseName, String commitTag) { mBaseName = baseName; - mBackupName = new File(baseName.getPath() + ".bak"); + mNewName = new File(baseName.getPath() + ".new"); + mLegacyBackupName = new File(baseName.getPath() + ".bak"); mCommitTag = commitTag; } @@ -78,11 +80,12 @@ public class AtomicFile { } /** - * Delete the atomic file. This deletes both the base and backup files. + * Delete the atomic file. This deletes both the base and new files. */ public void delete() { mBaseName.delete(); - mBackupName.delete(); + mNewName.delete(); + mLegacyBackupName.delete(); } /** @@ -112,36 +115,28 @@ public class AtomicFile { public FileOutputStream startWrite(long startTime) throws IOException { mStartTime = startTime; - // Rename the current file so it may be used as a backup during the next read - if (mBaseName.exists()) { - if (!mBackupName.exists()) { - if (!mBaseName.renameTo(mBackupName)) { - Log.w("AtomicFile", "Couldn't rename file " + mBaseName - + " to backup file " + mBackupName); - } - } else { - mBaseName.delete(); + if (mLegacyBackupName.exists()) { + if (!mLegacyBackupName.renameTo(mBaseName)) { + Log.e(LOG_TAG, "Failed to rename legacy backup file " + mLegacyBackupName + + " to base file " + mBaseName); } } - FileOutputStream str = null; + try { - str = new FileOutputStream(mBaseName); + return new FileOutputStream(mNewName); } catch (FileNotFoundException e) { - File parent = mBaseName.getParentFile(); + File parent = mNewName.getParentFile(); if (!parent.mkdirs()) { - throw new IOException("Couldn't create directory " + mBaseName); + throw new IOException("Failed to create directory for " + mNewName); } - FileUtils.setPermissions( - parent.getPath(), - FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, - -1, -1); + FileUtils.setPermissions(parent.getPath(), FileUtils.S_IRWXU | FileUtils.S_IRWXG + | FileUtils.S_IXOTH, -1, -1); try { - str = new FileOutputStream(mBaseName); + return new FileOutputStream(mNewName); } catch (FileNotFoundException e2) { - throw new IOException("Couldn't create " + mBaseName); + throw new IOException("Failed to create new file " + mNewName, e2); } } - return str; } /** @@ -151,36 +146,45 @@ public class AtomicFile { * will return the new file stream. */ public void finishWrite(FileOutputStream str) { - if (str != null) { - FileUtils.sync(str); - try { - str.close(); - mBackupName.delete(); - } catch (IOException e) { - Log.w("AtomicFile", "finishWrite: Got exception:", e); - } - if (mCommitTag != null) { - com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( - mCommitTag, SystemClock.uptimeMillis() - mStartTime); - } + if (str == null) { + return; + } + if (!FileUtils.sync(str)) { + Log.e(LOG_TAG, "Failed to sync file output stream"); + } + try { + str.close(); + } catch (IOException e) { + Log.e(LOG_TAG, "Failed to close file output stream", e); + } + if (!mNewName.renameTo(mBaseName)) { + Log.e(LOG_TAG, "Failed to rename new file " + mNewName + " to base file " + mBaseName); + } + if (mCommitTag != null) { + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + mCommitTag, SystemClock.uptimeMillis() - mStartTime); } } /** * Call when you have failed for some reason at writing to the stream * returned by {@link #startWrite()}. This will close the current - * write stream, and roll back to the previous state of the file. + * write stream, and delete the new file. */ public void failWrite(FileOutputStream str) { - if (str != null) { - FileUtils.sync(str); - try { - str.close(); - mBaseName.delete(); - mBackupName.renameTo(mBaseName); - } catch (IOException e) { - Log.w("AtomicFile", "failWrite: Got exception:", e); - } + if (str == null) { + return; + } + if (!FileUtils.sync(str)) { + Log.e(LOG_TAG, "Failed to sync file output stream"); + } + try { + str.close(); + } catch (IOException e) { + Log.e(LOG_TAG, "Failed to close file output stream", e); + } + if (!mNewName.delete()) { + Log.e(LOG_TAG, "Failed to delete new file " + mNewName); } } @@ -210,32 +214,34 @@ public class AtomicFile { } /** - * Open the atomic file for reading. If there previously was an - * incomplete write, this will roll back to the last good data before - * opening for read. You should call close() on the FileInputStream when - * you are done reading from it. - * - * <p>Note that if another thread is currently performing - * a write, this will incorrectly consider it to be in the state of a bad - * write and roll back, causing the new data currently being written to - * be dropped. You must do your own threading protection for access to - * AtomicFile. + * Open the atomic file for reading. You should call close() on the FileInputStream when you are + * done reading from it. + * <p> + * You must do your own threading protection for access to AtomicFile. */ public FileInputStream openRead() throws FileNotFoundException { - if (mBackupName.exists()) { - mBaseName.delete(); - mBackupName.renameTo(mBaseName); + if (mLegacyBackupName.exists()) { + if (!mLegacyBackupName.renameTo(mBaseName)) { + Log.e(LOG_TAG, "Failed to rename legacy backup file " + mLegacyBackupName + + " to base file " + mBaseName); + } + } + + if (mNewName.exists()) { + if (!mNewName.delete()) { + Log.e(LOG_TAG, "Failed to delete outdated new file " + mNewName); + } } return new FileInputStream(mBaseName); } /** * @hide - * Checks if the original or backup file exists. - * @return whether the original or backup file exists. + * Checks if the original or legacy backup file exists. + * @return whether the original or legacy backup file exists. */ public boolean exists() { - return mBaseName.exists() || mBackupName.exists(); + return mBaseName.exists() || mLegacyBackupName.exists(); } /** @@ -246,8 +252,8 @@ public class AtomicFile { * the file does not exist or an I/O error is encountered. */ public long getLastModifiedTime() { - if (mBackupName.exists()) { - return mBackupName.lastModified(); + if (mLegacyBackupName.exists()) { + return mLegacyBackupName.lastModified(); } return mBaseName.lastModified(); } diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 39c7210d8dac..301ce9f013e4 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -166,6 +166,8 @@ public abstract class ContentCaptureSession implements AutoCloseable { public static final int FLUSH_REASON_IDLE_TIMEOUT = 5; /** @hide */ public static final int FLUSH_REASON_TEXT_CHANGE_TIMEOUT = 6; + /** @hide */ + public static final int FLUSH_REASON_SESSION_CONNECTED = 7; /** @hide */ @IntDef(prefix = { "FLUSH_REASON_" }, value = { @@ -174,7 +176,8 @@ public abstract class ContentCaptureSession implements AutoCloseable { FLUSH_REASON_SESSION_STARTED, FLUSH_REASON_SESSION_FINISHED, FLUSH_REASON_IDLE_TIMEOUT, - FLUSH_REASON_TEXT_CHANGE_TIMEOUT + FLUSH_REASON_TEXT_CHANGE_TIMEOUT, + FLUSH_REASON_SESSION_CONNECTED }) @Retention(RetentionPolicy.SOURCE) public @interface FlushReason{} @@ -609,6 +612,8 @@ public abstract class ContentCaptureSession implements AutoCloseable { return "IDLE"; case FLUSH_REASON_TEXT_CHANGE_TIMEOUT: return "TEXT_CHANGE"; + case FLUSH_REASON_SESSION_CONNECTED: + return "CONNECTED"; default: return "UNKOWN-" + reason; } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 893d38dcfde7..6eb71f747be6 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -273,6 +273,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } else { mState = resultCode; mDisabled.set(false); + // Flush any pending data immediately as buffering forced until now. + flushIfNeeded(FLUSH_REASON_SESSION_CONNECTED); } if (sVerbose) { Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index e224e84a56fe..91b9390745ef 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -1121,6 +1121,7 @@ public abstract class WebSettings { * @deprecated The Application Cache API is deprecated and this method will * become a no-op on all Android versions once support is * removed in Chromium. Consider using Service Workers instead. + * See https://web.dev/appcache-removal/ for more information. */ public abstract void setAppCacheEnabled(boolean flag); @@ -1136,6 +1137,7 @@ public abstract class WebSettings { * @deprecated The Application Cache API is deprecated and this method will * become a no-op on all Android versions once support is * removed in Chromium. Consider using Service Workers instead. + * See https://web.dev/appcache-removal/ for more information. */ public abstract void setAppCachePath(String appCachePath); diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index 2568d097d404..f1b716143787 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -238,8 +238,9 @@ public class ChooserListAdapter extends ResolverListAdapter { } @Override - protected void onBindView(View view, TargetInfo info) { - super.onBindView(view, info); + protected void onBindView(View view, TargetInfo info, int position) { + super.onBindView(view, info, position); + if (info == null) return; // If target is loading, show a special placeholder shape in the label, make unclickable final ViewHolder holder = (ViewHolder) view.getTag(); @@ -257,11 +258,16 @@ public class ChooserListAdapter extends ResolverListAdapter { holder.itemView.setBackground(holder.defaultItemViewBackground); } - // If the target is grouped show an indicator if (info instanceof MultiDisplayResolveInfo) { + // If the target is grouped show an indicator Drawable bkg = mContext.getDrawable(R.drawable.chooser_group_background); holder.text.setPaddingRelative(0, 0, bkg.getIntrinsicWidth() /* end */, 0); holder.text.setBackground(bkg); + } else if (info.isPinned() && getPositionTargetType(position) == TARGET_STANDARD) { + // If the target is pinned and in the suggested row show a pinned indicator + Drawable bkg = mContext.getDrawable(R.drawable.chooser_pinned_background); + holder.text.setPaddingRelative(bkg.getIntrinsicWidth() /* start */, 0, 0, 0); + holder.text.setBackground(bkg); } else { holder.text.setBackground(null); holder.text.setPaddingRelative(0, 0, 0, 0); diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index af9c0408ccaa..d942e859ccd0 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -524,7 +524,7 @@ public class ResolverListAdapter extends BaseAdapter { if (view == null) { view = createView(parent); } - onBindView(view, getItem(position)); + onBindView(view, getItem(position), position); return view; } @@ -541,10 +541,10 @@ public class ResolverListAdapter extends BaseAdapter { } public final void bindView(int position, View view) { - onBindView(view, getItem(position)); + onBindView(view, getItem(position), position); } - protected void onBindView(View view, TargetInfo info) { + protected void onBindView(View view, TargetInfo info, int position) { final ViewHolder holder = (ViewHolder) view.getTag(); if (info == null) { holder.icon.setImageDrawable( diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index a420ba67868f..7b708efdb278 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -652,6 +652,7 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p char dex2oatImageFlagsBuf[PROPERTY_VALUE_MAX]; char extraOptsBuf[PROPERTY_VALUE_MAX]; char voldDecryptBuf[PROPERTY_VALUE_MAX]; + char perfettoHprofOptBuf[sizeof("-XX:PerfettoHprof=") + PROPERTY_VALUE_MAX]; enum { kEMDefault, kEMIntPortable, @@ -766,6 +767,16 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p addOption("-verbose:gc"); //addOption("-verbose:class"); + // On Android, we always want to allow loading the PerfettoHprof plugin. + // Even with this option set, we will still only actually load the plugin + // if we are on a userdebug build or the app is debuggable or profileable. + // This is enforced in art/runtime/runtime.cc. + // + // We want to be able to disable this, because this does not work on host, + // and we do not want to enable it in tests. + parseRuntimeOption("dalvik.vm.perfetto_hprof", perfettoHprofOptBuf, "-XX:PerfettoHprof=", + "true"); + if (primary_zygote) { addOption("-Xprimaryzygote"); } diff --git a/core/proto/android/view/imefocuscontroller.proto b/core/proto/android/view/imefocuscontroller.proto new file mode 100644 index 000000000000..ff9dee69207b --- /dev/null +++ b/core/proto/android/view/imefocuscontroller.proto @@ -0,0 +1,30 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package android.view; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.ImeFocusController} object. + */ +message ImeFocusControllerProto { + optional bool has_ime_focus = 1; + optional string served_view = 2; + optional string next_served_view = 3; +}
\ No newline at end of file diff --git a/core/proto/android/view/imeinsetssourceconsumer.proto b/core/proto/android/view/imeinsetssourceconsumer.proto new file mode 100644 index 000000000000..680916345a31 --- /dev/null +++ b/core/proto/android/view/imeinsetssourceconsumer.proto @@ -0,0 +1,31 @@ +/* + * 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. + */ + +syntax = "proto2"; + +import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto"; + +package android.view; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.ImeInsetsSourceConsumer} object. + */ +message ImeInsetsSourceConsumerProto { + optional .android.view.inputmethod.EditorInfoProto focused_editor = 1; + optional bool is_requested_visible_awaiting_control = 2; +}
\ No newline at end of file diff --git a/core/proto/android/view/inputmethod/editorinfo.proto b/core/proto/android/view/inputmethod/editorinfo.proto new file mode 100644 index 000000000000..f93096f9d395 --- /dev/null +++ b/core/proto/android/view/inputmethod/editorinfo.proto @@ -0,0 +1,33 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package android.view.inputmethod; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.inputmethod.EditorInfo} object. + */ +message EditorInfoProto { + optional int32 input_type = 1; + optional int32 ime_options = 2; + optional string private_ime_options = 3; + optional string package_name = 4; + optional int32 field_id = 5; + optional int32 target_input_method_user_id = 6; +}
\ No newline at end of file diff --git a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto new file mode 100644 index 000000000000..732213966014 --- /dev/null +++ b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto @@ -0,0 +1,69 @@ +/* + * 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. + */ + +syntax = "proto2"; +option java_outer_classname = "InputMethodEditorTraceProto"; + +package android.view.inputmethod; + +import "frameworks/base/core/proto/android/view/inputmethod/inputmethodmanager.proto"; +import "frameworks/base/core/proto/android/view/viewrootimpl.proto"; +import "frameworks/base/core/proto/android/view/insetscontroller.proto"; +import "frameworks/base/core/proto/android/view/insetssourceconsumer.proto"; +import "frameworks/base/core/proto/android/view/imeinsetssourceconsumer.proto"; +import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto"; +import "frameworks/base/core/proto/android/view/imefocuscontroller.proto"; + +/** + * Represents a file full of input method editor trace entries. + * Encoded, it should start with 0x9 0x49 0x4d 0x45 0x54 0x52 0x41 0x43 0x45 (.IMETRACE), such + * that they can be easily identified. + */ +message InputMethodEditorTraceFileProto { + + /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L + (this is needed because enums have to be 32 bits and there's no nice way to put 64bit + constants into .proto files.) */ + enum MagicNumber { + INVALID = 0; + MAGIC_NUMBER_L = 0x54454d49; /* IMET (little-endian ASCII) */ + MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */ + } + + /* Must be the first field to allow winscope to auto-detect the dump type. Set to value + in MagicNumber */ + optional fixed64 magic_number = 1; + repeated InputMethodEditorProto entry = 2; +} + +/* one input method editor dump entry. */ +message InputMethodEditorProto { + + /* required: elapsed realtime in nanos since boot of when this entry was logged */ + optional fixed64 elapsed_realtime_nanos = 1; + optional ClientSideProto client_side_dump = 2; + + /* groups together the dump from ime related client side classes */ + message ClientSideProto { + optional InputMethodManagerProto input_method_manager = 1; + optional ViewRootImplProto view_root_impl = 2; + optional InsetsControllerProto insets_controller = 3; + optional InsetsSourceConsumerProto insets_source_consumer = 4; + optional ImeInsetsSourceConsumerProto ime_insets_source_consumer = 5; + optional EditorInfoProto editor_info = 6; + optional ImeFocusControllerProto ime_focus_controller = 7; + } +}
\ No newline at end of file diff --git a/core/proto/android/view/inputmethod/inputmethodmanager.proto b/core/proto/android/view/inputmethod/inputmethodmanager.proto new file mode 100644 index 000000000000..9fed0ef95a27 --- /dev/null +++ b/core/proto/android/view/inputmethod/inputmethodmanager.proto @@ -0,0 +1,32 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package android.view.inputmethod; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.inputmethod.InputMethodManager} object. + */ +message InputMethodManagerProto { + optional string cur_id = 1; + optional bool fullscreen_mode = 2; + optional int32 display_id = 3; + optional bool active = 4; + optional bool served_connecting = 5; +}
\ No newline at end of file diff --git a/core/proto/android/view/insetsanimationcontrolimpl.proto b/core/proto/android/view/insetsanimationcontrolimpl.proto new file mode 100644 index 000000000000..6eec37b8298e --- /dev/null +++ b/core/proto/android/view/insetsanimationcontrolimpl.proto @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.view; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.InsetsAnimationControlImpl} object. + */ +message InsetsAnimationControlImplProto { + optional bool is_cancelled = 1; + optional bool is_finished = 2; + optional string tmp_matrix = 3; + optional string pending_insets = 4; + optional float pending_fraction = 5; + optional bool shown_on_finish = 6; + optional float current_alpha = 7; + optional float pending_alpha = 8; +}
\ No newline at end of file diff --git a/core/proto/android/view/insetscontroller.proto b/core/proto/android/view/insetscontroller.proto new file mode 100644 index 000000000000..a8bf431ce156 --- /dev/null +++ b/core/proto/android/view/insetscontroller.proto @@ -0,0 +1,32 @@ +/* + * 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. + */ + +syntax = "proto2"; + +import "frameworks/base/core/proto/android/view/insetsstate.proto"; +import "frameworks/base/core/proto/android/view/insetsanimationcontrolimpl.proto"; + +package android.view; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.InsetsController} object. + */ +message InsetsControllerProto { + optional InsetsStateProto state = 1; + repeated InsetsAnimationControlImplProto control = 2; +}
\ No newline at end of file diff --git a/core/proto/android/view/insetssource.proto b/core/proto/android/view/insetssource.proto new file mode 100644 index 000000000000..41b9f432a0ed --- /dev/null +++ b/core/proto/android/view/insetssource.proto @@ -0,0 +1,33 @@ +/* + * 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. + */ + +syntax = "proto2"; + +import "frameworks/base/core/proto/android/graphics/rect.proto"; + +package android.view; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.InsetsSource} object. + */ +message InsetsSourceProto { + optional string type = 1; + optional .android.graphics.RectProto frame = 2; + optional .android.graphics.RectProto visible_frame = 3; + optional bool visible = 4; +}
\ No newline at end of file diff --git a/core/proto/android/view/insetssourceconsumer.proto b/core/proto/android/view/insetssourceconsumer.proto new file mode 100644 index 000000000000..487e06c1ccdf --- /dev/null +++ b/core/proto/android/view/insetssourceconsumer.proto @@ -0,0 +1,36 @@ +/* + * 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. + */ + +syntax = "proto2"; + +import "frameworks/base/core/proto/android/view/insetssourcecontrol.proto"; +import "frameworks/base/core/proto/android/graphics/rect.proto"; + +package android.view; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.InsetsSourceConsumer} object. + */ +message InsetsSourceConsumerProto { + optional string internal_insets_type = 1; + optional bool has_window_focus = 2; + optional bool is_requested_visible = 3; + optional InsetsSourceControlProto source_control = 4; + optional .android.graphics.RectProto pending_frame = 5; + optional .android.graphics.RectProto pending_visible_frame = 6; +}
\ No newline at end of file diff --git a/core/proto/android/view/insetssourcecontrol.proto b/core/proto/android/view/insetssourcecontrol.proto new file mode 100644 index 000000000000..3ac3cbfafaff --- /dev/null +++ b/core/proto/android/view/insetssourcecontrol.proto @@ -0,0 +1,33 @@ +/* + * 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. + */ + +syntax = "proto2"; + +import "frameworks/base/core/proto/android/graphics/point.proto"; +import "frameworks/base/core/proto/android/view/surfacecontrol.proto"; + +package android.view; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.InsetsSourceControl} object. + */ +message InsetsSourceControlProto { + optional string type = 1; + optional .android.graphics.PointProto position = 2; + optional SurfaceControlProto leash = 3; +}
\ No newline at end of file diff --git a/core/proto/android/view/insetsstate.proto b/core/proto/android/view/insetsstate.proto new file mode 100644 index 000000000000..9e9933d72c6c --- /dev/null +++ b/core/proto/android/view/insetsstate.proto @@ -0,0 +1,32 @@ +/* + * 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. + */ + +syntax = "proto2"; + +import "frameworks/base/core/proto/android/view/insetssource.proto"; +import "frameworks/base/core/proto/android/graphics/rect.proto"; + +package android.view; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.InsetsState} object. + */ +message InsetsStateProto { + repeated InsetsSourceProto sources = 1; + optional .android.graphics.RectProto display_frame = 2; +}
\ No newline at end of file diff --git a/core/proto/android/view/viewrootimpl.proto b/core/proto/android/view/viewrootimpl.proto new file mode 100644 index 000000000000..0abe5e0624e3 --- /dev/null +++ b/core/proto/android/view/viewrootimpl.proto @@ -0,0 +1,48 @@ +/* + * 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. + */ + +syntax = "proto2"; + +import "frameworks/base/core/proto/android/graphics/rect.proto"; +import "frameworks/base/core/proto/android/view/displaycutout.proto"; +import "frameworks/base/core/proto/android/view/windowlayoutparams.proto"; + +package android.view; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.ViewRootImpl} object. + */ +message ViewRootImplProto { + optional string view = 1; + optional int32 display_id = 2; + optional bool app_visible = 3; + optional int32 width = 4; + optional int32 height = 5; + optional bool is_animating = 6; + optional .android.graphics.RectProto visible_rect = 7; + optional bool is_drawing = 8; + optional bool added = 9; + optional .android.graphics.RectProto win_frame = 10; + optional DisplayCutoutProto pending_display_cutout = 11; + optional string last_window_insets = 12; + optional string soft_input_mode = 13; + optional int32 scroll_y = 14; + optional int32 cur_scroll_y = 15; + optional bool removed = 16; + optional .android.view.WindowLayoutParamsProto window_attributes = 17; +}
\ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 3cd0f03de727..4bc570ad0adb 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5474,7 +5474,8 @@ android:permission="android.permission.BIND_JOB_SERVICE"> </service> - <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"> + <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader" + android:exported="false"> <intent-filter> <action android:name="android.intent.action.LOAD_DATA" /> </intent-filter> diff --git a/core/res/res/drawable/chooser_group_background.xml b/core/res/res/drawable/chooser_group_background.xml index 2bf9337557ed..036028de7bcb 100644 --- a/core/res/res/drawable/chooser_group_background.xml +++ b/core/res/res/drawable/chooser_group_background.xml @@ -21,5 +21,5 @@ android:width="12dp" android:height="12dp" android:start="4dp" - android:end="4dp" /> + android:end="0dp" /> </layer-list> diff --git a/core/res/res/drawable/chooser_pinned_background.xml b/core/res/res/drawable/chooser_pinned_background.xml new file mode 100644 index 000000000000..fbbe8c107fb9 --- /dev/null +++ b/core/res/res/drawable/chooser_pinned_background.xml @@ -0,0 +1,25 @@ +<?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. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/ic_chooser_pin" + android:gravity="start|center_vertical" + android:width="12dp" + android:height="12dp" + android:start="0dp" + android:end="4dp" /> +</layer-list>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_chooser_pin.xml b/core/res/res/drawable/ic_chooser_pin.xml new file mode 100644 index 000000000000..47851dcbf5bd --- /dev/null +++ b/core/res/res/drawable/ic_chooser_pin.xml @@ -0,0 +1,26 @@ +<?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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="12dp" + android:height="12dp" + android:viewportWidth="12" + android:viewportHeight="12" + android:tint="?attr/textColorSecondary"> + <path + android:pathData="M8.5,2C8.5,1.45 8.055,1 7.5,1L4.5,1C3.95,1 3.5,1.45 3.5,2L3.5,5.5L2.5,7L2.5,8L5.5,8L5.5,10.5L6,11L6.5,10.5L6.5,8L9.5,8L9.5,7L8.5,5.5L8.5,2Z" + android:fillColor="#FF000000" /> +</vector> diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml index fdd965f3b157..50e6f33f628a 100644 --- a/core/res/res/layout/resolve_grid_item.xml +++ b/core/res/res/layout/resolve_grid_item.xml @@ -44,7 +44,7 @@ android:layout_height="wrap_content" android:textAppearance="?attr/textAppearanceSmall" android:textColor="?attr/textColorPrimary" - android:textSize="14sp" + android:textSize="12sp" android:gravity="top|center_horizontal" android:lines="1" android:ellipsize="end" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b62bb33256dc..ac808df7ef5d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -42,8 +42,10 @@ <item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item> <item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item> <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_media</xliff:g></item> <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item> <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item> <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item> <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item> <item><xliff:g id="id">@string/status_bar_camera</xliff:g></item> @@ -59,7 +61,6 @@ <item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item> <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item> <item><xliff:g id="id">@string/status_bar_sensors_off</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item> </string-array> <string translatable="false" name="status_bar_rotate">rotate</string> @@ -96,6 +97,7 @@ <string translatable="false" name="status_bar_airplane">airplane</string> <string translatable="false" name="status_bar_sensors_off">sensors_off</string> <string translatable="false" name="status_bar_screen_record">screen_record</string> + <string translatable="false" name="status_bar_media">media</string> <!-- Flag indicating whether the surface flinger has limited alpha compositing functionality in hardware. If set, the window @@ -3329,6 +3331,17 @@ <!-- Controls the size of the back gesture inset. --> <dimen name="config_backGestureInset">0dp</dimen> + <!-- Array of values used in Gesture Navigation settings page to reduce/increase the back + gesture's inset size. These values will be multiplied into the default width, read from the + gesture navigation overlay package, in order to create 4 different sizes which are selectable + via a slider component. --> + <array name="config_backGestureInsetScales"> + <item>0.75</item> + <item>1.00</item> + <item>1.33</item> + <item>1.66</item> + </array> + <!-- Controls whether the navbar needs a scrim with {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. --> <bool name="config_navBarNeedsScrim">true</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b483ee04a7ec..758a4f7baffc 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2747,6 +2747,8 @@ <java-symbol type="dimen" name="chooser_preview_image_max_dimen"/> <java-symbol type="drawable" name="ic_chooser_group_arrow"/> <java-symbol type="drawable" name="chooser_group_background"/> + <java-symbol type="drawable" name="ic_chooser_pin"/> + <java-symbol type="drawable" name="chooser_pinned_background"/> <java-symbol type="integer" name="config_maxShortcutTargetsPerApp" /> <java-symbol type="layout" name="resolve_grid_item" /> <java-symbol type="id" name="day_picker_view_pager" /> @@ -2816,6 +2818,7 @@ <java-symbol type="bool" name="config_navBarNeedsScrim" /> <java-symbol type="bool" name="config_allowSeamlessRotationDespiteNavBarMoving" /> <java-symbol type="dimen" name="config_backGestureInset" /> + <java-symbol type="array" name="config_backGestureInsetScales" /> <java-symbol type="color" name="system_bar_background_semi_transparent" /> <java-symbol type="bool" name="config_showGesturalNavigationHints" /> @@ -2913,6 +2916,7 @@ <java-symbol type="string" name="status_bar_camera" /> <java-symbol type="string" name="status_bar_sensors_off" /> <java-symbol type="string" name="status_bar_screen_record" /> + <java-symbol type="string" name="status_bar_media" /> <!-- Locale picker --> <java-symbol type="id" name="locale_search_menu" /> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index a93dacf47050..000e870369db 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -20,6 +20,7 @@ import static android.content.Intent.ACTION_EDIT; import static android.content.Intent.ACTION_VIEW; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; @@ -29,6 +30,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.testng.Assert.assertFalse; +import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityThread; import android.app.IApplicationThread; @@ -38,6 +40,7 @@ import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.ConfigurationChangeItem; import android.app.servertransaction.NewIntentItem; import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.StopActivityItem; @@ -225,7 +228,7 @@ public class ActivityThreadTest { } @Test - public void testHandleActivityConfigurationChanged_PickNewerPendingConfiguration() { + public void testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { @@ -237,23 +240,88 @@ public class ActivityThreadTest { final ActivityThread activityThread = activity.getActivityThread(); - final Configuration pendingConfig = new Configuration(); - pendingConfig.orientation = orientation == ORIENTATION_LANDSCAPE - ? ORIENTATION_PORTRAIT - : ORIENTATION_LANDSCAPE; - pendingConfig.seq = seq + 2; + final Configuration newerConfig = new Configuration(); + newerConfig.orientation = orientation == ORIENTATION_LANDSCAPE + ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; + newerConfig.seq = seq + 2; activityThread.updatePendingActivityConfiguration(activity.getActivityToken(), - pendingConfig); + newerConfig); - final Configuration newConfig = new Configuration(); - newConfig.orientation = orientation; - newConfig.seq = seq + 1; + final Configuration olderConfig = new Configuration(); + olderConfig.orientation = orientation; + olderConfig.seq = seq + 1; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), - newConfig, Display.INVALID_DISPLAY); + olderConfig, INVALID_DISPLAY); + assertEquals(numOfConfig, activity.mNumOfConfigChanges); + assertEquals(olderConfig.orientation, activity.mConfig.orientation); + + activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), + newerConfig, INVALID_DISPLAY); assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); - assertEquals(pendingConfig.orientation, activity.mConfig.orientation); + assertEquals(newerConfig.orientation, activity.mConfig.orientation); + }); + } + + @Test + public void testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder() + throws Exception { + final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); + + final ActivityThread activityThread = activity.getActivityThread(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + final Configuration config = new Configuration(); + config.seq = BASE_SEQ; + config.orientation = ORIENTATION_PORTRAIT; + + activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), + config, INVALID_DISPLAY); }); + + final IApplicationThread appThread = activityThread.getApplicationThread(); + final int numOfConfig = activity.mNumOfConfigChanges; + + final Configuration processConfigLandscape = new Configuration(); + processConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 60)); + processConfigLandscape.seq = BASE_SEQ + 1; + + final Configuration activityConfigLandscape = new Configuration(); + activityConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 50)); + activityConfigLandscape.seq = BASE_SEQ + 2; + + final Configuration processConfigPortrait = new Configuration(); + processConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 60, 100)); + processConfigPortrait.seq = BASE_SEQ + 3; + + final Configuration activityConfigPortrait = new Configuration(); + activityConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 50, 100)); + activityConfigPortrait.seq = BASE_SEQ + 4; + + activity.mConfigLatch = new CountDownLatch(1); + activity.mTestLatch = new CountDownLatch(1); + + ClientTransaction transaction = newTransaction(activityThread, null); + transaction.addCallback(ConfigurationChangeItem.obtain(processConfigLandscape)); + appThread.scheduleTransaction(transaction); + + transaction = newTransaction(activityThread, activity.getActivityToken()); + transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigLandscape)); + transaction.addCallback(ConfigurationChangeItem.obtain(processConfigPortrait)); + transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigPortrait)); + appThread.scheduleTransaction(transaction); + + activity.mTestLatch.await(); + activity.mConfigLatch.countDown(); + + activity.mConfigLatch = null; + activity.mTestLatch = null; + + // Check display metrics, bounds should match the portrait activity bounds. + final Rect bounds = activity.getWindowManager().getCurrentWindowMetrics().getBounds(); + assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds); + + // Ensure that Activity#onConfigurationChanged() is only called once. + assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); } @Test @@ -268,7 +336,7 @@ public class ActivityThreadTest { config.orientation = ORIENTATION_PORTRAIT; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), - config, Display.INVALID_DISPLAY); + config, INVALID_DISPLAY); }); final int numOfConfig = activity.mNumOfConfigChanges; @@ -504,7 +572,7 @@ public class ActivityThreadTest { config.orientation = ORIENTATION_PORTRAIT; config.seq = seq; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config, - Display.INVALID_DISPLAY); + INVALID_DISPLAY); if (activity.mNumOfConfigChanges > numOfConfig) { return config.seq; @@ -514,7 +582,7 @@ public class ActivityThreadTest { config.orientation = ORIENTATION_LANDSCAPE; config.seq = seq + 1; activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config, - Display.INVALID_DISPLAY); + INVALID_DISPLAY); return config.seq; } @@ -572,8 +640,12 @@ public class ActivityThreadTest { } private static ClientTransaction newTransaction(Activity activity) { - final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); - return ClientTransaction.obtain(appThread, activity.getActivityToken()); + return newTransaction(activity.getActivityThread(), activity.getActivityToken()); + } + + private static ClientTransaction newTransaction(ActivityThread activityThread, + @Nullable IBinder activityToken) { + return ClientTransaction.obtain(activityThread.getApplicationThread(), activityToken); } // Test activity diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 6c23125aaf13..4654f63a2a91 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -63,7 +63,8 @@ public class ObjectPoolTests { @Test public void testRecycleActivityConfigurationChangeItem() { - ActivityConfigurationChangeItem emptyItem = ActivityConfigurationChangeItem.obtain(null); + ActivityConfigurationChangeItem emptyItem = + ActivityConfigurationChangeItem.obtain(Configuration.EMPTY); ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config()); assertNotSame(item, emptyItem); assertFalse(item.equals(emptyItem)); @@ -186,7 +187,7 @@ public class ObjectPoolTests { @Test public void testRecycleMoveToDisplayItem() { - MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(0, null); + MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(0, Configuration.EMPTY); MoveToDisplayItem item = MoveToDisplayItem.obtain(4, config()); assertNotSame(item, emptyItem); assertFalse(item.equals(emptyItem)); diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 3f8d9ef964db..f11adef81793 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -563,6 +563,11 @@ public class TransactionParcelTests { } @Override + public void dumpCacheInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings) + throws RemoteException { + } + + @Override public void dumpProvider(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder, String[] strings) throws RemoteException { } diff --git a/core/tests/utiltests/src/android/util/AtomicFileTest.java b/core/tests/utiltests/src/android/util/AtomicFileTest.java new file mode 100644 index 000000000000..547d4d844181 --- /dev/null +++ b/core/tests/utiltests/src/android/util/AtomicFileTest.java @@ -0,0 +1,245 @@ +/* + * 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.util; + +import static org.junit.Assert.assertArrayEquals; + +import android.app.Instrumentation; +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +@RunWith(Parameterized.class) +public class AtomicFileTest { + private static final String BASE_NAME = "base"; + private static final String NEW_NAME = BASE_NAME + ".new"; + private static final String LEGACY_BACKUP_NAME = BASE_NAME + ".bak"; + + private enum WriteAction { + FINISH, + FAIL, + ABORT + } + + private static final byte[] BASE_BYTES = "base".getBytes(StandardCharsets.UTF_8); + private static final byte[] EXISTING_NEW_BYTES = "unnew".getBytes(StandardCharsets.UTF_8); + private static final byte[] NEW_BYTES = "new".getBytes(StandardCharsets.UTF_8); + private static final byte[] LEGACY_BACKUP_BYTES = "bak".getBytes(StandardCharsets.UTF_8); + + // JUnit wants every parameter to be used so make it happy. + @Parameterized.Parameter() + public String mUnusedTestName; + @Nullable + @Parameterized.Parameter(1) + public String[] mExistingFileNames; + @Nullable + @Parameterized.Parameter(2) + public WriteAction mWriteAction; + @Nullable + @Parameterized.Parameter(3) + public byte[] mExpectedBytes; + + private final Instrumentation mInstrumentation = + InstrumentationRegistry.getInstrumentation(); + private final Context mContext = mInstrumentation.getContext(); + + private final File mDirectory = mContext.getFilesDir(); + private final File mBaseFile = new File(mDirectory, BASE_NAME); + private final File mNewFile = new File(mDirectory, NEW_NAME); + private final File mLegacyBackupFile = new File(mDirectory, LEGACY_BACKUP_NAME); + + @Parameterized.Parameters(name = "{0}") + public static Object[][] data() { + return new Object[][] { + { "none + none = none", null, null, null }, + { "none + finish = new", null, WriteAction.FINISH, NEW_BYTES }, + { "none + fail = none", null, WriteAction.FAIL, null }, + { "none + abort = none", null, WriteAction.ABORT, null }, + { "base + none = base", new String[] { BASE_NAME }, null, BASE_BYTES }, + { "base + finish = new", new String[] { BASE_NAME }, WriteAction.FINISH, + NEW_BYTES }, + { "base + fail = base", new String[] { BASE_NAME }, WriteAction.FAIL, BASE_BYTES }, + { "base + abort = base", new String[] { BASE_NAME }, WriteAction.ABORT, + BASE_BYTES }, + { "new + none = none", new String[] { NEW_NAME }, null, null }, + { "new + finish = new", new String[] { NEW_NAME }, WriteAction.FINISH, NEW_BYTES }, + { "new + fail = none", new String[] { NEW_NAME }, WriteAction.FAIL, null }, + { "new + abort = none", new String[] { NEW_NAME }, WriteAction.ABORT, null }, + { "bak + none = bak", new String[] { LEGACY_BACKUP_NAME }, null, + LEGACY_BACKUP_BYTES }, + { "bak + finish = new", new String[] { LEGACY_BACKUP_NAME }, WriteAction.FINISH, + NEW_BYTES }, + { "bak + fail = bak", new String[] { LEGACY_BACKUP_NAME }, WriteAction.FAIL, + LEGACY_BACKUP_BYTES }, + { "bak + abort = bak", new String[] { LEGACY_BACKUP_NAME }, WriteAction.ABORT, + LEGACY_BACKUP_BYTES }, + { "base & new + none = base", new String[] { BASE_NAME, NEW_NAME }, null, + BASE_BYTES }, + { "base & new + finish = new", new String[] { BASE_NAME, NEW_NAME }, + WriteAction.FINISH, NEW_BYTES }, + { "base & new + fail = base", new String[] { BASE_NAME, NEW_NAME }, + WriteAction.FAIL, BASE_BYTES }, + { "base & new + abort = base", new String[] { BASE_NAME, NEW_NAME }, + WriteAction.ABORT, BASE_BYTES }, + { "base & bak + none = bak", new String[] { BASE_NAME, LEGACY_BACKUP_NAME }, null, + LEGACY_BACKUP_BYTES }, + { "base & bak + finish = new", new String[] { BASE_NAME, LEGACY_BACKUP_NAME }, + WriteAction.FINISH, NEW_BYTES }, + { "base & bak + fail = bak", new String[] { BASE_NAME, LEGACY_BACKUP_NAME }, + WriteAction.FAIL, LEGACY_BACKUP_BYTES }, + { "base & bak + abort = bak", new String[] { BASE_NAME, LEGACY_BACKUP_NAME }, + WriteAction.ABORT, LEGACY_BACKUP_BYTES }, + { "new & bak + none = bak", new String[] { NEW_NAME, LEGACY_BACKUP_NAME }, null, + LEGACY_BACKUP_BYTES }, + { "new & bak + finish = new", new String[] { NEW_NAME, LEGACY_BACKUP_NAME }, + WriteAction.FINISH, NEW_BYTES }, + { "new & bak + fail = bak", new String[] { NEW_NAME, LEGACY_BACKUP_NAME }, + WriteAction.FAIL, LEGACY_BACKUP_BYTES }, + { "new & bak + abort = bak", new String[] { NEW_NAME, LEGACY_BACKUP_NAME }, + WriteAction.ABORT, LEGACY_BACKUP_BYTES }, + { "base & new & bak + none = bak", + new String[] { BASE_NAME, NEW_NAME, LEGACY_BACKUP_NAME }, null, + LEGACY_BACKUP_BYTES }, + { "base & new & bak + finish = new", + new String[] { BASE_NAME, NEW_NAME, LEGACY_BACKUP_NAME }, + WriteAction.FINISH, NEW_BYTES }, + { "base & new & bak + fail = bak", + new String[] { BASE_NAME, NEW_NAME, LEGACY_BACKUP_NAME }, WriteAction.FAIL, + LEGACY_BACKUP_BYTES }, + { "base & new & bak + abort = bak", + new String[] { BASE_NAME, NEW_NAME, LEGACY_BACKUP_NAME }, WriteAction.ABORT, + LEGACY_BACKUP_BYTES }, + }; + } + + @Before + @After + public void deleteFiles() { + mBaseFile.delete(); + mNewFile.delete(); + mLegacyBackupFile.delete(); + } + + @Test + public void testAtomicFile() throws Exception { + if (mExistingFileNames != null) { + for (String fileName : mExistingFileNames) { + switch (fileName) { + case BASE_NAME: + writeBytes(mBaseFile, BASE_BYTES); + break; + case NEW_NAME: + writeBytes(mNewFile, EXISTING_NEW_BYTES); + break; + case LEGACY_BACKUP_NAME: + writeBytes(mLegacyBackupFile, LEGACY_BACKUP_BYTES); + break; + default: + throw new AssertionError(fileName); + } + } + } + + AtomicFile atomicFile = new AtomicFile(mBaseFile); + if (mWriteAction != null) { + try (FileOutputStream outputStream = atomicFile.startWrite()) { + outputStream.write(NEW_BYTES); + switch (mWriteAction) { + case FINISH: + atomicFile.finishWrite(outputStream); + break; + case FAIL: + atomicFile.failWrite(outputStream); + break; + case ABORT: + // Neither finishing nor failing is called upon abort. + break; + default: + throw new AssertionError(mWriteAction); + } + } + } + + if (mExpectedBytes != null) { + try (FileInputStream inputStream = atomicFile.openRead()) { + assertArrayEquals(mExpectedBytes, readAllBytes(inputStream)); + } + } else { + assertThrows(FileNotFoundException.class, () -> atomicFile.openRead()); + } + } + + private static void writeBytes(@NonNull File file, @NonNull byte[] bytes) throws IOException { + try (FileOutputStream outputStream = new FileOutputStream(file)) { + outputStream.write(bytes); + } + } + + // InputStream.readAllBytes() is introduced in Java 9. Our files are small enough so that a + // naive implementation is okay. + private static byte[] readAllBytes(@NonNull InputStream inputStream) throws IOException { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + int b; + while ((b = inputStream.read()) != -1) { + outputStream.write(b); + } + return outputStream.toByteArray(); + } + } + + @NonNull + public static <T extends Throwable> T assertThrows(@NonNull Class<T> expectedType, + @NonNull ThrowingRunnable runnable) { + try { + runnable.run(); + } catch (Throwable t) { + if (!expectedType.isInstance(t)) { + sneakyThrow(t); + } + //noinspection unchecked + return (T) t; + } + throw new AssertionError(String.format("Expected %s wasn't thrown", + expectedType.getSimpleName())); + } + + private static <T extends Throwable> void sneakyThrow(@NonNull Throwable throwable) throws T { + //noinspection unchecked + throw (T) throwable; + } + + private interface ThrowingRunnable { + void run() throws Throwable; + } +} diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index 72827a956f78..a5a2221e5532 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -16,6 +16,7 @@ --> <permissions> <privapp-permissions package="com.android.systemui"> + <permission name="android.permission.CAPTURE_AUDIO_OUTPUT"/> <permission name="android.permission.BATTERY_STATS"/> <permission name="android.permission.BIND_APPWIDGET"/> <permission name="android.permission.BLUETOOTH_PRIVILEGED"/> diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java index 1275cb9ca4f9..eb940e2f9017 100644 --- a/graphics/java/android/graphics/PorterDuff.java +++ b/graphics/java/android/graphics/PorterDuff.java @@ -30,8 +30,6 @@ public class PorterDuff { /** * {@usesMathJax} * - * <h3>Porter-Duff</h3> - * * <p>The name of the parent class is an homage to the work of Thomas Porter and * Tom Duff, presented in their seminal 1984 paper titled "Compositing Digital Images". * In this paper, the authors describe 12 compositing operators that govern how to diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java index 357c3332fe10..b44d7bba834f 100644 --- a/media/java/android/media/AudioManagerInternal.java +++ b/media/java/android/media/AudioManagerInternal.java @@ -29,13 +29,13 @@ import com.android.server.LocalServices; public abstract class AudioManagerInternal { public abstract void adjustSuggestedStreamVolumeForUid(int streamType, int direction, - int flags, String callingPackage, int uid); + int flags, String callingPackage, int uid, int pid); public abstract void adjustStreamVolumeForUid(int streamType, int direction, int flags, - String callingPackage, int uid); + String callingPackage, int uid, int pid); public abstract void setStreamVolumeForUid(int streamType, int direction, int flags, - String callingPackage, int uid); + String callingPackage, int uid, int pid); public abstract void setRingerModeDelegate(RingerModeDelegate delegate); diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java index bbd739941422..54675d018038 100644 --- a/media/java/android/media/MediaMuxer.java +++ b/media/java/android/media/MediaMuxer.java @@ -321,6 +321,21 @@ final public class MediaMuxer { @UnsupportedAppUsage private long mNativeObject; + private String convertMuxerStateCodeToString(int aState) { + switch (aState) { + case MUXER_STATE_UNINITIALIZED: + return "UNINITIALIZED"; + case MUXER_STATE_INITIALIZED: + return "INITIALIZED"; + case MUXER_STATE_STARTED: + return "STARTED"; + case MUXER_STATE_STOPPED: + return "STOPPED"; + default: + return "UNKNOWN"; + } + } + /** * Constructor. * Creates a media muxer that writes to the specified path. @@ -397,7 +412,7 @@ final public class MediaMuxer { nativeSetOrientationHint(mNativeObject, degrees); } else { throw new IllegalStateException("Can't set rotation degrees due" + - " to wrong state."); + " to wrong state(" + convertMuxerStateCodeToString(mState) + ")"); } } @@ -432,7 +447,8 @@ final public class MediaMuxer { if (mState == MUXER_STATE_INITIALIZED && mNativeObject != 0) { nativeSetLocation(mNativeObject, latitudex10000, longitudex10000); } else { - throw new IllegalStateException("Can't set location due to wrong state."); + throw new IllegalStateException("Can't set location due to wrong state(" + + convertMuxerStateCodeToString(mState) + ")"); } } @@ -451,7 +467,8 @@ final public class MediaMuxer { nativeStart(mNativeObject); mState = MUXER_STATE_STARTED; } else { - throw new IllegalStateException("Can't start due to wrong state."); + throw new IllegalStateException("Can't start due to wrong state(" + + convertMuxerStateCodeToString(mState) + ")"); } } @@ -462,10 +479,16 @@ final public class MediaMuxer { */ public void stop() { if (mState == MUXER_STATE_STARTED) { - nativeStop(mNativeObject); - mState = MUXER_STATE_STOPPED; + try { + nativeStop(mNativeObject); + } catch (Exception e) { + throw e; + } finally { + mState = MUXER_STATE_STOPPED; + } } else { - throw new IllegalStateException("Can't stop due to wrong state."); + throw new IllegalStateException("Can't stop due to wrong state(" + + convertMuxerStateCodeToString(mState) + ")"); } } diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 50af60a0ad92..a458b16c551a 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.app.ActivityManager; import android.content.Context; import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.TvInputService; @@ -55,6 +56,8 @@ import android.os.Looper; import android.os.Message; import android.util.Log; +import com.android.internal.util.FrameworkStatsLog; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -208,7 +211,7 @@ public class Tuner implements AutoCloseable { private FrontendInfo mFrontendInfo; private Integer mFrontendHandle; private int mFrontendType = FrontendSettings.TYPE_UNDEFINED; - + private int mUserId; private Lnb mLnb; private Integer mLnbHandle; @Nullable @@ -232,6 +235,11 @@ public class Tuner implements AutoCloseable { new TunerResourceManager.ResourcesReclaimListener() { @Override public void onReclaimResources() { + if (mFrontend != null) { + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN); + } mHandler.sendMessage(mHandler.obtainMessage(MSG_RESOURCE_LOST)); } }; @@ -261,6 +269,8 @@ public class Tuner implements AutoCloseable { profile, new HandlerExecutor(mHandler), mResourceListener, clientId); mClientId = clientId[0]; + mUserId = ActivityManager.getCurrentUser(); + setFrontendInfoList(); setLnbIds(); } @@ -358,6 +368,9 @@ public class Tuner implements AutoCloseable { TunerUtils.throwExceptionForResult(res, "failed to close frontend"); } mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN); mFrontendHandle = null; mFrontend = null; } @@ -557,9 +570,14 @@ public class Tuner implements AutoCloseable { */ @Result public int tune(@NonNull FrontendSettings settings) { + Log.d(TAG, "Tune to " + settings.getFrequency()); mFrontendType = settings.getType(); if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) { mFrontendInfo = null; + Log.d(TAG, "Write Stats Log for tuning."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING); return nativeTune(settings.getType(), settings); } return RESULT_UNAVAILABLE; @@ -602,6 +620,9 @@ public class Tuner implements AutoCloseable { mScanCallback = scanCallback; mScanCallbackExecutor = executor; mFrontendInfo = null; + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING); return nativeScan(settings.getType(), settings, scanType); } return RESULT_UNAVAILABLE; @@ -620,6 +641,10 @@ public class Tuner implements AutoCloseable { */ @Result public int cancelScanning() { + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED); + int retVal = nativeStopScan(); mScanCallback = null; mScanCallbackExecutor = null; @@ -779,12 +804,33 @@ public class Tuner implements AutoCloseable { } private void onFrontendEvent(int eventType) { + Log.d(TAG, "Got event from tuning. Event type: " + eventType); if (mOnTunerEventExecutor != null && mOnTuneEventListener != null) { mOnTunerEventExecutor.execute(() -> mOnTuneEventListener.onTuneEvent(eventType)); } + + Log.d(TAG, "Wrote Stats Log for the events from tuning."); + if (eventType == OnTuneEventListener.SIGNAL_LOCKED) { + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED); + } else if (eventType == OnTuneEventListener.SIGNAL_NO_SIGNAL) { + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__NOT_LOCKED); + } else if (eventType == OnTuneEventListener.SIGNAL_LOST_LOCK) { + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SIGNAL_LOST); + } } private void onLocked() { + Log.d(TAG, "Wrote Stats Log for locked event from scanning."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED); + if (mScanCallbackExecutor != null && mScanCallback != null) { mScanCallbackExecutor.execute(() -> mScanCallback.onLocked()); } diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 331477f72aa4..43cb25f03fb0 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -43,7 +43,7 @@ #include <android_runtime/android_hardware_HardwareBuffer.h> -#include <binder/MemoryHeapBase.h> +#include <binder/MemoryDealer.h> #include <cutils/compiler.h> @@ -306,6 +306,7 @@ status_t JMediaCodec::configure( CHECK(format->findString("mime", &mime)); mGraphicOutput = (mime.startsWithIgnoreCase("video/") || mime.startsWithIgnoreCase("image/")) && !(flags & CONFIGURE_FLAG_ENCODE); + mHasCryptoOrDescrambler = (crypto != nullptr) || (descrambler != nullptr); return mCodec->configure( format, mSurfaceTextureClient, crypto, descrambler, flags); @@ -1603,14 +1604,13 @@ struct NativeCryptoInfo { ScopedLocalRef<jobject> patternObj{ env, env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID)}; - CryptoPlugin::Pattern pattern; if (patternObj.get() == nullptr) { - pattern.mEncryptBlocks = 0; - pattern.mSkipBlocks = 0; + mPattern.mEncryptBlocks = 0; + mPattern.mSkipBlocks = 0; } else { - pattern.mEncryptBlocks = env->GetIntField( + mPattern.mEncryptBlocks = env->GetIntField( patternObj.get(), gFields.patternEncryptBlocksID); - pattern.mSkipBlocks = env->GetIntField( + mPattern.mSkipBlocks = env->GetIntField( patternObj.get(), gFields.patternSkipBlocksID); } @@ -1679,6 +1679,18 @@ struct NativeCryptoInfo { mIv = env->GetByteArrayElements(mIvObj.get(), nullptr); } } + + } + + explicit NativeCryptoInfo(jint size) + : mIvObj{nullptr, nullptr}, + mKeyObj{nullptr, nullptr}, + mMode{CryptoPlugin::kMode_Unencrypted}, + mPattern{0, 0} { + mSubSamples = new CryptoPlugin::SubSample[1]; + mNumSubSamples = 1; + mSubSamples[0].mNumBytesOfClearData = size; + mSubSamples[0].mNumBytesOfEncryptedData = 0; } ~NativeCryptoInfo() { @@ -2128,10 +2140,13 @@ static void android_media_MediaCodec_native_queueLinearBlock( if (env->GetBooleanField(bufferObj, gLinearBlockInfo.validId)) { JMediaCodecLinearBlock *context = (JMediaCodecLinearBlock *)env->GetLongField(bufferObj, gLinearBlockInfo.contextId); - if (cryptoInfoObj != nullptr) { + if (codec->hasCryptoOrDescrambler()) { memory = context->toHidlMemory(); + // TODO: copy if memory is null + offset += context->mHidlMemoryOffset; } else { buffer = context->toC2Buffer(offset, size); + // TODO: copy if buffer is null } } env->MonitorExit(lock.get()); @@ -2141,13 +2156,19 @@ static void android_media_MediaCodec_native_queueLinearBlock( } AString errorDetailMsg; - if (cryptoInfoObj != nullptr) { + if (codec->hasCryptoOrDescrambler()) { if (!memory) { + ALOGI("queueLinearBlock: no ashmem memory for encrypted content"); throwExceptionAsNecessary(env, BAD_VALUE); return; } - - NativeCryptoInfo cryptoInfo{env, cryptoInfoObj}; + NativeCryptoInfo cryptoInfo = [env, cryptoInfoObj, size]{ + if (cryptoInfoObj == nullptr) { + return NativeCryptoInfo{size}; + } else { + return NativeCryptoInfo{env, cryptoInfoObj}; + } + }(); err = codec->queueEncryptedLinearBlock( index, memory, @@ -2162,6 +2183,7 @@ static void android_media_MediaCodec_native_queueLinearBlock( &errorDetailMsg); } else { if (!buffer) { + ALOGI("queueLinearBlock: no C2Buffer found"); throwExceptionAsNecessary(env, BAD_VALUE); return; } @@ -2955,13 +2977,13 @@ static jobject android_media_MediaCodec_LinearBlock_native_map( context->mLegacyBuffer->size(), true, // readOnly true /* clearBuffer */); - } else if (context->mHeap) { + } else if (context->mMemory) { return CreateByteBuffer( env, - static_cast<uint8_t *>(context->mHeap->getBase()) + context->mHeap->getOffset(), - context->mHeap->getSize(), + context->mMemory->unsecurePointer(), + context->mMemory->size(), 0, - context->mHeap->getSize(), + context->mMemory->size(), false, // readOnly true /* clearBuffer */); } @@ -3011,8 +3033,26 @@ static void android_media_MediaCodec_LinearBlock_native_obtain( } } if (hasSecure && !hasNonSecure) { - context->mHeap = new MemoryHeapBase(capacity); - context->mMemory = hardware::fromHeap(context->mHeap); + constexpr size_t kInitialDealerCapacity = 1048576; // 1MB + thread_local sp<MemoryDealer> sDealer = new MemoryDealer( + kInitialDealerCapacity, "JNI(1MB)"); + context->mMemory = sDealer->allocate(capacity); + if (context->mMemory == nullptr) { + size_t newDealerCapacity = sDealer->getMemoryHeap()->getSize() * 2; + while (capacity * 2 > newDealerCapacity) { + newDealerCapacity *= 2; + } + ALOGI("LinearBlock.native_obtain: " + "Dealer capacity increasing from %zuMB to %zuMB", + sDealer->getMemoryHeap()->getSize() / 1048576, + newDealerCapacity / 1048576); + sDealer = new MemoryDealer( + newDealerCapacity, + AStringPrintf("JNI(%zuMB)", newDealerCapacity).c_str()); + context->mMemory = sDealer->allocate(capacity); + } + context->mHidlMemory = hardware::fromHeap(context->mMemory->getMemory( + &context->mHidlMemoryOffset, &context->mHidlMemorySize)); } else { context->mBlock = MediaCodec::FetchLinearBlock(capacity, names); if (!context->mBlock) { diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 400ce1bc98e7..5c34341a86a1 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -162,6 +162,8 @@ struct JMediaCodec : public AHandler { void selectAudioPresentation(const int32_t presentationId, const int32_t programId); + bool hasCryptoOrDescrambler() { return mHasCryptoOrDescrambler; } + protected: virtual ~JMediaCodec(); @@ -181,6 +183,7 @@ private: sp<MediaCodec> mCodec; AString mNameAtCreation; bool mGraphicOutput{false}; + bool mHasCryptoOrDescrambler{false}; std::once_flag mReleaseFlag; sp<AMessage> mCallbackNotification; diff --git a/media/jni/android_media_MediaCodecLinearBlock.h b/media/jni/android_media_MediaCodecLinearBlock.h index 08438340d803..8f1d2fa35d70 100644 --- a/media/jni/android_media_MediaCodecLinearBlock.h +++ b/media/jni/android_media_MediaCodecLinearBlock.h @@ -31,8 +31,10 @@ struct JMediaCodecLinearBlock { std::shared_ptr<C2LinearBlock> mBlock; std::shared_ptr<C2WriteView> mReadWriteMapping; - sp<IMemoryHeap> mHeap; - sp<hardware::HidlMemory> mMemory; + sp<IMemory> mMemory; + sp<hardware::HidlMemory> mHidlMemory; + ssize_t mHidlMemoryOffset; + size_t mHidlMemorySize; sp<MediaCodecBuffer> mLegacyBuffer; @@ -56,8 +58,8 @@ struct JMediaCodecLinearBlock { } sp<hardware::HidlMemory> toHidlMemory() { - if (mMemory) { - return mMemory; + if (mHidlMemory) { + return mHidlMemory; } return nullptr; } @@ -65,4 +67,4 @@ struct JMediaCodecLinearBlock { } // namespace android -#endif // _ANDROID_MEDIA_MEDIACODECLINEARBLOCK_H_
\ No newline at end of file +#endif // _ANDROID_MEDIA_MEDIACODECLINEARBLOCK_H_ diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp index 0c1e9a2ad7bd..262ec76da26e 100644 --- a/media/jni/android_media_MediaMuxer.cpp +++ b/media/jni/android_media_MediaMuxer.cpp @@ -26,15 +26,11 @@ #include <unistd.h> #include <fcntl.h> -#include <android/api-level.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> -#include <media/stagefright/MediaErrors.h> #include <media/stagefright/MediaMuxer.h> -extern "C" int android_get_application_target_sdk_version(); - namespace android { struct fields_t { @@ -233,31 +229,11 @@ static void android_media_MediaMuxer_stop(JNIEnv *env, jclass /* clazz */, status_t err = muxer->stop(); - if (android_get_application_target_sdk_version() >= __ANDROID_API_R__) { - switch (err) { - case OK: - break; - case ERROR_IO: { - jniThrowException(env, "java/lang/UncheckedIOException", - "Muxer stopped unexpectedly"); - return; - } - case ERROR_MALFORMED: { - jniThrowException(env, "java/io/IOError", - "Failure of reading or writing operation"); - return; - } - default: { - jniThrowException(env, "java/lang/IllegalStateException", - "Failed to stop the muxer"); - return; - } - } - } else { - if (err != OK) { - jniThrowException(env, "java/lang/IllegalStateException", "Failed to stop the muxer"); - return; - } + if (err != OK) { + ALOGE("Error during stop:%d", err); + jniThrowException(env, "java/lang/IllegalStateException", + "Error during stop(), muxer would have stopped already"); + return; } } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index e42e43801936..3dbf5a4d2b6b 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1376,4 +1376,7 @@ <!-- A content description for work profile app [CHAR LIMIT=35] --> <string name="accessibility_work_profile_app_description">Work <xliff:g id="app_name" example="Camera">%s</xliff:g></string> + + <!-- Name of the 3.5mm and usb audio device. [CHAR LIMIT=40] --> + <string name="media_transfer_wired_usb_device_name">Wired headphone</string> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 9e8c70fcc000..197e34df22a8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -145,15 +145,16 @@ public class LocalMediaManager implements BluetoothCallback { /** * Connect the MediaDevice to transfer media * @param connectDevice the MediaDevice + * @return {@code true} if successfully call, otherwise return {@code false} */ - public void connectDevice(MediaDevice connectDevice) { + public boolean connectDevice(MediaDevice connectDevice) { MediaDevice device = null; synchronized (mMediaDevicesLock) { device = getMediaDeviceById(mMediaDevices, connectDevice.getId()); } if (device == null) { Log.w(TAG, "connectDevice() connectDevice not in the list!"); - return; + return false; } if (device instanceof BluetoothMediaDevice) { final CachedBluetoothDevice cachedDevice = @@ -162,13 +163,13 @@ public class LocalMediaManager implements BluetoothCallback { mOnTransferBluetoothDevice = connectDevice; device.setState(MediaDeviceState.STATE_CONNECTING); cachedDevice.connect(); - return; + return true; } } if (device == mCurrentConnectedDevice) { Log.d(TAG, "connectDevice() this device all ready connected! : " + device.getName()); - return; + return false; } if (mCurrentConnectedDevice != null) { @@ -181,6 +182,7 @@ public class LocalMediaManager implements BluetoothCallback { } else { device.connect(); } + return true; } void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 8ea5ff1bf662..9a3ae1be14c0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -61,11 +61,11 @@ public class PhoneMediaDevice extends MediaDevice { switch (mRouteInfo.getType()) { case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: - name = mContext.getString(R.string.media_transfer_wired_device_name); - break; case TYPE_USB_DEVICE: case TYPE_USB_HEADSET: case TYPE_USB_ACCESSORY: + name = mContext.getString(R.string.media_transfer_wired_usb_device_name); + break; case TYPE_DOCK: case TYPE_HDMI: name = mRouteInfo.getName(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index 365a16c50b99..517071b5511f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -141,7 +141,7 @@ public class LocalMediaManagerTest { when(currentDevice.getId()).thenReturn(TEST_CURRENT_DEVICE_ID); mLocalMediaManager.registerCallback(mCallback); - mLocalMediaManager.connectDevice(device); + assertThat(mLocalMediaManager.connectDevice(device)).isTrue(); verify(currentDevice).disconnect(); verify(device).connect(); @@ -154,7 +154,7 @@ public class LocalMediaManagerTest { mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1; mLocalMediaManager.registerCallback(mCallback); - mLocalMediaManager.connectDevice(mInfoMediaDevice2); + assertThat(mLocalMediaManager.connectDevice(mInfoMediaDevice2)).isTrue(); assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState .STATE_CONNECTING); @@ -167,7 +167,7 @@ public class LocalMediaManagerTest { mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1; mLocalMediaManager.registerCallback(mCallback); - mLocalMediaManager.connectDevice(mInfoMediaDevice1); + assertThat(mLocalMediaManager.connectDevice(mInfoMediaDevice1)).isFalse(); assertThat(mInfoMediaDevice1.getState()).isNotEqualTo(LocalMediaManager.MediaDeviceState .STATE_CONNECTING); @@ -185,7 +185,7 @@ public class LocalMediaManagerTest { when(cachedDevice.isBusy()).thenReturn(false); mLocalMediaManager.registerCallback(mCallback); - mLocalMediaManager.connectDevice(device); + assertThat(mLocalMediaManager.connectDevice(device)).isTrue(); verify(cachedDevice).connect(); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java index 47f6fe3bce02..421e5c0db1a1 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java @@ -100,12 +100,12 @@ public class PhoneMediaDeviceTest { when(mInfo.getName()).thenReturn(deviceName); assertThat(mPhoneMediaDevice.getName()) - .isEqualTo(mContext.getString(R.string.media_transfer_wired_device_name)); + .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name)); when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE); assertThat(mPhoneMediaDevice.getName()) - .isEqualTo(deviceName); + .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name)); when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 83d72189de74..b85c7714bf96 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -346,22 +346,6 @@ android:excludeFromRecents="true" android:exported="false" /> - <!-- - The following is used as a no-op/null home activity when - no other MAIN/HOME activity is present (e.g., in CSI). - --> - <activity android:name=".NullHome" - android:excludeFromRecents="true" - android:label="" - android:screenOrientation="nosensor"> - <!-- The priority here is set to be lower than that for Settings --> - <intent-filter android:priority="-1100"> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.HOME" /> - <category android:name="android.intent.category.DEFAULT" /> - </intent-filter> - </activity> - <receiver android:name=".BugreportRequestedReceiver" android:permission="android.permission.TRIGGER_SHELL_BUGREPORT"> diff --git a/packages/Shell/res/layout/null_home_finishing_boot.xml b/packages/Shell/res/layout/null_home_finishing_boot.xml deleted file mode 100644 index 5f9563a5d25c..000000000000 --- a/packages/Shell/res/layout/null_home_finishing_boot.xml +++ /dev/null @@ -1,44 +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" - android:background="#80000000" - android:forceHasOverlappingRendering="false"> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - android:layout_gravity="center" - android:layout_marginStart="16dp" - android:layout_marginEnd="16dp"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="40sp" - android:textColor="?android:attr/textColorPrimary" - android:text="@*android:string/android_start_title"/> - <ProgressBar - style="@android:style/Widget.Material.ProgressBar.Horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="12.75dp" - android:colorControlActivated="?android:attr/textColorPrimary" - android:indeterminate="true"/> - </LinearLayout> -</FrameLayout> diff --git a/packages/Shell/src/com/android/shell/NullHome.java b/packages/Shell/src/com/android/shell/NullHome.java deleted file mode 100644 index bd975614a50a..000000000000 --- a/packages/Shell/src/com/android/shell/NullHome.java +++ /dev/null @@ -1,43 +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. - */ - -package com.android.shell; - -import android.app.Activity; -import android.os.Bundle; -import android.util.Log; - -/** - * This covers the fallback case where no launcher is available. - * Usually Settings.apk has one fallback home activity. - * Settings.apk, however, is not part of CSI, which needs to be - * standalone (bootable and testable). - */ -public class NullHome extends Activity { - private static final String TAG = "NullHome"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Log.i(TAG, "onCreate"); - setContentView(R.layout.null_home_finishing_boot); - } - - protected void onDestroy() { - super.onDestroy(); - Log.i(TAG, "onDestroy"); - } -} diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 4510b87556c7..055b2beaaa65 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -155,6 +155,7 @@ <!-- Screen Recording --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/> <!-- Assist --> <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" /> diff --git a/packages/SystemUI/res/anim/media_button_state_list_animator.xml b/packages/SystemUI/res/anim/media_button_state_list_animator.xml new file mode 100644 index 000000000000..62ebbaa112ea --- /dev/null +++ b/packages/SystemUI/res/anim/media_button_state_list_animator.xml @@ -0,0 +1,50 @@ +<?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. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true"> + <set> + <objectAnimator + android:interpolator="@interpolator/control_state" + android:duration="50" + android:propertyName="scaleX" + android:valueTo="0.9" + android:valueType="floatType" /> + <objectAnimator + android:interpolator="@interpolator/control_state" + android:duration="50" + android:propertyName="scaleY" + android:valueTo="0.9" + android:valueType="floatType" /> + </set> + </item> + <item> + <set> + <objectAnimator + android:interpolator="@interpolator/control_state" + android:duration="250" + android:propertyName="scaleX" + android:valueTo="1" + android:valueType="floatType" /> + <objectAnimator + android:interpolator="@interpolator/control_state" + android:duration="250" + android:propertyName="scaleY" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </item> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/stat_sys_media.xml b/packages/SystemUI/res/drawable/stat_sys_media.xml new file mode 100644 index 000000000000..d48db7bd0d28 --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_sys_media.xml @@ -0,0 +1,31 @@ +<!-- + ~ 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="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M5,7.81l0,8.38l6,-4.19z" + android:fillColor="#000000"/> + <path + android:pathData="M13,8h2v8h-2z" + android:fillColor="#000000"/> + <path + android:pathData="M17,8h2v8h-2z" + android:fillColor="#000000"/> +</vector> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml index 1b42ceb50bf7..99b9ced53090 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml @@ -14,9 +14,7 @@ limitations under the License. --> -<!-- RelativeLayouts have an issue enforcing minimum heights, so just - work around this for now with LinearLayouts. --> -<LinearLayout +<com.android.systemui.globalactions.GlobalActionsItem xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="0dp" android:layout_weight="1" @@ -42,7 +40,7 @@ android:id="@*android:id/message" android:layout_width="match_parent" android:layout_height="wrap_content" - android:ellipsize="marquee" + android:ellipsize="end" android:marqueeRepeatLimit="marquee_forever" android:maxLines="2" android:textSize="12sp" @@ -51,4 +49,4 @@ android:breakStrategy="high_quality" android:hyphenationFrequency="full" android:textAppearance="?android:attr/textAppearanceSmall" /> -</LinearLayout> +</com.android.systemui.globalactions.GlobalActionsItem> diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml index e4e9d2975220..ab4732de7124 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml @@ -46,8 +46,6 @@ <com.android.systemui.globalactions.MinHeightScrollView android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset" - android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset" android:orientation="vertical" android:scrollbars="none" > diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml index e4ae7c1f5827..46396e3e62b4 100644 --- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml +++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml @@ -20,23 +20,29 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/screenshot_action_chip_margin_right" + android:paddingVertical="@dimen/screenshot_action_chip_margin_vertical" android:layout_gravity="center" - android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical" - android:background="@drawable/action_chip_background" - android:alpha="0.0" - android:gravity="center"> - <ImageView - android:id="@+id/screenshot_action_chip_icon" - android:layout_width="@dimen/screenshot_action_chip_icon_size" - android:layout_height="@dimen/screenshot_action_chip_icon_size" - android:layout_marginStart="@dimen/screenshot_action_chip_padding_start" - android:layout_marginEnd="@dimen/screenshot_action_chip_padding_middle"/> - <TextView - android:id="@+id/screenshot_action_chip_text" + android:gravity="center" + android:alpha="0.0"> + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:textSize="@dimen/screenshot_action_chip_text_size" - android:textColor="@color/global_screenshot_button_text"/> + android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical" + android:background="@drawable/action_chip_background" + android:gravity="center"> + <ImageView + android:id="@+id/screenshot_action_chip_icon" + android:layout_width="@dimen/screenshot_action_chip_icon_size" + android:layout_height="@dimen/screenshot_action_chip_icon_size" + android:layout_marginStart="@dimen/screenshot_action_chip_padding_start" + android:layout_marginEnd="@dimen/screenshot_action_chip_padding_middle"/> + <TextView + android:id="@+id/screenshot_action_chip_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:textSize="@dimen/screenshot_action_chip_text_size" + android:textColor="@color/global_screenshot_button_text"/> + </LinearLayout> </com.android.systemui.screenshot.ScreenshotActionChip> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a3d32c12d1c0..31edcf643285 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -314,13 +314,14 @@ <dimen name="screenshot_dismiss_button_margin">8dp</dimen> <dimen name="screenshot_action_container_offset_y">32dp</dimen> <dimen name="screenshot_action_container_corner_radius">10dp</dimen> - <dimen name="screenshot_action_container_padding_vertical">16dp</dimen> + <dimen name="screenshot_action_container_padding_vertical">6dp</dimen> <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen> <dimen name="screenshot_action_container_padding_left">96dp</dimen> <dimen name="screenshot_action_container_padding_right">8dp</dimen> <!-- Radius of the chip background on global screenshot actions --> <dimen name="screenshot_button_corner_radius">20dp</dimen> <dimen name="screenshot_action_chip_margin_right">8dp</dimen> + <dimen name="screenshot_action_chip_margin_vertical">10dp</dimen> <dimen name="screenshot_action_chip_padding_vertical">7dp</dimen> <dimen name="screenshot_action_chip_icon_size">18dp</dimen> <dimen name="screenshot_action_chip_padding_start">8dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 8bbcfa0e7898..de483179a92a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -790,6 +790,9 @@ <!-- Accessibility text describing sensors off active. [CHAR LIMIT=NONE] --> <string name="accessibility_sensors_off_active">Sensors off active</string> + <!-- Accessibility text describing that media is playing. [CHAR LIMIT=NONE] --> + <string name="accessibility_media_active">Media is active</string> + <!-- Content description of the clear button in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_clear_all">Clear all notifications.</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index b461295dc384..4f42370483c4 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -623,6 +623,7 @@ <style name="MediaPlayer.Button" parent="@android:style/Widget.Material.Button.Borderless.Small"> <item name="android:background">@null</item> <item name="android:tint">@android:color/white</item> + <item name="android:stateListAnimator">@anim/media_button_state_list_animator</item> </style> <!-- Used to style charging animation AVD animation --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 0350f2d47569..114472b15f02 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -16,6 +16,7 @@ package com.android.systemui.shared.recents; +import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; import android.view.MotionEvent; @@ -69,4 +70,9 @@ oneway interface IOverviewProxy { * Sent when some system ui state changes. */ void onSystemUiStateChanged(int stateFlags) = 16; + + /** + * Sent when the split screen is resized + */ + void onSplitScreenSecondaryBoundsChanged(in Rect bounds, in Rect insets) = 17; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 136935080824..5a78c903109c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -86,6 +86,8 @@ public class QuickStepContract { // enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble // stack. public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14; + // The global actions dialog is showing + public static final int SYSUI_STATE_GLOBAL_ACTIONS_SHOWING = 1 << 15; @Retention(RetentionPolicy.SOURCE) @IntDef({SYSUI_STATE_SCREEN_PINNING, @@ -102,7 +104,8 @@ public class QuickStepContract { SYSUI_STATE_SEARCH_DISABLED, SYSUI_STATE_TRACING_ENABLED, SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED, - SYSUI_STATE_BUBBLES_EXPANDED + SYSUI_STATE_BUBBLES_EXPANDED, + SYSUI_STATE_GLOBAL_ACTIONS_SHOWING }) public @interface SystemUiStateFlags {} @@ -119,6 +122,7 @@ public class QuickStepContract { str.add((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0 ? "keygrd_occluded" : ""); str.add((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0 ? "bouncer_visible" : ""); + str.add((flags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0 ? "global_actions" : ""); str.add((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0 ? "a11y_click" : ""); str.add((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0 ? "a11y_long_click" : ""); str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : ""); @@ -192,8 +196,9 @@ public class QuickStepContract { * disabled. */ public static boolean isBackGestureDisabled(int sysuiStateFlags) { - // Always allow when the bouncer is showing (even on top of the keyguard) - if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0) { + // Always allow when the bouncer/global actions is showing (even on top of the keyguard) + if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0 + || (sysuiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0) { return false; } // Disable when in immersive, or the notifications are interactive diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java index 8cd89ddabe72..525e98971266 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Context; import android.os.Handler; import android.os.SystemClock; +import android.provider.Settings; import android.util.Log; import android.view.accessibility.AccessibilityManager; @@ -61,6 +62,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = 0; private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3); + private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; /** * This is the default behavior that will be used once the system is up. It will be set once the @@ -203,6 +205,10 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac } private boolean handlesUnblocked(boolean ignoreThreshold) { + if (!isUserSetupComplete()) { + return false; + } + long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt; boolean notThrottled = ignoreThreshold || timeSinceHidden >= getShownFrequencyThreshold(); ComponentName assistantComponent = @@ -284,6 +290,11 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac mShowAndGoEndsAt = 0; } + private boolean isUserSetupComplete() { + return Settings.Secure.getInt( + mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; + } + @VisibleForTesting void setInGesturalModeForTest(boolean inGesturalMode) { mInGesturalMode = inGesturalMode; diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index 9055479c47fe..10e913743224 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -58,7 +58,7 @@ class ControlActionCoordinatorImpl @Inject constructor( override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) { bouncerOrRun { - val effect = if (isChecked) Vibrations.toggleOnEffect else Vibrations.toggleOffEffect + val effect = if (!isChecked) Vibrations.toggleOnEffect else Vibrations.toggleOffEffect vibrate(effect) cvh.action(BooleanAction(templateId, !isChecked)) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 52d564da7890..3aa417ab904b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -59,6 +59,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsPopupMenu import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.phone.ShadeController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Lazy @@ -79,7 +80,8 @@ class ControlsUiControllerImpl @Inject constructor ( @Main val sharedPreferences: SharedPreferences, val controlActionCoordinator: ControlActionCoordinator, private val activityStarter: ActivityStarter, - private val keyguardStateController: KeyguardStateController + private val keyguardStateController: KeyguardStateController, + private val shadeController: ShadeController ) : ControlsUiController { companion object { @@ -254,14 +256,11 @@ class ControlsUiControllerImpl @Inject constructor ( intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true) dismissGlobalActions.run() - if (!keyguardStateController.isUnlocked()) { - activityStarter.dismissKeyguardThenExecute({ - context.startActivity(intent) - true - }, null, true) - } else { + activityStarter.dismissKeyguardThenExecute({ + shadeController.collapsePanel(false) context.startActivity(intent) - } + true + }, null, true) } private fun showControlsView(items: List<SelectionItem>) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index 236fa2d29aca..f97015282222 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -18,7 +18,6 @@ package com.android.systemui.controls.ui import android.app.ActivityView import android.app.Dialog -import android.content.ComponentName import android.content.Intent import android.provider.Settings import android.view.View @@ -58,17 +57,13 @@ class DetailDialog( launchIntent.putExtra(EXTRA_USE_PANEL, true) // Apply flags to make behaviour match documentLaunchMode=always. - launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) view.startActivity(launchIntent) } override fun onActivityViewDestroyed(view: ActivityView) {} - - override fun onTaskCreated(taskId: Int, componentName: ComponentName) {} - - override fun onTaskRemovalStarted(taskId: Int) {} } init { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt index a97113cc598b..c0f6aabe2427 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt @@ -20,7 +20,7 @@ import android.os.VibrationEffect import android.os.VibrationEffect.Composition.PRIMITIVE_TICK object Vibrations { - private const val TOGGLE_TICK_COUNT = 12 + private const val TOGGLE_TICK_COUNT = 40 val toggleOnEffect = initToggleOnEffect() val toggleOffEffect = initToggleOffEffect() @@ -29,6 +29,7 @@ object Vibrations { private fun initToggleOnEffect(): VibrationEffect { val composition = VibrationEffect.startComposition() + composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 200) var i = 0 while (i++ < TOGGLE_TICK_COUNT) { composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 0) @@ -43,7 +44,7 @@ object Vibrations { composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 100) var i = 0 while (i++ < TOGGLE_TICK_COUNT) { - composition?.addPrimitive(PRIMITIVE_TICK, 0.05f, 0) + composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 0) } return composition.compose() } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 5e367046bd2b..65729372363a 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -34,6 +34,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.wakelock.DelayedWakeLock; @@ -56,6 +57,7 @@ public class DozeFactory { private final ProximitySensor mProximitySensor; private final DelayedWakeLock.Builder mDelayedWakeLockBuilder; private final Handler mHandler; + private final DelayableExecutor mDelayableExecutor; private final BiometricUnlockController mBiometricUnlockController; private final BroadcastDispatcher mBroadcastDispatcher; private final DozeHost mDozeHost; @@ -68,6 +70,7 @@ public class DozeFactory { DockManager dockManager, @Nullable IWallpaperManager wallpaperManager, ProximitySensor proximitySensor, DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler, + DelayableExecutor delayableExecutor, BiometricUnlockController biometricUnlockController, BroadcastDispatcher broadcastDispatcher, DozeHost dozeHost) { mFalsingManager = falsingManager; @@ -83,6 +86,7 @@ public class DozeFactory { mProximitySensor = proximitySensor; mDelayedWakeLockBuilder = delayedWakeLockBuilder; mHandler = handler; + mDelayableExecutor = delayableExecutor; mBiometricUnlockController = biometricUnlockController; mBroadcastDispatcher = broadcastDispatcher; mDozeHost = dozeHost; @@ -107,8 +111,8 @@ public class DozeFactory { new DozePauser(mHandler, machine, mAlarmManager, mDozeParameters.getPolicy()), new DozeFalsingManagerAdapter(mFalsingManager), createDozeTriggers(dozeService, mAsyncSensorManager, mDozeHost, - mAlarmManager, config, mDozeParameters, mHandler, wakeLock, machine, - mDockManager, mDozeLog), + mAlarmManager, config, mDozeParameters, mDelayableExecutor, wakeLock, + machine, mDockManager, mDozeLog), createDozeUi(dozeService, mDozeHost, wakeLock, machine, mHandler, mAlarmManager, mDozeParameters, mDozeLog), new DozeScreenState(wrappedService, mHandler, mDozeHost, mDozeParameters, @@ -135,11 +139,11 @@ public class DozeFactory { private DozeTriggers createDozeTriggers(Context context, AsyncSensorManager sensorManager, DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config, - DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine, - DockManager dockManager, DozeLog dozeLog) { + DozeParameters params, DelayableExecutor delayableExecutor, WakeLock wakeLock, + DozeMachine machine, DockManager dockManager, DozeLog dozeLog) { boolean allowPulseTriggers = true; return new DozeTriggers(context, machine, host, alarmManager, config, params, - sensorManager, handler, wakeLock, allowPulseTriggers, dockManager, + sensorManager, delayableExecutor, wakeLock, allowPulseTriggers, dockManager, mProximitySensor, dozeLog, mBroadcastDispatcher); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index e1081cd5ef82..78f8f673cab9 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -101,7 +101,8 @@ public class DozeSensors { public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager, DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, - Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog) { + Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog, + ProximitySensor proximitySensor) { mContext = context; mAlarmManager = alarmManager; mSensorManager = sensorManager; @@ -111,6 +112,7 @@ public class DozeSensors { mProxCallback = proxCallback; mResolver = mContext.getContentResolver(); mCallback = callback; + mProximitySensor = proximitySensor; boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT); mSensors = new TriggerSensor[] { @@ -173,7 +175,6 @@ public class DozeSensors { dozeLog), }; - mProximitySensor = new ProximitySensor(context.getResources(), sensorManager); setProxListening(false); // Don't immediately start listening when we register. mProximitySensor.register( proximityEvent -> { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 3510e07d2cea..6a5501445a4d 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -26,7 +26,6 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.hardware.display.AmbientDisplayConfiguration; import android.metrics.LogMaker; -import android.os.Handler; import android.os.SystemClock; import android.os.UserHandle; import android.text.format.Formatter; @@ -43,6 +42,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.Assert; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.wakelock.WakeLock; @@ -152,9 +152,9 @@ public class DozeTriggers implements DozeMachine.Part { public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost, AlarmManager alarmManager, AmbientDisplayConfiguration config, - DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler, - WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager, - ProximitySensor proximitySensor, + DozeParameters dozeParameters, AsyncSensorManager sensorManager, + DelayableExecutor delayableExecutor, WakeLock wakeLock, boolean allowPulseTriggers, + DockManager dockManager, ProximitySensor proximitySensor, DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher) { mContext = context; mMachine = machine; @@ -165,10 +165,10 @@ public class DozeTriggers implements DozeMachine.Part { mWakeLock = wakeLock; mAllowPulseTriggers = allowPulseTriggers; mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters, - config, wakeLock, this::onSensor, this::onProximityFar, dozeLog); + config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor); mUiModeManager = mContext.getSystemService(UiModeManager.class); mDockManager = dockManager; - mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler); + mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, delayableExecutor); mDozeLog = dozeLog; mBroadcastDispatcher = broadcastDispatcher; } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 62744673e76e..d69c3b01493f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -124,9 +125,11 @@ import com.android.systemui.controls.management.ControlsListingController; import com.android.systemui.controls.ui.ControlsUiController; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; @@ -200,6 +203,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final UiEventLogger mUiEventLogger; private final NotificationShadeDepthController mDepthController; private final BlurUtils mBlurUtils; + private final SysUiState mSysUiState; // Used for RingerModeTracker private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); @@ -301,7 +305,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Background Executor backgroundExecutor, ControlsListingController controlsListingController, ControlsController controlsController, UiEventLogger uiEventLogger, - RingerModeTracker ringerModeTracker, @Main Handler handler) { + RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -330,6 +334,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mBlurUtils = blurUtils; mRingerModeTracker = ringerModeTracker; mControlsController = controlsController; + mSysUiState = sysUiState; mMainHandler = handler; // receive broadcasts @@ -638,7 +643,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter, getWalletPanelViewController(), mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, - shouldShowControls() ? mControlsUiController : null, mBlurUtils, + shouldShowControls() ? mControlsUiController : null, mBlurUtils, mSysUiState, shouldUseControlsLayout(), this::onRotate, mKeyguardShowing); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. dialog.setOnDismissListener(this); @@ -1920,6 +1925,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final NotificationShadeWindowController mNotificationShadeWindowController; private final NotificationShadeDepthController mDepthController; private final BlurUtils mBlurUtils; + private final SysUiState mSysUiState; private final boolean mUseControlsLayout; private ListPopupWindow mOverflowPopup; private final Runnable mOnRotateCallback; @@ -1934,7 +1940,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, ControlsUiController controlsUiController, BlurUtils blurUtils, - boolean useControlsLayout, Runnable onRotateCallback, boolean keyguardShowing) { + SysUiState sysuiState, boolean useControlsLayout, Runnable onRotateCallback, + boolean keyguardShowing) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); mContext = context; mAdapter = adapter; @@ -1945,6 +1952,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mNotificationShadeWindowController = notificationShadeWindowController; mControlsUiController = controlsUiController; mBlurUtils = blurUtils; + mSysUiState = sysuiState; mUseControlsLayout = useControlsLayout; mOnRotateCallback = onRotateCallback; mKeyguardShowing = keyguardShowing; @@ -2203,6 +2211,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mShowing = true; mHadTopUi = mNotificationShadeWindowController.getForceHasTopUi(); mNotificationShadeWindowController.setForceHasTopUi(true); + mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true) + .commitUpdate(mContext.getDisplayId()); ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView(); root.setOnApplyWindowInsetsListener((v, windowInsets) -> { @@ -2303,6 +2313,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (mControlsUiController != null) mControlsUiController.hide(); mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi); mDepthController.updateGlobalDialogVisibility(0, null /* view */); + mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false) + .commitUpdate(mContext.getDisplayId()); super.dismiss(); } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java index c7612d41c45d..83046ef450c3 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java @@ -23,6 +23,7 @@ import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; import android.content.Context; import android.util.AttributeSet; import android.view.View; +import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.HardwareBgDrawable; @@ -78,6 +79,31 @@ public class GlobalActionsFlatLayout extends GlobalActionsLayout { } } + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + boolean anyTruncated = false; + ViewGroup listView = getListView(); + // Check to see if any of the GlobalActionsItems have had their messages truncated + for (int i = 0; i < listView.getChildCount(); i++) { + View child = listView.getChildAt(i); + if (child instanceof GlobalActionsItem) { + GlobalActionsItem item = (GlobalActionsItem) child; + anyTruncated = anyTruncated || item.isTruncated(); + } + } + // If any of the items have been truncated, set the all to single-line marquee + if (anyTruncated) { + for (int i = 0; i < listView.getChildCount(); i++) { + View child = listView.getChildAt(i); + if (child instanceof GlobalActionsItem) { + GlobalActionsItem item = (GlobalActionsItem) child; + item.setMarquee(true); + } + } + } + } + @VisibleForTesting protected float getGridItemSize() { return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsItem.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsItem.java new file mode 100644 index 000000000000..07fa59200a1d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsItem.java @@ -0,0 +1,76 @@ +/* + * 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.globalactions; + +import android.content.Context; +import android.text.Layout; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.internal.R; + +/** + * Layout for GlobalActions items. + */ +public class GlobalActionsItem extends LinearLayout { + public GlobalActionsItem(Context context) { + super(context); + } + + public GlobalActionsItem(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public GlobalActionsItem(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + private TextView getTextView() { + return (TextView) findViewById(R.id.message); + } + + /** + * Sets this item to marquee or not. + */ + public void setMarquee(boolean marquee) { + TextView text = getTextView(); + text.setSingleLine(marquee); + text.setEllipsize(marquee ? TextUtils.TruncateAt.MARQUEE : TextUtils.TruncateAt.END); + } + + /** + * Determines whether the message for this item has been truncated. + */ + public boolean isTruncated() { + TextView message = getTextView(); + if (message != null) { + Layout messageLayout = message.getLayout(); + if (messageLayout != null) { + if (messageLayout.getLineCount() > 0) { + // count the number of ellipses in the last line. + int ellipses = messageLayout.getEllipsisCount( + messageLayout.getLineCount() - 1); + // If ellipses are present, the line was forced to truncate. + return ellipses > 0; + } + } + } + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt index 524c6955ba4a..4ee4ad46d4c7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt @@ -67,5 +67,4 @@ class KeyguardMediaController @Inject constructor( statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER) view?.visibility = if (shouldBeVisible) View.VISIBLE else View.GONE } -} - +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt new file mode 100644 index 000000000000..94a0835f22f8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt @@ -0,0 +1,40 @@ +/* + * 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.media + +import android.content.Context + +import com.android.settingslib.bluetooth.LocalBluetoothManager +import com.android.settingslib.media.InfoMediaManager +import com.android.settingslib.media.LocalMediaManager + +import javax.inject.Inject + +/** + * Factory to create [LocalMediaManager] objects. + */ +class LocalMediaManagerFactory @Inject constructor( + private val context: Context, + private val localBluetoothManager: LocalBluetoothManager? +) { + /** Creates a [LocalMediaManager] for the given package. */ + fun create(packageName: String): LocalMediaManager { + return InfoMediaManager(context, packageName, null, localBluetoothManager).run { + LocalMediaManager(context, localBluetoothManager, this, packageName) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 1691c53386d6..f90798bd30b8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -41,7 +41,6 @@ import android.util.Log; import android.view.View; import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.TextView; @@ -56,14 +55,11 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawable; import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; import com.android.settingslib.Utils; -import com.android.settingslib.media.LocalMediaManager; -import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.MediaOutputSliceConstants; import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSMediaBrowser; -import com.android.systemui.util.Assert; import com.android.systemui.util.concurrency.DelayableExecutor; import org.jetbrains.annotations.NotNull; @@ -77,7 +73,6 @@ import java.util.concurrent.Executor; */ public class MediaControlPanel { private static final String TAG = "MediaControlPanel"; - @Nullable private final LocalMediaManager mLocalMediaManager; // Button IDs for QS controls static final int[] ACTION_IDS = { @@ -100,7 +95,6 @@ public class MediaControlPanel { private MediaSession.Token mToken; private MediaController mController; private int mBackgroundColor; - private MediaDevice mDevice; protected ComponentName mServiceComponent; private boolean mIsRegistered = false; private List<KeyFrames> mKeyFrames; @@ -113,7 +107,6 @@ public class MediaControlPanel { public static final String MEDIA_PREFERENCE_KEY = "browser_components"; private SharedPreferences mSharedPrefs; private boolean mCheckedForResumption = false; - private boolean mIsRemotePlayback; private QSMediaBrowser mQSMediaBrowser; private final MediaController.Callback mSessionCallback = new MediaController.Callback() { @@ -122,7 +115,6 @@ public class MediaControlPanel { Log.d(TAG, "session destroyed"); mController.unregisterCallback(mSessionCallback); clearControls(); - makeInactive(); } @Override public void onPlaybackStateChanged(PlaybackState state) { @@ -130,31 +122,6 @@ public class MediaControlPanel { if (s == PlaybackState.STATE_NONE) { Log.d(TAG, "playback state change will trigger resumption, state=" + state); clearControls(); - makeInactive(); - } - } - }; - - private final LocalMediaManager.DeviceCallback mDeviceCallback = - new LocalMediaManager.DeviceCallback() { - @Override - public void onDeviceListUpdate(List<MediaDevice> devices) { - if (mLocalMediaManager == null) { - return; - } - MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice(); - // Check because this can be called several times while changing devices - if (mDevice == null || !mDevice.equals(currentDevice)) { - mDevice = currentDevice; - updateDevice(mDevice); - } - } - - @Override - public void onSelectedDeviceStateChanged(MediaDevice device, int state) { - if (mDevice == null || !mDevice.equals(device)) { - mDevice = device; - updateDevice(mDevice); } } }; @@ -162,16 +129,13 @@ public class MediaControlPanel { /** * Initialize a new control panel * @param context - * @param routeManager Manager used to listen for device change events. * @param foregroundExecutor foreground executor * @param backgroundExecutor background executor, used for processing artwork * @param activityStarter activity starter */ - public MediaControlPanel(Context context, @Nullable LocalMediaManager routeManager, - Executor foregroundExecutor, DelayableExecutor backgroundExecutor, - ActivityStarter activityStarter) { + public MediaControlPanel(Context context, Executor foregroundExecutor, + DelayableExecutor backgroundExecutor, ActivityStarter activityStarter) { mContext = context; - mLocalMediaManager = routeManager; mForegroundExecutor = foregroundExecutor; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; @@ -183,7 +147,6 @@ public class MediaControlPanel { if (mSeekBarObserver != null) { mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver); } - makeInactive(); } private void loadDimens() { @@ -228,7 +191,7 @@ public class MediaControlPanel { mLayoutAnimationHelper = new LayoutAnimationHelper(motionView); GoneChildrenHideHelper.clipGoneChildrenOnLayout(motionView); mKeyFrames = motionView.getDefinedTransitions().get(0).getKeyFrameList(); - mSeekBarObserver = new SeekBarObserver(motionView); + mSeekBarObserver = new SeekBarObserver(vh); mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver); SeekBar bar = vh.getSeekBar(); bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener()); @@ -318,30 +281,67 @@ public class MediaControlPanel { artistText.setText(data.getArtist()); // Transfer chip - if (mLocalMediaManager != null) { - mViewHolder.getSeamless().setVisibility(View.VISIBLE); - setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */); - setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */); - updateDevice(mLocalMediaManager.getCurrentConnectedDevice()); - mViewHolder.getSeamless().setOnClickListener(v -> { - final Intent intent = new Intent() - .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) - .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, - mController.getPackageName()) - .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken); - mActivityStarter.startActivity(intent, false, true /* dismissShade */, - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - }); - } else { - Log.d(TAG, "LocalMediaManager is null. Not binding output chip for pkg=" + pkgName); - } + mViewHolder.getSeamless().setVisibility(View.VISIBLE); + setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */); + setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */); + mViewHolder.getSeamless().setOnClickListener(v -> { + final Intent intent = new Intent() + .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) + .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, + mController.getPackageName()) + .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken); + mActivityStarter.startActivity(intent, false, true /* dismissShade */, + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + }); + final boolean isRemotePlayback; PlaybackInfo playbackInfo = mController.getPlaybackInfo(); if (playbackInfo != null) { - mIsRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE; + isRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE; } else { Log.d(TAG, "PlaybackInfo was null. Defaulting to local playback."); - mIsRemotePlayback = false; + isRemotePlayback = false; + } + + ImageView iconView = mViewHolder.getSeamlessIcon(); + TextView deviceName = mViewHolder.getSeamlessText(); + + // Update the outline color + RippleDrawable bkgDrawable = (RippleDrawable) mViewHolder.getSeamless().getBackground(); + GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0); + rect.setStroke(2, deviceName.getCurrentTextColor()); + rect.setColor(Color.TRANSPARENT); + + if (isRemotePlayback) { + mViewHolder.getSeamless().setEnabled(false); + // TODO(b/156875717): setEnabled should cause the alpha to change. + mViewHolder.getSeamless().setAlpha(0.38f); + iconView.setImageResource(R.drawable.ic_hardware_speaker); + iconView.setVisibility(View.VISIBLE); + deviceName.setText(R.string.media_seamless_remote_device); + } else if (data.getDevice() != null && data.getDevice().getIcon() != null + && data.getDevice().getName() != null) { + mViewHolder.getSeamless().setEnabled(true); + mViewHolder.getSeamless().setAlpha(1f); + Drawable icon = data.getDevice().getIcon(); + iconView.setVisibility(View.VISIBLE); + + if (icon instanceof AdaptiveIcon) { + AdaptiveIcon aIcon = (AdaptiveIcon) icon; + aIcon.setBackgroundColor(mBackgroundColor); + iconView.setImageDrawable(aIcon); + } else { + iconView.setImageDrawable(icon); + } + deviceName.setText(data.getDevice().getName()); + } else { + // Reset to default + Log.w(TAG, "device is null. Not binding output chip."); + mViewHolder.getSeamless().setEnabled(true); + mViewHolder.getSeamless().setAlpha(1f); + iconView.setVisibility(View.GONE); + deviceName.setText(com.android.internal.R.string.ext_media_seamless_action); } + List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact(); // Media controls int i = 0; @@ -382,8 +382,6 @@ public class MediaControlPanel { // Set up long press menu // TODO: b/156036025 bring back media guts - makeActive(); - // Update both constraint sets to regenerate the animation. mViewHolder.getPlayer().updateState(R.id.collapsed, collapsedSet); mViewHolder.getPlayer().updateState(R.id.expanded, expandedSet); @@ -515,60 +513,6 @@ public class MediaControlPanel { } /** - * Update the current device information - * @param device device information to display - */ - private void updateDevice(MediaDevice device) { - mForegroundExecutor.execute(() -> { - updateChipInternal(device); - }); - } - - private void updateChipInternal(MediaDevice device) { - if (mViewHolder == null) { - return; - } - ImageView iconView = mViewHolder.getSeamlessIcon(); - TextView deviceName = mViewHolder.getSeamlessText(); - - // Update the outline color - LinearLayout viewLayout = (LinearLayout) mViewHolder.getSeamless(); - RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground(); - GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0); - rect.setStroke(2, deviceName.getCurrentTextColor()); - rect.setColor(Color.TRANSPARENT); - - if (mIsRemotePlayback) { - mViewHolder.getSeamless().setEnabled(false); - mViewHolder.getSeamless().setAlpha(0.38f); - iconView.setImageResource(R.drawable.ic_hardware_speaker); - iconView.setVisibility(View.VISIBLE); - deviceName.setText(R.string.media_seamless_remote_device); - } else if (device != null) { - mViewHolder.getSeamless().setEnabled(true); - mViewHolder.getSeamless().setAlpha(1f); - Drawable icon = device.getIcon(); - iconView.setVisibility(View.VISIBLE); - - if (icon instanceof AdaptiveIcon) { - AdaptiveIcon aIcon = (AdaptiveIcon) icon; - aIcon.setBackgroundColor(mBackgroundColor); - iconView.setImageDrawable(aIcon); - } else { - iconView.setImageDrawable(icon); - } - deviceName.setText(device.getName()); - } else { - // Reset to default - Log.d(TAG, "device is null. Not binding output chip."); - mViewHolder.getSeamless().setEnabled(true); - mViewHolder.getSeamless().setAlpha(1f); - iconView.setVisibility(View.GONE); - deviceName.setText(com.android.internal.R.string.ext_media_seamless_action); - } - } - - /** * Puts controls into a resumption state if possible, or calls removePlayer if no component was * found that could resume playback */ @@ -642,27 +586,6 @@ public class MediaControlPanel { set.setAlpha(actionId, visible ? 1.0f : 0.0f); } - private void makeActive() { - Assert.isMainThread(); - if (!mIsRegistered) { - if (mLocalMediaManager != null) { - mLocalMediaManager.registerCallback(mDeviceCallback); - mLocalMediaManager.startScan(); - } - mIsRegistered = true; - } - } - - private void makeInactive() { - Assert.isMainThread(); - if (mIsRegistered) { - if (mLocalMediaManager != null) { - mLocalMediaManager.stopScan(); - mLocalMediaManager.unregisterCallback(mDeviceCallback); - } - mIsRegistered = false; - } - } /** * Verify that we can connect to the given component with a MediaBrowser, and if so, add that * component to the list of resumption components diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 85965d03a096..41d411019921 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -34,7 +34,8 @@ data class MediaData( val actionsToShowInCompact: List<Int>, val packageName: String?, val token: MediaSession.Token?, - val clickIntent: PendingIntent? + val clickIntent: PendingIntent?, + val device: MediaDeviceData? ) /** State of a media action. */ @@ -43,3 +44,9 @@ data class MediaAction( val intent: PendingIntent?, val contentDescription: CharSequence? ) + +/** State of the media device. */ +data class MediaDeviceData( + val icon: Drawable?, + val name: String? +) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt new file mode 100644 index 000000000000..cce9838bb8e2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.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.media + +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Combines updates from [MediaDataManager] with [MediaDeviceManager]. + */ +@Singleton +class MediaDataCombineLatest @Inject constructor( + private val dataSource: MediaDataManager, + private val deviceSource: MediaDeviceManager +) { + private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf() + private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf() + + init { + dataSource.addListener(object : MediaDataManager.Listener { + override fun onMediaDataLoaded(key: String, data: MediaData) { + entries[key] = data to entries[key]?.second + update(key) + } + override fun onMediaDataRemoved(key: String) { + remove(key) + } + }) + deviceSource.addListener(object : MediaDeviceManager.Listener { + override fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) { + entries[key] = entries[key]?.first to data + update(key) + } + override fun onKeyRemoved(key: String) { + remove(key) + } + }) + } + + /** + * Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData]. + */ + fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener) + + /** + * Remove a listener registered with addListener. + */ + fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener) + + private fun update(key: String) { + val (entry, device) = entries[key] ?: null to null + if (entry != null && device != null) { + val data = entry.copy(device = device) + listeners.forEach { + it.onMediaDataLoaded(key, data) + } + } + } + + private fun remove(key: String) { + entries.remove(key)?.let { + listeners.forEach { + it.onMediaDataRemoved(key) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 90c558a1ee97..8cbe3ecf5387 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -55,7 +55,19 @@ private const val LUMINOSITY_THRESHOLD = 0.05f private const val SATURATION_MULTIPLIER = 0.8f private val LOADING = MediaData(false, 0, null, null, null, null, null, - emptyList(), emptyList(), null, null, null) + emptyList(), emptyList(), null, null, null, null) + +fun isMediaNotification(sbn: StatusBarNotification): Boolean { + if (!sbn.notification.hasMediaSession()) { + return false + } + val notificationStyle = sbn.notification.notificationStyle + if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle) || + Notification.MediaStyle::class.java.equals(notificationStyle)) { + return true + } + return false +} /** * A class that facilitates management and loading of Media Data, ready for binding. @@ -65,14 +77,14 @@ class MediaDataManager @Inject constructor( private val context: Context, private val mediaControllerFactory: MediaControllerFactory, @Background private val backgroundExecutor: Executor, - @Main private val foregroundExcecutor: Executor + @Main private val foregroundExecutor: Executor ) { private val listeners: MutableSet<Listener> = mutableSetOf() private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap() fun onNotificationAdded(key: String, sbn: StatusBarNotification) { - if (isMediaNotification(sbn)) { + if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) { if (!mediaEntries.containsKey(key)) { mediaEntries.put(key, LOADING) } @@ -201,10 +213,10 @@ class MediaDataManager @Inject constructor( } } - foregroundExcecutor.execute { + foregroundExecutor.execute { onMediaDataLoaded(key, MediaData(true, bgColor, app, smallIconDrawable, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token, - notif.contentIntent)) + notif.contentIntent, null)) } } @@ -270,25 +282,10 @@ class MediaDataManager @Inject constructor( } } - private fun isMediaNotification(sbn: StatusBarNotification): Boolean { - if (!Utils.useQsMediaPlayer(context)) { - return false - } - if (!sbn.notification.hasMediaSession()) { - return false - } - val notificationStyle = sbn.notification.notificationStyle - if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle) || - Notification.MediaStyle::class.java.equals(notificationStyle)) { - return true - } - return false - } - /** * Are there any media notifications active? */ - fun hasActiveMedia() = mediaEntries.size > 0 + fun hasActiveMedia() = mediaEntries.isNotEmpty() fun hasAnyMedia(): Boolean { // TODO: implement this when we implemented resumption diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt new file mode 100644 index 000000000000..2d16e2930365 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -0,0 +1,117 @@ +/* + * 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.media + +import android.content.Context +import android.service.notification.StatusBarNotification +import com.android.settingslib.media.LocalMediaManager +import com.android.settingslib.media.MediaDevice +import com.android.systemui.dagger.qualifiers.Main +import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Provides information about the route (ie. device) where playback is occurring. + */ +@Singleton +class MediaDeviceManager @Inject constructor( + private val context: Context, + private val localMediaManagerFactory: LocalMediaManagerFactory, + private val featureFlag: MediaFeatureFlag, + @Main private val fgExecutor: Executor +) { + private val listeners: MutableSet<Listener> = mutableSetOf() + private val entries: MutableMap<String, Token> = mutableMapOf() + + /** + * Add a listener for changes to the media route (ie. device). + */ + fun addListener(listener: Listener) = listeners.add(listener) + + /** + * Remove a listener that has been registered with addListener. + */ + fun removeListener(listener: Listener) = listeners.remove(listener) + + fun onNotificationAdded(key: String, sbn: StatusBarNotification) { + if (featureFlag.enabled && isMediaNotification(sbn)) { + var tok = entries[key] + if (tok == null) { + tok = Token(key, localMediaManagerFactory.create(sbn.packageName)) + entries[key] = tok + tok.start() + } + } else { + onNotificationRemoved(key) + } + } + + fun onNotificationRemoved(key: String) { + val token = entries.remove(key) + token?.stop() + token?.let { + listeners.forEach { + it.onKeyRemoved(key) + } + } + } + + private fun processDevice(key: String, device: MediaDevice?) { + val data = MediaDeviceData(device?.icon, device?.name) + listeners.forEach { + it.onMediaDeviceChanged(key, data) + } + } + + interface Listener { + /** Called when the route has changed for a given notification. */ + fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) + /** Called when the notification was removed. */ + fun onKeyRemoved(key: String) + } + + private inner class Token( + val key: String, + val localMediaManager: LocalMediaManager + ) : LocalMediaManager.DeviceCallback { + private var current: MediaDevice? = null + set(value) { + if (value != field) { + field = value + processDevice(key, value) + } + } + fun start() { + localMediaManager.registerCallback(this) + localMediaManager.startScan() + current = localMediaManager.getCurrentConnectedDevice() + } + fun stop() { + localMediaManager.stopScan() + localMediaManager.unregisterCallback(this) + } + override fun onDeviceListUpdate(devices: List<MediaDevice>?) = fgExecutor.execute { + current = localMediaManager.getCurrentConnectedDevice() + } + override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) { + fgExecutor.execute { + current = device + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt new file mode 100644 index 000000000000..75eb33da64d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt @@ -0,0 +1,29 @@ +/* + * 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.media + +import android.content.Context +import com.android.systemui.util.Utils +import javax.inject.Inject + +/** + * Provides access to the current value of the feature flag. + */ +class MediaFeatureFlag @Inject constructor(private val context: Context) { + val enabled + get() = Utils.useQsMediaPlayer(context) +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index 6e7b6bcb7502..240e44cb8db4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -26,8 +26,8 @@ class MediaHost @Inject constructor( /** * Get the current Media state. This also updates the location on screen */ - val currentState : MediaState - get () { + val currentState: MediaState + get() { hostView.getLocationOnScreen(tmpLocationOnScreen) var left = tmpLocationOnScreen[0] + hostView.paddingLeft var top = tmpLocationOnScreen[1] + hostView.paddingTop @@ -37,11 +37,11 @@ class MediaHost @Inject constructor( // the above could return negative widths, which is wrong if (right < left) { left = 0 - right = 0; + right = 0 } if (bottom < top) { bottom = 0 - top = 0; + top = 0 } state.boundsOnScreen.set(left, top, right, bottom) return state @@ -64,7 +64,7 @@ class MediaHost @Inject constructor( * transitions. */ fun init(@MediaLocation location: Int) { - this.location = location; + this.location = location hostView = mediaHierarchyManager.register(this) hostView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View?) { @@ -95,7 +95,7 @@ class MediaHost @Inject constructor( override var showsOnlyActiveMedia: Boolean = false override val boundsOnScreen: Rect = Rect() - override fun copy() : MediaState { + override fun copy(): MediaState { val mediaHostState = MediaHostState() mediaHostState.expansion = expansion mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia @@ -104,7 +104,7 @@ class MediaHost @Inject constructor( return mediaHostState } - override fun interpolate(other: MediaState, amount: Float) : MediaState { + override fun interpolate(other: MediaState, amount: Float): MediaState { val result = MediaHostState() result.expansion = MathUtils.lerp(expansion, other.expansion, amount) val left = MathUtils.lerp(boundsOnScreen.left.toFloat(), @@ -121,10 +121,10 @@ class MediaHost @Inject constructor( if (other is MediaHostState) { result.measurementInput = other.measurementInput } - } else { + } else { result.measurementInput } - return result + return result } override fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput { @@ -138,8 +138,8 @@ interface MediaState { var expansion: Float var showsOnlyActiveMedia: Boolean val boundsOnScreen: Rect - fun copy() : MediaState - fun interpolate(other: MediaState, amount: Float) : MediaState + fun copy(): MediaState + fun interpolate(other: MediaState, amount: Float): MediaState fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput } /** @@ -147,7 +147,8 @@ interface MediaState { */ data class MediaMeasurementInput( private val viewInput: MeasurementInput, - val expansion: Float) : MeasurementInput by viewInput { + val expansion: Float +) : MeasurementInput by viewInput { override fun sameAs(input: MeasurementInput?): Boolean { if (!(input is MediaMeasurementInput)) { @@ -155,5 +156,4 @@ data class MediaMeasurementInput( } return width == input.width && expansion == input.expansion } -} - +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt index 8db9dcc1ecec..17e8404fe705 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt @@ -6,9 +6,6 @@ import android.view.View import android.view.ViewGroup import android.widget.HorizontalScrollView import android.widget.LinearLayout -import com.android.settingslib.bluetooth.LocalBluetoothManager -import com.android.settingslib.media.InfoMediaManager -import com.android.settingslib.media.LocalMediaManager import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -30,10 +27,9 @@ class MediaViewManager @Inject constructor( private val context: Context, @Main private val foregroundExecutor: Executor, @Background private val backgroundExecutor: DelayableExecutor, - private val localBluetoothManager: LocalBluetoothManager?, private val visualStabilityManager: VisualStabilityManager, private val activityStarter: ActivityStarter, - mediaManager: MediaDataManager + mediaManager: MediaDataCombineLatest ) { private var playerWidth: Int = 0 private var playerWidthPlusPadding: Int = 0 @@ -42,7 +38,7 @@ class MediaViewManager @Inject constructor( val mediaCarousel: HorizontalScrollView private val mediaContent: ViewGroup private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf() - private val visualStabilityCallback : VisualStabilityManager.Callback + private val visualStabilityCallback: VisualStabilityManager.Callback private var activeMediaIndex: Int = 0 private var needsReordering: Boolean = false private var scrollIntoCurrentMedia: Int = 0 @@ -151,15 +147,8 @@ class MediaViewManager @Inject constructor( private fun updateView(key: String, data: MediaData) { var existingPlayer = mediaPlayers[key] if (existingPlayer == null) { - // Set up listener for device changes - // TODO: integrate with MediaTransferManager? - val imm = InfoMediaManager(context, data.packageName, - null /* notification */, localBluetoothManager) - val routeManager = LocalMediaManager(context, localBluetoothManager, - imm, data.packageName) - - existingPlayer = MediaControlPanel(context, routeManager, foregroundExecutor, - backgroundExecutor, activityStarter) + existingPlayer = MediaControlPanel(context, foregroundExecutor, backgroundExecutor, + activityStarter) existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context), mediaContent)) mediaPlayers[key] = existingPlayer diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt index 110b08c4b808..cd8ed265bd53 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt @@ -16,58 +16,41 @@ package com.android.systemui.media -import android.content.res.ColorStateList -import android.graphics.Color import android.text.format.DateUtils -import android.view.View -import android.widget.SeekBar -import android.widget.TextView import androidx.annotation.UiThread import androidx.lifecycle.Observer -import com.android.systemui.R - /** * Observer for changes from SeekBarViewModel. * * <p>Updates the seek bar views in response to changes to the model. */ -class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> { - - private val seekBarView: SeekBar - private val elapsedTimeView: TextView - private val totalTimeView: TextView - - init { - seekBarView = view.findViewById(R.id.media_progress_bar) - elapsedTimeView = view.findViewById(R.id.media_elapsed_time) - totalTimeView = view.findViewById(R.id.media_total_time) - } +class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarViewModel.Progress> { /** Updates seek bar views when the data model changes. */ @UiThread override fun onChanged(data: SeekBarViewModel.Progress) { if (!data.enabled) { - seekBarView.setEnabled(false) - seekBarView.getThumb().setAlpha(0) - seekBarView.setProgress(0) - elapsedTimeView.setText("") - totalTimeView.setText("") + holder.seekBar.setEnabled(false) + holder.seekBar.getThumb().setAlpha(0) + holder.seekBar.setProgress(0) + holder.elapsedTimeView.setText("") + holder.totalTimeView.setText("") return } - seekBarView.getThumb().setAlpha(if (data.seekAvailable) 255 else 0) - seekBarView.setEnabled(data.seekAvailable) + holder.seekBar.getThumb().setAlpha(if (data.seekAvailable) 255 else 0) + holder.seekBar.setEnabled(data.seekAvailable) data.elapsedTime?.let { - seekBarView.setProgress(it) - elapsedTimeView.setText(DateUtils.formatElapsedTime( + holder.seekBar.setProgress(it) + holder.elapsedTimeView.setText(DateUtils.formatElapsedTime( it / DateUtils.SECOND_IN_MILLIS)) } data.duration?.let { - seekBarView.setMax(it) - totalTimeView.setText(DateUtils.formatElapsedTime( + holder.seekBar.setMax(it) + holder.totalTimeView.setText(DateUtils.formatElapsedTime( it / DateUtils.SECOND_IN_MILLIS)) } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java index 13516a9e03c4..7f7e1085d497 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java @@ -170,9 +170,9 @@ public class PipAnimationController { private final @AnimationType int mAnimationType; private final Rect mDestinationBounds = new Rect(); - private T mStartValue; + protected T mCurrentValue; + protected T mStartValue; private T mEndValue; - private T mCurrentValue; private PipAnimationCallback mPipAnimationCallback; private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; @@ -288,7 +288,6 @@ public class PipAnimationController { */ void updateEndValue(T endValue) { mEndValue = endValue; - mStartValue = mCurrentValue; } SurfaceControl.Transaction newSurfaceControlTransaction() { @@ -337,6 +336,12 @@ public class PipAnimationController { tx.show(leash); tx.apply(); } + + @Override + void updateEndValue(Float endValue) { + super.updateEndValue(endValue); + mStartValue = mCurrentValue; + } }; } @@ -392,6 +397,14 @@ public class PipAnimationController { getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds()) .crop(tx, leash, getDestinationBounds()); } + + @Override + void updateEndValue(Rect endValue) { + super.updateEndValue(endValue); + if (mStartValue != null && mCurrentValue != null) { + mStartValue.set(mCurrentValue); + } + } }; } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 394f9975579d..93605170f22e 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -61,12 +61,6 @@ public class PipBoundsHandler { private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Rect mTmpInsets = new Rect(); - /** - * Tracks the destination bounds, used for any following - * {@link #onMovementBoundsChanged(Rect, Rect, Rect, DisplayInfo)} calculations. - */ - private final Rect mLastDestinationBounds = new Rect(); - private ComponentName mLastPipComponentName; private float mReentrySnapFraction = INVALID_SNAP_FRACTION; private Size mReentrySize; @@ -198,11 +192,6 @@ public class PipBoundsHandler { mReentrySnapFraction = INVALID_SNAP_FRACTION; mReentrySize = null; mLastPipComponentName = null; - mLastDestinationBounds.setEmpty(); - } - - public Rect getLastDestinationBounds() { - return mLastDestinationBounds; } public Rect getDisplayBounds() { @@ -262,7 +251,6 @@ public class PipBoundsHandler { false /* useCurrentMinEdgeSize */); } mAspectRatio = aspectRatio; - mLastDestinationBounds.set(destinationBounds); return destinationBounds; } @@ -276,8 +264,8 @@ public class PipBoundsHandler { * * @return {@code true} if internal {@link DisplayInfo} is rotated, {@code false} otherwise. */ - public boolean onDisplayRotationChanged(Rect outBounds, int displayId, int fromRotation, - int toRotation, WindowContainerTransaction t) { + public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, int displayId, + int fromRotation, int toRotation, WindowContainerTransaction t) { // Bail early if the event is not sent to current {@link #mDisplayInfo} if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) { return false; @@ -295,7 +283,7 @@ public class PipBoundsHandler { } // Calculate the snap fraction of the current stack along the old movement bounds - final Rect postChangeStackBounds = new Rect(mLastDestinationBounds); + final Rect postChangeStackBounds = new Rect(oldBounds); final float snapFraction = getSnapFraction(postChangeStackBounds); // Populate the new {@link #mDisplayInfo}. @@ -313,7 +301,6 @@ public class PipBoundsHandler { snapFraction); outBounds.set(postChangeStackBounds); - mLastDestinationBounds.set(outBounds); t.setBounds(pinnedStackInfo.stackToken, outBounds); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 78d2d9857628..ae6100675cb4 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -600,6 +600,7 @@ public class PipTaskOrganizer extends TaskOrganizer { Log.w(TAG, "Abort animation, invalid leash"); return; } + mLastReportedBounds.set(destinationBounds); final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); mSurfaceTransactionHelper .crop(tx, mLeash, destinationBounds) diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 30d6cd9d23b7..64df2ffee1c6 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -95,7 +95,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio private final DisplayChangeController.OnDisplayChangingListener mRotationController = ( int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> { final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mTmpNormalBounds, - displayId, fromRotation, toRotation, t); + mPipTaskOrganizer.getLastReportedBounds(), displayId, fromRotation, toRotation, t); if (changed) { updateMovementBounds(mTmpNormalBounds, true /* fromRotation */, false /* fromImeAdjustment */, false /* fromShelfAdjustment */); @@ -293,7 +293,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio final boolean changed = mPipBoundsHandler.setShelfHeight(visible, height); if (changed) { mTouchHandler.onShelfVisibilityChanged(visible, height); - updateMovementBounds(mPipBoundsHandler.getLastDestinationBounds(), + updateMovementBounds(mPipTaskOrganizer.getLastReportedBounds(), false /* fromRotation */, false /* fromImeAdjustment */, true /* fromShelfAdjustment */); } 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 59c962da7baa..af9dd574c8af 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -424,20 +424,28 @@ public class PipTouchHandler { // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not // occluded by the IME or shelf. - if (fromImeAdjustment || fromShelfAdjustment || fromDisplayRotationChanged) { + if (fromImeAdjustment || fromShelfAdjustment) { if (mTouchState.isUserInteracting()) { // Defer the update of the current movement bounds until after the user finishes // touching the screen } else { final float offsetBufferPx = BOTTOM_OFFSET_BUFFER_DP * mContext.getResources().getDisplayMetrics().density; - final Rect toMovementBounds = mMenuState == MENU_STATE_FULL && willResizeMenu() + final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu(); + final Rect toMovementBounds = isExpanded ? new Rect(expandedMovementBounds) : new Rect(normalMovementBounds); final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets; final int toBottom = toMovementBounds.bottom < toMovementBounds.top ? toMovementBounds.bottom : toMovementBounds.bottom - extraOffset; + + if (isExpanded) { + curBounds.set(mExpandedBounds); + mSnapAlgorithm.applySnapFraction(curBounds, toMovementBounds, + mSavedSnapFraction); + } + if ((Math.min(prevBottom, toBottom) - offsetBufferPx) <= curBounds.top && curBounds.top <= (Math.max(prevBottom, toBottom) + offsetBufferPx)) { mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top); diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 34a9e28b943a..b272b60f3593 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -842,7 +842,26 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Log.e(TAG_OPS, "Failed to get overview proxy for assistant visibility."); } } catch (RemoteException e) { - Log.e(TAG_OPS, "Failed to call onAssistantVisibilityChanged()", e); + Log.e(TAG_OPS, "Failed to call notifyAssistantVisibilityChanged()", e); + } + } + + /** + * Notifies the Launcher of split screen size changes + * @param secondaryWindowBounds Bounds of the secondary window including the insets + * @param secondaryWindowInsets stable insets received by the secondary window + */ + public void notifySplitScreenBoundsChanged( + Rect secondaryWindowBounds, Rect secondaryWindowInsets) { + try { + if (mOverviewProxy != null) { + mOverviewProxy.onSplitScreenSecondaryBoundsChanged( + secondaryWindowBounds, secondaryWindowInsets); + } else { + Log.e(TAG_OPS, "Failed to get overview proxy for split screen bounds."); + } + } catch (RemoteException e) { + Log.e(TAG_OPS, "Failed to call onSplitScreenSecondaryBoundsChanged()", e); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 839ea69953af..f2d2eb3a0836 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -642,13 +642,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private void startAnimation(final Consumer<Uri> finisher, int w, int h, @Nullable Rect screenRect) { // If power save is on, show a toast so there is some visual indication that a - // screenshot - // has been taken. - PowerManager powerManager = (PowerManager) mContext.getSystemService( - Context.POWER_SERVICE); + // screenshot has been taken. + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); if (powerManager.isPowerSaveMode()) { - Toast.makeText(mContext, R.string.screenshot_saved_title, - Toast.LENGTH_SHORT).show(); + Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); } mScreenshotAnimation = createScreenshotDropInAnimation(w, h, screenRect); @@ -706,24 +703,27 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotPreview.buildLayer(); mScreenshotAnimation.start(); }); - }); - } private AnimatorSet createScreenshotDropInAnimation(int width, int height, Rect bounds) { + float screenWidth = mDisplayMetrics.widthPixels; + float screenHeight = mDisplayMetrics.heightPixels; + int rotation = mContext.getDisplay().getRotation(); float cornerScale; if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) { - cornerScale = (mCornerSizeX / (float) height); + cornerScale = (mCornerSizeX / screenHeight); } else { - cornerScale = (mCornerSizeX / (float) width); + cornerScale = (mCornerSizeX / screenWidth); } + float currentScale = width / screenWidth; - mScreenshotAnimatedView.setScaleX(1); - mScreenshotAnimatedView.setScaleY(1); - mScreenshotAnimatedView.setX(0); - mScreenshotAnimatedView.setY(0); + mScreenshotAnimatedView.setScaleX(currentScale); + mScreenshotAnimatedView.setScaleY(currentScale); + + mScreenshotAnimatedView.setPivotX(0); + mScreenshotAnimatedView.setPivotY(0); mScreenshotAnimatedView.setImageBitmap(mScreenBitmap); mScreenshotPreview.setImageBitmap(mScreenBitmap); @@ -744,12 +744,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset final PointF startPos = new PointF(bounds.centerX(), bounds.centerY()); float finalX; if (mDirectionLTR) { - finalX = mScreenshotOffsetXPx + width * cornerScale / 2f; + finalX = mScreenshotOffsetXPx + screenWidth * cornerScale / 2f; } else { - finalX = width - mScreenshotOffsetXPx - width * cornerScale / 2f; + finalX = screenWidth - mScreenshotOffsetXPx - screenWidth * cornerScale / 2f; } - float finalY = - mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale / 2f; + float finalY = screenHeight - mScreenshotOffsetYPx - screenHeight * cornerScale / 2f; final PointF finalPos = new PointF(finalX, finalY); ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1); @@ -757,13 +756,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset float xPositionPct = SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS; float scalePct = - SCREENSHOT_TO_CORNER_SCALE_DURATION_MS - / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS; + SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS; toCorner.addUpdateListener(animation -> { float t = animation.getAnimatedFraction(); if (t < scalePct) { float scale = MathUtils.lerp( - 1, cornerScale, mFastOutSlowIn.getInterpolation(t / scalePct)); + currentScale, cornerScale, mFastOutSlowIn.getInterpolation(t / scalePct)); mScreenshotAnimatedView.setScaleX(scale); mScreenshotAnimatedView.setScaleY(scale); } else { @@ -777,13 +775,13 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset if (t < xPositionPct) { float xCenter = MathUtils.lerp(startPos.x, finalPos.x, mFastOutSlowIn.getInterpolation(t / xPositionPct)); - mScreenshotAnimatedView.setX(xCenter - width * currentScaleX / 2f); + mScreenshotAnimatedView.setX(xCenter - screenWidth * currentScaleX / 2f); } else { - mScreenshotAnimatedView.setX(finalPos.x - width * currentScaleX / 2f); + mScreenshotAnimatedView.setX(finalPos.x - screenWidth * currentScaleX / 2f); } float yCenter = MathUtils.lerp(startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t)); - mScreenshotAnimatedView.setY(yCenter - height * currentScaleY / 2f); + mScreenshotAnimatedView.setY(yCenter - screenHeight * currentScaleY / 2f); }); toCorner.addListener(new AnimatorListenerAdapter() { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java index 44b20e535cce..b5209bbbdd21 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java @@ -22,8 +22,8 @@ import android.content.Context; import android.graphics.drawable.Icon; import android.util.AttributeSet; import android.util.Log; +import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; import com.android.systemui.R; @@ -31,7 +31,7 @@ import com.android.systemui.R; /** * View for a chip with an icon and text. */ -public class ScreenshotActionChip extends LinearLayout { +public class ScreenshotActionChip extends FrameLayout { private static final String TAG = "ScreenshotActionChip"; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java index cdd1280dd86c..379bb9d247db 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -18,15 +18,10 @@ package com.android.systemui.stackdivider; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED; -import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.content.Context; @@ -36,15 +31,11 @@ import android.os.Handler; import android.provider.Settings; import android.util.Slog; import android.view.LayoutInflater; -import android.view.SurfaceControl; import android.view.View; -import android.window.TaskOrganizer; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import android.window.WindowOrganizer; -import androidx.annotation.Nullable; - import com.android.internal.policy.DividerSnapAlgorithm; import com.android.systemui.R; import com.android.systemui.SystemUI; @@ -81,7 +72,6 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, static final boolean DEBUG = false; static final int DEFAULT_APP_TRANSITION_DURATION = 336; - static final float ADJUSTED_NONFOCUS_DIM = 0.3f; private final Optional<Lazy<Recents>> mRecentsOptionalLazy; @@ -139,303 +129,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, } }; - private class DividerImeController implements DisplayImeController.ImePositionProcessor { - /** - * These are the y positions of the top of the IME surface when it is hidden and when it is - * shown respectively. These are NOT necessarily the top of the visible IME itself. - */ - private int mHiddenTop = 0; - private int mShownTop = 0; - - // The following are target states (what we are curretly animating towards). - /** - * {@code true} if, at the end of the animation, the split task positions should be - * adjusted by height of the IME. This happens when the secondary split is the IME target. - */ - private boolean mTargetAdjusted = false; - /** - * {@code true} if, at the end of the animation, the IME should be shown/visible - * regardless of what has focus. - */ - private boolean mTargetShown = false; - private float mTargetPrimaryDim = 0.f; - private float mTargetSecondaryDim = 0.f; - - // The following are the current (most recent) states set during animation - /** {@code true} if the secondary split has IME focus. */ - private boolean mSecondaryHasFocus = false; - /** The dimming currently applied to the primary/secondary splits. */ - private float mLastPrimaryDim = 0.f; - private float mLastSecondaryDim = 0.f; - /** The most recent y position of the top of the IME surface */ - private int mLastAdjustTop = -1; - - // The following are states reached last time an animation fully completed. - /** {@code true} if the IME was shown/visible by the last-completed animation. */ - private boolean mImeWasShown = false; - /** {@code true} if the split positions were adjusted by the last-completed animation. */ - private boolean mAdjusted = false; - - /** - * When some aspect of split-screen needs to animate independent from the IME, - * this will be non-null and control split animation. - */ - @Nullable - private ValueAnimator mAnimation = null; - - private boolean mPaused = true; - private boolean mPausedTargetAdjusted = false; - - private boolean getSecondaryHasFocus(int displayId) { - WindowContainerToken imeSplit = TaskOrganizer.getImeTarget(displayId); - return imeSplit != null - && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder()); - } - - private void updateDimTargets() { - final boolean splitIsVisible = !mView.isHidden(); - mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible) - ? ADJUSTED_NONFOCUS_DIM : 0.f; - mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible) - ? ADJUSTED_NONFOCUS_DIM : 0.f; - } - - @Override - public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop, - boolean imeShouldShow, SurfaceControl.Transaction t) { - if (!isDividerVisible()) { - return; - } - final boolean splitIsVisible = !mView.isHidden(); - mSecondaryHasFocus = getSecondaryHasFocus(displayId); - final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus - && !mSplitLayout.mDisplayLayout.isLandscape(); - mHiddenTop = hiddenTop; - mShownTop = shownTop; - mTargetShown = imeShouldShow; - if (mLastAdjustTop < 0) { - mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop; - } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) { - if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) { - // Check for an "interruption" of an existing animation. In this case, we - // need to fake-flip the last-known state direction so that the animation - // completes in the other direction. - mAdjusted = mTargetAdjusted; - } else if (targetAdjusted && mTargetAdjusted && mAdjusted) { - // Already fully adjusted for IME, but IME height has changed; so, force-start - // an async animation to the new IME height. - mAdjusted = false; - } - } - if (mPaused) { - mPausedTargetAdjusted = targetAdjusted; - if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState()); - return; - } - mTargetAdjusted = targetAdjusted; - updateDimTargets(); - if (DEBUG) Slog.d(TAG, " ime starting. vis:" + splitIsVisible + " " + dumpState()); - if (mAnimation != null || (mImeWasShown && imeShouldShow - && mTargetAdjusted != mAdjusted)) { - // We need to animate adjustment independently of the IME position, so - // start our own animation to drive adjustment. This happens when a - // different split's editor has gained focus while the IME is still visible. - startAsyncAnimation(); - } - if (splitIsVisible) { - // If split is hidden, we don't want to trigger any relayouts that would cause the - // divider to show again. - updateImeAdjustState(); - } - } - - private void updateImeAdjustState() { - // Reposition the server's secondary split position so that it evaluates - // insets properly. - WindowContainerTransaction wct = new WindowContainerTransaction(); - if (mTargetAdjusted) { - mSplitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop); - wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mAdjustedSecondary); - // "Freeze" the configuration size so that the app doesn't get a config - // or relaunch. This is required because normally nav-bar contributes - // to configuration bounds (via nondecorframe). - Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration - .windowConfiguration.getAppBounds()); - adjustAppBounds.offset(0, mSplitLayout.mAdjustedSecondary.top - - mSplitLayout.mSecondary.top); - wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds); - wct.setScreenSizeDp(mSplits.mSecondary.token, - mSplits.mSecondary.configuration.screenWidthDp, - mSplits.mSecondary.configuration.screenHeightDp); - - wct.setBounds(mSplits.mPrimary.token, mSplitLayout.mAdjustedPrimary); - adjustAppBounds = new Rect(mSplits.mPrimary.configuration - .windowConfiguration.getAppBounds()); - adjustAppBounds.offset(0, mSplitLayout.mAdjustedPrimary.top - - mSplitLayout.mPrimary.top); - wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds); - wct.setScreenSizeDp(mSplits.mPrimary.token, - mSplits.mPrimary.configuration.screenWidthDp, - mSplits.mPrimary.configuration.screenHeightDp); - } else { - wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary); - wct.setAppBounds(mSplits.mSecondary.token, null); - wct.setScreenSizeDp(mSplits.mSecondary.token, - SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); - wct.setBounds(mSplits.mPrimary.token, mSplitLayout.mPrimary); - wct.setAppBounds(mSplits.mPrimary.token, null); - wct.setScreenSizeDp(mSplits.mPrimary.token, - SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); - } - - WindowOrganizer.applyTransaction(wct); - - // Update all the adjusted-for-ime states - if (!mPaused) { - mView.setAdjustedForIme(mTargetShown, mTargetShown - ? DisplayImeController.ANIMATION_DURATION_SHOW_MS - : DisplayImeController.ANIMATION_DURATION_HIDE_MS); - } - setAdjustedForIme(mTargetShown && !mPaused); - } - - @Override - public void onImePositionChanged(int displayId, int imeTop, - SurfaceControl.Transaction t) { - if (mAnimation != null || !isDividerVisible() || mPaused) { - // Not synchronized with IME anymore, so return. - return; - } - final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop); - final float progress = mTargetShown ? fraction : 1.f - fraction; - onProgress(progress, t); - } - - @Override - public void onImeEndPositioning(int displayId, boolean cancelled, - SurfaceControl.Transaction t) { - if (mAnimation != null || !isDividerVisible() || mPaused) { - // Not synchronized with IME anymore, so return. - return; - } - onEnd(cancelled, t); - } - - private void onProgress(float progress, SurfaceControl.Transaction t) { - if (mTargetAdjusted != mAdjusted && !mPaused) { - final float fraction = mTargetAdjusted ? progress : 1.f - progress; - mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop); - mSplitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop); - mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary, - mSplitLayout.mAdjustedSecondary); - } - final float invProg = 1.f - progress; - mView.setResizeDimLayer(t, true /* primary */, - mLastPrimaryDim * invProg + progress * mTargetPrimaryDim); - mView.setResizeDimLayer(t, false /* primary */, - mLastSecondaryDim * invProg + progress * mTargetSecondaryDim); - } - - private void onEnd(boolean cancelled, SurfaceControl.Transaction t) { - if (!cancelled) { - onProgress(1.f, t); - mAdjusted = mTargetAdjusted; - mImeWasShown = mTargetShown; - mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop; - mLastPrimaryDim = mTargetPrimaryDim; - mLastSecondaryDim = mTargetSecondaryDim; - } - } - - private void startAsyncAnimation() { - if (mAnimation != null) { - mAnimation.cancel(); - } - mAnimation = ValueAnimator.ofFloat(0.f, 1.f); - mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS); - if (mTargetAdjusted != mAdjusted) { - final float fraction = - ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop); - final float progress = mTargetAdjusted ? fraction : 1.f - fraction; - mAnimation.setCurrentFraction(progress); - } - - mAnimation.addUpdateListener(animation -> { - SurfaceControl.Transaction t = mTransactionPool.acquire(); - float value = (float) animation.getAnimatedValue(); - onProgress(value, t); - t.apply(); - mTransactionPool.release(t); - }); - mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR); - mAnimation.addListener(new AnimatorListenerAdapter() { - private boolean mCancel = false; - @Override - public void onAnimationCancel(Animator animation) { - mCancel = true; - } - @Override - public void onAnimationEnd(Animator animation) { - SurfaceControl.Transaction t = mTransactionPool.acquire(); - onEnd(mCancel, t); - t.apply(); - mTransactionPool.release(t); - mAnimation = null; - } - }); - mAnimation.start(); - } - - private String dumpState() { - return "top:" + mHiddenTop + "->" + mShownTop - + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")" - + " shw:" + mImeWasShown + "->" + mTargetShown - + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim - + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim - + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null) - + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]"; - } - - /** Completely aborts/resets adjustment state */ - public void pause(int displayId) { - if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState()); - mHandler.post(() -> { - if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState()); - if (mPaused) { - return; - } - mPaused = true; - mPausedTargetAdjusted = mTargetAdjusted; - mTargetAdjusted = false; - mTargetPrimaryDim = mTargetSecondaryDim = 0.f; - updateImeAdjustState(); - startAsyncAnimation(); - if (mAnimation != null) { - mAnimation.end(); - } - }); - } - - public void resume(int displayId) { - if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState()); - mHandler.post(() -> { - if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState()); - if (!mPaused) { - return; - } - mPaused = false; - mTargetAdjusted = mPausedTargetAdjusted; - updateDimTargets(); - if ((mTargetAdjusted != mAdjusted) && !mMinimized && mView != null) { - // End unminimize animations since they conflict with adjustment animations. - mView.finishAnimations(); - } - updateImeAdjustState(); - startAsyncAnimation(); - }); - } - } - private final DividerImeController mImePositionProcessor = new DividerImeController(); + private final DividerImeController mImePositionProcessor; private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() { @Override @@ -465,6 +159,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, mRecentsOptionalLazy = recentsOptionalLazy; mForcedResizableController = new ForcedResizableInfoActivityController(context, this); mTransactionPool = transactionPool; + mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler); } @Override @@ -830,6 +525,10 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, } } + SplitDisplayLayout getSplitLayout() { + return mSplitLayout; + } + /** @return the container token for the secondary split root task. */ public WindowContainerToken getSecondaryRoot() { if (mSplits == null || mSplits.mSecondary == null) { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java new file mode 100644 index 000000000000..533fd5fa6352 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java @@ -0,0 +1,368 @@ +/* + * 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.stackdivider; + +import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED; +import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.graphics.Rect; +import android.os.Handler; +import android.util.Slog; +import android.view.SurfaceControl; +import android.window.TaskOrganizer; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; +import android.window.WindowOrganizer; + +import androidx.annotation.Nullable; + +import com.android.systemui.TransactionPool; +import com.android.systemui.wm.DisplayImeController; + +class DividerImeController implements DisplayImeController.ImePositionProcessor { + private static final String TAG = "DividerImeController"; + private static final boolean DEBUG = Divider.DEBUG; + + private static final float ADJUSTED_NONFOCUS_DIM = 0.3f; + + private final SplitScreenTaskOrganizer mSplits; + private final TransactionPool mTransactionPool; + private final Handler mHandler; + + /** + * These are the y positions of the top of the IME surface when it is hidden and when it is + * shown respectively. These are NOT necessarily the top of the visible IME itself. + */ + private int mHiddenTop = 0; + private int mShownTop = 0; + + // The following are target states (what we are curretly animating towards). + /** + * {@code true} if, at the end of the animation, the split task positions should be + * adjusted by height of the IME. This happens when the secondary split is the IME target. + */ + private boolean mTargetAdjusted = false; + /** + * {@code true} if, at the end of the animation, the IME should be shown/visible + * regardless of what has focus. + */ + private boolean mTargetShown = false; + private float mTargetPrimaryDim = 0.f; + private float mTargetSecondaryDim = 0.f; + + // The following are the current (most recent) states set during animation + /** {@code true} if the secondary split has IME focus. */ + private boolean mSecondaryHasFocus = false; + /** The dimming currently applied to the primary/secondary splits. */ + private float mLastPrimaryDim = 0.f; + private float mLastSecondaryDim = 0.f; + /** The most recent y position of the top of the IME surface */ + private int mLastAdjustTop = -1; + + // The following are states reached last time an animation fully completed. + /** {@code true} if the IME was shown/visible by the last-completed animation. */ + private boolean mImeWasShown = false; + /** {@code true} if the split positions were adjusted by the last-completed animation. */ + private boolean mAdjusted = false; + + /** + * When some aspect of split-screen needs to animate independent from the IME, + * this will be non-null and control split animation. + */ + @Nullable + private ValueAnimator mAnimation = null; + + private boolean mPaused = true; + private boolean mPausedTargetAdjusted = false; + + DividerImeController(SplitScreenTaskOrganizer splits, TransactionPool pool, Handler handler) { + mSplits = splits; + mTransactionPool = pool; + mHandler = handler; + } + + private DividerView getView() { + return mSplits.mDivider.getView(); + } + + private SplitDisplayLayout getLayout() { + return mSplits.mDivider.getSplitLayout(); + } + + private boolean isDividerVisible() { + return mSplits.mDivider.isDividerVisible(); + } + + private boolean getSecondaryHasFocus(int displayId) { + WindowContainerToken imeSplit = TaskOrganizer.getImeTarget(displayId); + return imeSplit != null + && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder()); + } + + private void updateDimTargets() { + final boolean splitIsVisible = !getView().isHidden(); + mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible) + ? ADJUSTED_NONFOCUS_DIM : 0.f; + mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible) + ? ADJUSTED_NONFOCUS_DIM : 0.f; + } + + @Override + public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop, + boolean imeShouldShow, SurfaceControl.Transaction t) { + if (!isDividerVisible()) { + return; + } + final boolean splitIsVisible = !getView().isHidden(); + mSecondaryHasFocus = getSecondaryHasFocus(displayId); + final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus + && !getLayout().mDisplayLayout.isLandscape(); + mHiddenTop = hiddenTop; + mShownTop = shownTop; + mTargetShown = imeShouldShow; + if (mLastAdjustTop < 0) { + mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop; + } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) { + if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) { + // Check for an "interruption" of an existing animation. In this case, we + // need to fake-flip the last-known state direction so that the animation + // completes in the other direction. + mAdjusted = mTargetAdjusted; + } else if (targetAdjusted && mTargetAdjusted && mAdjusted) { + // Already fully adjusted for IME, but IME height has changed; so, force-start + // an async animation to the new IME height. + mAdjusted = false; + } + } + if (mPaused) { + mPausedTargetAdjusted = targetAdjusted; + if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState()); + return; + } + mTargetAdjusted = targetAdjusted; + updateDimTargets(); + if (DEBUG) Slog.d(TAG, " ime starting. vis:" + splitIsVisible + " " + dumpState()); + if (mAnimation != null || (mImeWasShown && imeShouldShow + && mTargetAdjusted != mAdjusted)) { + // We need to animate adjustment independently of the IME position, so + // start our own animation to drive adjustment. This happens when a + // different split's editor has gained focus while the IME is still visible. + startAsyncAnimation(); + } + if (splitIsVisible) { + // If split is hidden, we don't want to trigger any relayouts that would cause the + // divider to show again. + updateImeAdjustState(); + } + } + + private void updateImeAdjustState() { + // Reposition the server's secondary split position so that it evaluates + // insets properly. + WindowContainerTransaction wct = new WindowContainerTransaction(); + final SplitDisplayLayout splitLayout = getLayout(); + if (mTargetAdjusted) { + splitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop); + wct.setBounds(mSplits.mSecondary.token, splitLayout.mAdjustedSecondary); + // "Freeze" the configuration size so that the app doesn't get a config + // or relaunch. This is required because normally nav-bar contributes + // to configuration bounds (via nondecorframe). + Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration + .windowConfiguration.getAppBounds()); + adjustAppBounds.offset(0, splitLayout.mAdjustedSecondary.top + - splitLayout.mSecondary.top); + wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds); + wct.setScreenSizeDp(mSplits.mSecondary.token, + mSplits.mSecondary.configuration.screenWidthDp, + mSplits.mSecondary.configuration.screenHeightDp); + + wct.setBounds(mSplits.mPrimary.token, splitLayout.mAdjustedPrimary); + adjustAppBounds = new Rect(mSplits.mPrimary.configuration + .windowConfiguration.getAppBounds()); + adjustAppBounds.offset(0, splitLayout.mAdjustedPrimary.top + - splitLayout.mPrimary.top); + wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds); + wct.setScreenSizeDp(mSplits.mPrimary.token, + mSplits.mPrimary.configuration.screenWidthDp, + mSplits.mPrimary.configuration.screenHeightDp); + } else { + wct.setBounds(mSplits.mSecondary.token, splitLayout.mSecondary); + wct.setAppBounds(mSplits.mSecondary.token, null); + wct.setScreenSizeDp(mSplits.mSecondary.token, + SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); + wct.setBounds(mSplits.mPrimary.token, splitLayout.mPrimary); + wct.setAppBounds(mSplits.mPrimary.token, null); + wct.setScreenSizeDp(mSplits.mPrimary.token, + SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); + } + + WindowOrganizer.applyTransaction(wct); + + // Update all the adjusted-for-ime states + if (!mPaused) { + final DividerView view = getView(); + if (view != null) { + view.setAdjustedForIme(mTargetShown, mTargetShown + ? DisplayImeController.ANIMATION_DURATION_SHOW_MS + : DisplayImeController.ANIMATION_DURATION_HIDE_MS); + } + } + mSplits.mDivider.setAdjustedForIme(mTargetShown && !mPaused); + } + + @Override + public void onImePositionChanged(int displayId, int imeTop, + SurfaceControl.Transaction t) { + if (mAnimation != null || !isDividerVisible() || mPaused) { + // Not synchronized with IME anymore, so return. + return; + } + final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop); + final float progress = mTargetShown ? fraction : 1.f - fraction; + onProgress(progress, t); + } + + @Override + public void onImeEndPositioning(int displayId, boolean cancelled, + SurfaceControl.Transaction t) { + if (mAnimation != null || !isDividerVisible() || mPaused) { + // Not synchronized with IME anymore, so return. + return; + } + onEnd(cancelled, t); + } + + private void onProgress(float progress, SurfaceControl.Transaction t) { + final DividerView view = getView(); + if (mTargetAdjusted != mAdjusted && !mPaused) { + final SplitDisplayLayout splitLayout = getLayout(); + final float fraction = mTargetAdjusted ? progress : 1.f - progress; + mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop); + splitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop); + view.resizeSplitSurfaces(t, splitLayout.mAdjustedPrimary, + splitLayout.mAdjustedSecondary); + } + final float invProg = 1.f - progress; + view.setResizeDimLayer(t, true /* primary */, + mLastPrimaryDim * invProg + progress * mTargetPrimaryDim); + view.setResizeDimLayer(t, false /* primary */, + mLastSecondaryDim * invProg + progress * mTargetSecondaryDim); + } + + private void onEnd(boolean cancelled, SurfaceControl.Transaction t) { + if (!cancelled) { + onProgress(1.f, t); + mAdjusted = mTargetAdjusted; + mImeWasShown = mTargetShown; + mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop; + mLastPrimaryDim = mTargetPrimaryDim; + mLastSecondaryDim = mTargetSecondaryDim; + } + } + + private void startAsyncAnimation() { + if (mAnimation != null) { + mAnimation.cancel(); + } + mAnimation = ValueAnimator.ofFloat(0.f, 1.f); + mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS); + if (mTargetAdjusted != mAdjusted) { + final float fraction = + ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop); + final float progress = mTargetAdjusted ? fraction : 1.f - fraction; + mAnimation.setCurrentFraction(progress); + } + + mAnimation.addUpdateListener(animation -> { + SurfaceControl.Transaction t = mTransactionPool.acquire(); + float value = (float) animation.getAnimatedValue(); + onProgress(value, t); + t.apply(); + mTransactionPool.release(t); + }); + mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR); + mAnimation.addListener(new AnimatorListenerAdapter() { + private boolean mCancel = false; + @Override + public void onAnimationCancel(Animator animation) { + mCancel = true; + } + @Override + public void onAnimationEnd(Animator animation) { + SurfaceControl.Transaction t = mTransactionPool.acquire(); + onEnd(mCancel, t); + t.apply(); + mTransactionPool.release(t); + mAnimation = null; + } + }); + mAnimation.start(); + } + + private String dumpState() { + return "top:" + mHiddenTop + "->" + mShownTop + + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")" + + " shw:" + mImeWasShown + "->" + mTargetShown + + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim + + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim + + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null) + + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]"; + } + + /** Completely aborts/resets adjustment state */ + public void pause(int displayId) { + if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState()); + mHandler.post(() -> { + if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState()); + if (mPaused) { + return; + } + mPaused = true; + mPausedTargetAdjusted = mTargetAdjusted; + mTargetAdjusted = false; + mTargetPrimaryDim = mTargetSecondaryDim = 0.f; + updateImeAdjustState(); + startAsyncAnimation(); + if (mAnimation != null) { + mAnimation.end(); + } + }); + } + + public void resume(int displayId) { + if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState()); + mHandler.post(() -> { + if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState()); + if (!mPaused) { + return; + } + mPaused = false; + mTargetAdjusted = mPausedTargetAdjusted; + updateDimTargets(); + final DividerView view = getView(); + if ((mTargetAdjusted != mAdjusted) && !mSplits.mDivider.isMinimized() && view != null) { + // End unminimize animations since they conflict with adjustment animations. + view.finishAnimations(); + } + updateImeAdjustState(); + startAsyncAnimation(); + }); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index db89cea385b7..8ca50cdddf71 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -19,7 +19,6 @@ package com.android.systemui.stackdivider; import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; import static android.view.WindowManager.DOCKED_RIGHT; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; import android.animation.AnimationHandler; import android.animation.Animator; @@ -39,7 +38,6 @@ import android.os.RemoteException; import android.util.AttributeSet; import android.util.Slog; import android.view.Display; -import android.view.InsetsState; import android.view.MotionEvent; import android.view.PointerIcon; import android.view.SurfaceControl; @@ -48,10 +46,8 @@ import android.view.VelocityTracker; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewConfiguration; -import android.view.ViewRootImpl; import android.view.ViewTreeObserver.InternalInsetsInfo; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; -import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -65,9 +61,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; import com.android.internal.policy.DockedDividerUtils; +import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.shared.system.WindowManagerWrapper; +import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.FlingAnimationUtils; import java.util.function.Consumer; @@ -120,7 +117,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, private int mStartY; private int mStartPosition; private int mDockSide; - private final int[] mTempInt2 = new int[2]; private boolean mMoving; private int mTouchSlop; private boolean mBackgroundLifted; @@ -148,7 +144,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, private FlingAnimationUtils mFlingAnimationUtils; private SplitDisplayLayout mSplitLayout; private DividerCallbacks mCallback; - private final Rect mStableInsets = new Rect(); private final AnimationHandler mAnimationHandler = new AnimationHandler(); private boolean mGrowRecents; @@ -336,29 +331,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, } @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - if (isAttachedToWindow() - && ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL) { - // Our window doesn't cover entire display, so we use the display frame to re-calculate - // the insets. - final InsetsState state = getWindowInsetsController().getState(); - insets = state.calculateInsets(state.getDisplayFrame(), - null /* ignoringVisibilityState */, insets.isRound(), - insets.shouldAlwaysConsumeSystemBars(), insets.getDisplayCutout(), - 0 /* legacySystemUiFlags */, - SOFT_INPUT_ADJUST_NOTHING, null /* typeSideMap */); - } - if (mStableInsets.left != insets.getStableInsetLeft() - || mStableInsets.top != insets.getStableInsetTop() - || mStableInsets.right != insets.getStableInsetRight() - || mStableInsets.bottom != insets.getStableInsetBottom()) { - mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(), - insets.getStableInsetRight(), insets.getStableInsetBottom()); - } - return super.onApplyWindowInsets(insets); - } - - @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mFirstLayout) { @@ -381,6 +353,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, if (changed) { mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), mHandle.getBottom())); + notifySplitScreenBoundsChanged(); } } @@ -405,19 +378,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, } public Rect getNonMinimizedSplitScreenSecondaryBounds() { - calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, - DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect); - mOtherTaskRect.bottom -= mStableInsets.bottom; - switch (mDockSide) { - case WindowManager.DOCKED_LEFT: - mOtherTaskRect.top += mStableInsets.top; - mOtherTaskRect.right -= mStableInsets.right; - break; - case WindowManager.DOCKED_RIGHT: - mOtherTaskRect.top += mStableInsets.top; - mOtherTaskRect.left += mStableInsets.left; - break; - } + mOtherTaskRect.set(mSplitLayout.mSecondary); return mOtherTaskRect; } @@ -681,6 +642,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, saveSnapTargetBeforeMinimized(saveTarget); } } + notifySplitScreenBoundsChanged(); }; anim.addListener(new AnimatorListenerAdapter() { @@ -713,6 +675,25 @@ public class DividerView extends FrameLayout implements OnTouchListener, return anim; } + private void notifySplitScreenBoundsChanged() { + mOtherTaskRect.set(mSplitLayout.mSecondary); + + mTmpRect.set(mSplitLayout.mDisplayLayout.stableInsets()); + switch (mSplitLayout.getPrimarySplitSide()) { + case WindowManager.DOCKED_LEFT: + mTmpRect.left = 0; + break; + case WindowManager.DOCKED_RIGHT: + mTmpRect.right = 0; + break; + case WindowManager.DOCKED_TOP: + mTmpRect.top = 0; + break; + } + Dependency.get(OverviewProxyService.class) + .notifySplitScreenBoundsChanged(mOtherTaskRect, mTmpRect); + } + private void cancelFlingAnimation() { if (mCurrentAnimator != null) { mCurrentAnimator.cancel(); @@ -846,8 +827,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, mDockedStackMinimized = minimized; if (mSplitLayout.mDisplayLayout.rotation() != mDefaultDisplay.getRotation()) { // Splitscreen to minimize is about to starts after rotating landscape to seascape, - // update insets, display info and snap algorithm targets - WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); + // update display info and snap algorithm targets repositionSnapTargetBeforeMinimized(); } if (mIsInMinimizeInteraction != minimized || mCurrentAnimator != null) { @@ -1149,7 +1129,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, // Move a right-docked-app to line up with the divider while dragging it if (mDockSide == DOCKED_RIGHT) { - mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize) + mDockedTaskRect.offset(Math.max(position, -mDividerSize) - mDockedTaskRect.left + mDividerSize, 0); } resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, @@ -1164,7 +1144,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, // Move a docked app if from the right in position with the divider up to insets if (mDockSide == DOCKED_RIGHT) { - mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize) + mDockedTaskRect.offset(Math.max(position, -mDividerSize) - mDockedTaskRect.left + mDividerSize, 0); } calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide), @@ -1180,7 +1160,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, // Move a right-docked-app to line up with the divider while dragging it if (mDockSide == DOCKED_RIGHT) { - mDockedTaskRect.offset(position - mStableInsets.left + mDividerSize, 0); + mDockedTaskRect.offset(position + mDividerSize, 0); } resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); } else if (taskPosition != TASK_POSITION_SAME) { @@ -1238,34 +1218,10 @@ public class DividerView extends FrameLayout implements OnTouchListener, float fraction = getSnapAlgorithm().calculateDismissingFraction(position); fraction = Math.max(0, Math.min(fraction, 1f)); fraction = DIM_INTERPOLATOR.getInterpolation(fraction); - if (hasInsetsAtDismissTarget(dismissTarget)) { - - // Less darkening with system insets. - fraction *= 0.8f; - } return fraction; } /** - * @return true if and only if there are system insets at the location of the dismiss target - */ - private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) { - if (isHorizontalDivision()) { - if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) { - return mStableInsets.top != 0; - } else { - return mStableInsets.bottom != 0; - } - } else { - if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) { - return mStableInsets.left != 0; - } else { - return mStableInsets.right != 0; - } - } - } - - /** * When the snap target is dismissing one side, make sure that the dismissing side doesn't get * 0 size. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 9f4932e74eaa..8ed69d8fb982 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; +import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON; import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_MEDIA_FAKE_ARTWORK; import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_LOCKSCREEN_WALLPAPER; import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK; @@ -44,8 +45,6 @@ import android.util.Log; import android.view.View; import android.widget.ImageView; -import androidx.annotation.NonNull; - import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dependency; @@ -53,8 +52,8 @@ import com.android.systemui.Dumpable; import com.android.systemui.Interpolators; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.media.MediaData; import com.android.systemui.media.MediaDataManager; +import com.android.systemui.media.MediaDeviceManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.notification.NotificationEntryListener; @@ -70,6 +69,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.Utils; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -79,7 +79,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import dagger.Lazy; @@ -90,6 +90,7 @@ import dagger.Lazy; public class NotificationMediaManager implements Dumpable { private static final String TAG = "NotificationMediaManager"; public static final boolean DEBUG_MEDIA = false; + private static final long PAUSED_MEDIA_TIMEOUT = TimeUnit.MINUTES.toMillis(10); private final StatusBarStateController mStatusBarStateController = Dependency.get(StatusBarStateController.class); @@ -106,6 +107,7 @@ public class NotificationMediaManager implements Dumpable { } private final NotificationEntryManager mEntryManager; + private final MediaDataManager mMediaDataManager; @Nullable private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController; @@ -117,7 +119,7 @@ public class NotificationMediaManager implements Dumpable { @Nullable private LockscreenWallpaper mLockscreenWallpaper; - private final Executor mMainExecutor; + private final DelayableExecutor mMainExecutor; private final Context mContext; private final MediaSessionManager mMediaSessionManager; @@ -130,6 +132,7 @@ public class NotificationMediaManager implements Dumpable { private MediaController mMediaController; private String mMediaNotificationKey; private MediaMetadata mMediaMetadata; + private Runnable mMediaTimeoutCancellation; private BackDropView mBackdrop; private ImageView mBackdropFront; @@ -159,11 +162,36 @@ public class NotificationMediaManager implements Dumpable { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state); } + if (mMediaTimeoutCancellation != null) { + mMediaTimeoutCancellation.run(); + mMediaTimeoutCancellation = null; + } if (state != null) { if (!isPlaybackActive(state.getState())) { clearCurrentMediaNotification(); } findAndUpdateMediaNotifications(); + scheduleMediaTimeout(state); + } + } + + private void scheduleMediaTimeout(PlaybackState state) { + final NotificationEntry entry; + synchronized (mEntryManager) { + entry = mEntryManager.getActiveNotificationUnfiltered(mMediaNotificationKey); + } + if (entry != null) { + if (!isPlayingState(state.getState())) { + mMediaTimeoutCancellation = mMainExecutor.executeDelayed(() -> { + synchronized (mEntryManager) { + if (mMediaNotificationKey == null) { + return; + } + mEntryManager.removeNotification(mMediaNotificationKey, null, + UNDEFINED_DISMISS_REASON); + } + }, PAUSED_MEDIA_TIMEOUT); + } } } @@ -189,9 +217,10 @@ public class NotificationMediaManager implements Dumpable { NotificationEntryManager notificationEntryManager, MediaArtworkProcessor mediaArtworkProcessor, KeyguardBypassController keyguardBypassController, - @Main Executor mainExecutor, + @Main DelayableExecutor mainExecutor, DeviceConfigProxy deviceConfig, - MediaDataManager mediaDataManager) { + MediaDataManager mediaDataManager, + MediaDeviceManager mediaDeviceManager) { mContext = context; mMediaArtworkProcessor = mediaArtworkProcessor; mKeyguardBypassController = keyguardBypassController; @@ -205,17 +234,20 @@ public class NotificationMediaManager implements Dumpable { mNotificationShadeWindowController = notificationShadeWindowController; mEntryManager = notificationEntryManager; mMainExecutor = mainExecutor; + mMediaDataManager = mediaDataManager; notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override public void onPendingEntryAdded(NotificationEntry entry) { mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); + mediaDeviceManager.onNotificationAdded(entry.getKey(), entry.getSbn()); } @Override public void onPreEntryUpdated(NotificationEntry entry) { mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); + mediaDeviceManager.onNotificationAdded(entry.getKey(), entry.getSbn()); } @Override @@ -236,6 +268,7 @@ public class NotificationMediaManager implements Dumpable { int reason) { onNotificationRemoved(entry.getKey()); mediaDataManager.onNotificationRemoved(entry.getKey()); + mediaDeviceManager.onNotificationRemoved(entry.getKey()); } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index b08eb9fafe41..ac2a9c118672 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -24,6 +24,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.MediaDataManager; +import com.android.systemui.media.MediaDeviceManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; @@ -47,6 +48,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.concurrent.Executor; @@ -98,9 +100,10 @@ public interface StatusBarDependenciesModule { NotificationEntryManager notificationEntryManager, MediaArtworkProcessor mediaArtworkProcessor, KeyguardBypassController keyguardBypassController, - @Main Executor mainExecutor, + @Main DelayableExecutor mainExecutor, DeviceConfigProxy deviceConfigProxy, - MediaDataManager mediaDataManager) { + MediaDataManager mediaDataManager, + MediaDeviceManager mediaDeviceManager) { return new NotificationMediaManager( context, statusBarLazy, @@ -110,7 +113,8 @@ public interface StatusBarDependenciesModule { keyguardBypassController, mainExecutor, deviceConfigProxy, - mediaDataManager); + mediaDataManager, + mediaDeviceManager); } /** */ 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 2be1531850c5..d6471243e053 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -151,16 +151,6 @@ public class NotificationEntryManager implements @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NotificationEntryManager state:"); - pw.println(" mAllNotifications="); - if (mAllNotifications.size() == 0) { - pw.println("null"); - } else { - int i = 0; - for (NotificationEntry entry : mAllNotifications) { - dumpEntry(pw, " ", i, entry); - i++; - } - } pw.print(" mPendingNotifications="); if (mPendingNotifications.size() == 0) { pw.println("null"); @@ -360,8 +350,8 @@ public class NotificationEntryManager implements private final NotificationHandler mNotifListener = new NotificationHandler() { @Override public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { - final boolean isUpdateToInflatedNotif = mActiveNotifications.containsKey(sbn.getKey()); - if (isUpdateToInflatedNotif) { + final boolean isUpdate = mActiveNotifications.containsKey(sbn.getKey()); + if (isUpdate) { updateNotification(sbn, rankingMap); } else { addNotification(sbn, rankingMap); @@ -452,12 +442,16 @@ public class NotificationEntryManager implements } if (!lifetimeExtended) { // At this point, we are guaranteed the notification will be removed - abortExistingInflation(key, "removeNotification"); mAllNotifications.remove(pendingEntry); - mLeakDetector.trackGarbage(pendingEntry); } } - } else { + } + + if (!lifetimeExtended) { + abortExistingInflation(key, "removeNotification"); + } + + if (entry != null) { // If a manager needs to keep the notification around for whatever reason, we // keep the notification boolean entryDismissed = entry.isRowDismissed(); @@ -475,8 +469,6 @@ public class NotificationEntryManager implements if (!lifetimeExtended) { // At this point, we are guaranteed the notification will be removed - abortExistingInflation(key, "removeNotification"); - mAllNotifications.remove(entry); // Ensure any managers keeping the lifetime extended stop managing the entry cancelLifetimeExtension(entry); @@ -485,10 +477,13 @@ public class NotificationEntryManager implements entry.removeRow(); } + mAllNotifications.remove(entry); + // Let's remove the children if this was a summary handleGroupSummaryRemoved(key); removeVisibleNotification(key); updateNotifications("removeNotificationInternal"); + mLeakDetector.trackGarbage(entry); removedByUser |= entryDismissed; mLogger.logNotifRemoved(entry.getKey(), removedByUser); @@ -502,7 +497,6 @@ public class NotificationEntryManager implements for (NotifCollectionListener listener : mNotifCollectionListeners) { listener.onEntryCleanUp(entry); } - mLeakDetector.trackGarbage(entry); } } } @@ -562,24 +556,17 @@ public class NotificationEntryManager implements Ranking ranking = new Ranking(); rankingMap.getRanking(key, ranking); - NotificationEntry entry = mPendingNotifications.get(key); - if (entry != null) { - entry.setSbn(notification); - } else { - entry = new NotificationEntry( - notification, - ranking, - mFgsFeatureController.isForegroundServiceDismissalEnabled(), - SystemClock.uptimeMillis()); - mAllNotifications.add(entry); - mLeakDetector.trackInstance(entry); - } - - abortExistingInflation(key, "addNotification"); - + NotificationEntry entry = new NotificationEntry( + notification, + ranking, + mFgsFeatureController.isForegroundServiceDismissalEnabled(), + SystemClock.uptimeMillis()); for (NotifCollectionListener listener : mNotifCollectionListeners) { listener.onEntryBind(entry, notification); } + mAllNotifications.add(entry); + + mLeakDetector.trackInstance(entry); for (NotifCollectionListener listener : mNotifCollectionListeners) { listener.onEntryInit(entry); @@ -594,6 +581,7 @@ public class NotificationEntryManager implements mInflationCallback); } + abortExistingInflation(key, "addNotification"); mPendingNotifications.put(key, entry); mLogger.logNotifAdded(entry.getKey()); for (NotificationEntryListener listener : mNotificationEntryListeners) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java index b1b6a1c12a0a..3517e245f624 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java @@ -92,6 +92,9 @@ public class PropertyAnimator { AnimatorListenerAdapter listener = properties.getAnimationFinishListener(property); if (currentValue.equals(newEndValue)) { // Skip the animation! + if (previousAnimator != null) { + previousAnimator.cancel(); + } if (listener != null) { listener.onAnimationEnd(null); } 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 3ee267362bc0..90d30dcf38ab 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 @@ -154,12 +154,14 @@ public final class NotifBindPipeline { * 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); if (bindEntry.row == null) { // Row is not managed yet but may be soon. Stop for now. + mLogger.logRequestPipelineRowNotSet(entry.getKey()); return; } - mLogger.logRequestPipelineRun(entry.getKey()); // Abort any existing pipeline run mStage.abortStage(entry, bindEntry.row); 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 199730427aec..f26598db27a5 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 @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.dagger.NotificationLog import javax.inject.Inject @@ -48,6 +49,14 @@ class NotifBindPipelineLogger @Inject constructor( }) } + fun logRequestPipelineRowNotSet(notifKey: String) { + buffer.log(TAG, WARNING, { + str1 = notifKey + }, { + "Row is not set so pipeline will not run. notif = $str1" + }) + } + fun logStartPipeline(notifKey: String) { buffer.log(TAG, INFO, { str1 = notifKey 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 e9849ec84987..9925909c3e16 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 @@ -64,6 +64,7 @@ import com.android.systemui.statusbar.policy.SmartReplyView; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; /** * A frame layout containing the actual payload of the notification, including the contracted, @@ -518,9 +519,12 @@ public class NotificationContentView extends FrameLayout { protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); updateVisibility(); - if (visibility != VISIBLE) { + if (visibility != VISIBLE && !mOnContentViewInactiveListeners.isEmpty()) { // View is no longer visible so all content views are inactive. - for (Runnable r : mOnContentViewInactiveListeners.values()) { + // Clone list as runnables may modify the list of listeners + ArrayList<Runnable> listeners = new ArrayList<>( + mOnContentViewInactiveListeners.values()); + for (Runnable r : listeners) { r.run(); } mOnContentViewInactiveListeners.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index 65633a2e209f..e39a4a0c799f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -360,7 +360,6 @@ class NotificationSectionsManager @Inject internal constructor( } } } - else -> throw IllegalStateException("Cannot find section bucket for view") } prev = child 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 3db4b6f7ffbb..e33cc6027c4f 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 @@ -5983,6 +5983,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd // ANIMATION_TYPE_ADD new AnimationFilter() + .animateAlpha() .animateHeight() .animateTopInset() .animateY() @@ -5991,6 +5992,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd // ANIMATION_TYPE_REMOVE new AnimationFilter() + .animateAlpha() .animateHeight() .animateTopInset() .animateY() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index a065b74bda99..2a4475bbe6b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -46,6 +46,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; +import com.android.systemui.media.MediaData; +import com.android.systemui.media.MediaDataManager; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.screenrecord.RecordingController; @@ -80,14 +82,14 @@ import javax.inject.Inject; */ public class PhoneStatusBarPolicy implements BluetoothController.Callback, - CommandQueue.Callbacks, - RotationLockControllerCallback, - Listener, - ZenModeController.Callback, - DeviceProvisionedListener, - KeyguardStateController.Callback, - LocationController.LocationChangeCallback, - RecordingController.RecordingStateChangeCallback { + CommandQueue.Callbacks, + RotationLockControllerCallback, + Listener, + ZenModeController.Callback, + DeviceProvisionedListener, + KeyguardStateController.Callback, + LocationController.LocationChangeCallback, + RecordingController.RecordingStateChangeCallback, MediaDataManager.Listener { private static final String TAG = "PhoneStatusBarPolicy"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -108,6 +110,7 @@ public class PhoneStatusBarPolicy private final String mSlotLocation; private final String mSlotSensorsOff; private final String mSlotScreenRecord; + private final String mSlotMedia; private final int mDisplayId; private final SharedPreferences mSharedPreferences; private final DateFormatUtil mDateFormatUtil; @@ -135,6 +138,7 @@ public class PhoneStatusBarPolicy private final SensorPrivacyController mSensorPrivacyController; private final RecordingController mRecordingController; private final RingerModeTracker mRingerModeTracker; + private final MediaDataManager mMediaDataManager; private boolean mZenVisible; private boolean mVolumeVisible; @@ -159,6 +163,7 @@ public class PhoneStatusBarPolicy SensorPrivacyController sensorPrivacyController, IActivityManager iActivityManager, AlarmManager alarmManager, UserManager userManager, RecordingController recordingController, + MediaDataManager mediaDataManager, @Nullable TelecomManager telecomManager, @DisplayId int displayId, @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil, RingerModeTracker ringerModeTracker) { @@ -185,6 +190,7 @@ public class PhoneStatusBarPolicy mUiBgExecutor = uiBgExecutor; mTelecomManager = telecomManager; mRingerModeTracker = ringerModeTracker; + mMediaDataManager = mediaDataManager; mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast); mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot); @@ -202,6 +208,7 @@ public class PhoneStatusBarPolicy mSlotSensorsOff = resources.getString(com.android.internal.R.string.status_bar_sensors_off); mSlotScreenRecord = resources.getString( com.android.internal.R.string.status_bar_screen_record); + mSlotMedia = resources.getString(com.android.internal.R.string.status_bar_media); mDisplayId = displayId; mSharedPreferences = sharedPreferences; @@ -280,6 +287,11 @@ public class PhoneStatusBarPolicy mIconController.setIconVisibility(mSlotSensorsOff, mSensorPrivacyController.isSensorPrivacyEnabled()); + // play/pause icon when media is active + mIconController.setIcon(mSlotMedia, R.drawable.stat_sys_media, + mResources.getString(R.string.accessibility_media_active)); + mIconController.setIconVisibility(mSlotMedia, mMediaDataManager.hasActiveMedia()); + // screen record mIconController.setIcon(mSlotScreenRecord, R.drawable.stat_sys_screen_record, null); mIconController.setIconVisibility(mSlotScreenRecord, false); @@ -296,6 +308,7 @@ public class PhoneStatusBarPolicy mSensorPrivacyController.addCallback(mSensorPrivacyListener); mLocationController.addCallback(this); mRecordingController.addCallback(this); + mMediaDataManager.addListener(this); mCommandQueue.addCallback(this); } @@ -700,4 +713,18 @@ public class PhoneStatusBarPolicy if (DEBUG) Log.d(TAG, "screenrecord: hiding icon"); mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false)); } + + @Override + public void onMediaDataLoaded(String key, MediaData data) { + updateMediaIcon(); + } + + @Override + public void onMediaDataRemoved(String key) { + updateMediaIcon(); + } + + private void updateMediaIcon() { + mIconController.setIconVisibility(mSlotMedia, mMediaDataManager.hasActiveMedia()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java index ba323271faaa..de9c745cb357 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java @@ -37,8 +37,9 @@ import android.view.View; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -60,7 +61,7 @@ public class RotationButtonController { private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3; - private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); + private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); private final ViewRippler mViewRippler = new ViewRippler(); private @StyleRes int mStyleRes; @@ -323,7 +324,7 @@ public class RotationButtonController { } private void onRotateSuggestionClick(View v) { - mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED); + mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED); incrementNumAcceptedRotationSuggestionsIfNeeded(); setRotationLockedAtAngle(mLastRotationSuggestion); } @@ -345,7 +346,7 @@ public class RotationButtonController { private void showAndLogRotationSuggestion() { setRotateSuggestionButtonState(true /* visible */); rescheduleRotationTimeout(false /* reasonHover */); - mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN); + mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_SHOWN); } private boolean shouldOverrideUserLockPrefs(final int rotation) { @@ -474,4 +475,19 @@ public class RotationButtonController { } }; } + + enum RotationButtonEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The rotation button was shown") + ROTATION_SUGGESTION_SHOWN(206), + @UiEvent(doc = "The rotation button was clicked") + ROTATION_SUGGESTION_ACCEPTED(207); + + private final int mId; + RotationButtonEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java index 378dde284747..708b5a7a45f7 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java @@ -21,16 +21,17 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.os.Handler; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import javax.inject.Inject; @@ -49,8 +50,9 @@ public class ProximitySensor { private String mTag = null; @VisibleForTesting ProximityEvent mLastEvent; private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL; - private boolean mPaused; + @VisibleForTesting protected boolean mPaused; private boolean mRegistered; + private final AtomicBoolean mAlerting = new AtomicBoolean(); private SensorEventListener mSensorEventListener = new SensorEventListener() { @Override @@ -217,8 +219,12 @@ public class ProximitySensor { /** Update all listeners with the last value this class received from the sensor. */ public void alertListeners() { + if (mAlerting.getAndSet(true)) { + return; + } mListeners.forEach(proximitySensorListener -> proximitySensorListener.onSensorEvent(mLastEvent)); + mAlerting.set(false); } private void onSensorEvent(SensorEvent event) { @@ -239,14 +245,14 @@ public class ProximitySensor { public static class ProximityCheck implements Runnable { private final ProximitySensor mSensor; - private final Handler mHandler; + private final DelayableExecutor mDelayableExecutor; private List<Consumer<Boolean>> mCallbacks = new ArrayList<>(); @Inject - public ProximityCheck(ProximitySensor sensor, Handler handler) { + public ProximityCheck(ProximitySensor sensor, DelayableExecutor delayableExecutor) { mSensor = sensor; mSensor.setTag("prox_check"); - mHandler = handler; + mDelayableExecutor = delayableExecutor; mSensor.pause(); ProximitySensorListener listener = proximityEvent -> { mCallbacks.forEach( @@ -280,7 +286,7 @@ public class ProximitySensor { mCallbacks.add(callback); if (!mSensor.isRegistered()) { mSensor.resume(); - mHandler.postDelayed(this, timeoutMs); + mDelayableExecutor.executeDelayed(this, timeoutMs); } } } diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index c7e9accce093..2968b92f51b7 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -178,6 +178,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged ValueAnimator mAnimation = null; int mRotation = Surface.ROTATION_0; boolean mImeShowing = false; + final Rect mImeFrame = new Rect(); PerDisplay(int displayId, int initialRotation) { mDisplayId = displayId; @@ -254,8 +255,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } } - private int imeTop(InsetsSource imeSource, float surfaceOffset) { - return imeSource.getFrame().top + (int) surfaceOffset; + private int imeTop(float surfaceOffset) { + return mImeFrame.top + (int) surfaceOffset; } private void startAnimation(final boolean show, final boolean forceRestart) { @@ -263,6 +264,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged if (imeSource == null || mImeSourceControl == null) { return; } + // Set frame, but only if the new frame isn't empty -- this maintains continuity + final Rect newFrame = imeSource.getFrame(); + if (newFrame.height() != 0) { + mImeFrame.set(newFrame); + } mHandler.post(() -> { if (DEBUG) { Slog.d(TAG, "Run startAnim show:" + show + " was:" @@ -284,7 +290,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } final float defaultY = mImeSourceControl.getSurfacePosition().y; final float x = mImeSourceControl.getSurfacePosition().x; - final float hiddenY = defaultY + imeSource.getFrame().height(); + final float hiddenY = defaultY + mImeFrame.height(); final float shownY = defaultY; final float startY = show ? hiddenY : shownY; final float endY = show ? shownY : hiddenY; @@ -306,7 +312,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged SurfaceControl.Transaction t = mTransactionPool.acquire(); float value = (float) animation.getAnimatedValue(); t.setPosition(mImeSourceControl.getLeash(), x, value); - dispatchPositionChanged(mDisplayId, imeTop(imeSource, value), t); + dispatchPositionChanged(mDisplayId, imeTop(value), t); t.apply(); mTransactionPool.release(t); }); @@ -319,11 +325,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged t.setPosition(mImeSourceControl.getLeash(), x, startY); if (DEBUG) { Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:" - + imeTop(imeSource, hiddenY) + "->" + imeTop(imeSource, shownY) + + imeTop(hiddenY) + "->" + imeTop(shownY) + " showing:" + (mAnimationDirection == DIRECTION_SHOW)); } - dispatchStartPositioning(mDisplayId, imeTop(imeSource, hiddenY), - imeTop(imeSource, shownY), mAnimationDirection == DIRECTION_SHOW, + dispatchStartPositioning(mDisplayId, imeTop(hiddenY), + imeTop(shownY), mAnimationDirection == DIRECTION_SHOW, t); if (mAnimationDirection == DIRECTION_SHOW) { t.show(mImeSourceControl.getLeash()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index 50b7af27f7d9..e0049d1349f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -19,6 +19,7 @@ package com.android.systemui.appops; import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -29,9 +30,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static java.lang.Thread.sleep; - import android.app.AppOpsManager; +import android.os.Looper; import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -229,12 +229,7 @@ public class AppOpsControllerTest extends SysuiTestCase { @Test public void testActiveOpNotRemovedAfterNoted() throws InterruptedException { // Replaces the timeout delay with 5 ms - AppOpsControllerImpl.H testHandler = mController.new H(mTestableLooper.getLooper()) { - @Override - public void scheduleRemoval(AppOpItem item, long timeToRemoval) { - super.scheduleRemoval(item, 5L); - } - }; + TestHandler testHandler = new TestHandler(mTestableLooper.getLooper()); mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); mController.setBGHandler(testHandler); @@ -245,6 +240,10 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); + // Check that we "scheduled" the removal. Don't actually schedule until we are ready to + // process messages at a later time. + assertNotNull(testHandler.mDelayScheduled); + mTestableLooper.processAllMessages(); List<AppOpItem> list = mController.getActiveAppOps(); verify(mCallback).onActiveStateChanged( @@ -253,8 +252,8 @@ public class AppOpsControllerTest extends SysuiTestCase { // Duplicates are not removed between active and noted assertEquals(2, list.size()); - sleep(10L); - + // Now is later, so we can schedule delayed messages. + testHandler.scheduleDelayed(); mTestableLooper.processAllMessages(); verify(mCallback, never()).onActiveStateChanged( @@ -321,4 +320,24 @@ public class AppOpsControllerTest extends SysuiTestCase { verify(mCallback).onActiveStateChanged( AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); } + + private class TestHandler extends AppOpsControllerImpl.H { + TestHandler(Looper looper) { + mController.super(looper); + } + + Runnable mDelayScheduled; + + void scheduleDelayed() { + if (mDelayScheduled != null) { + mDelayScheduled.run(); + mDelayScheduled = null; + } + } + + @Override + public void scheduleRemoval(AppOpItem item, long timeToRemoval) { + mDelayScheduled = () -> super.scheduleRemoval(item, 0L); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java index 317500cf5b02..a5675360a57e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java @@ -18,6 +18,7 @@ package com.android.systemui.doze; import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN; +import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; @@ -46,6 +47,7 @@ import com.android.systemui.doze.DozeSensors.TriggerSensor; import com.android.systemui.plugins.SensorManagerPlugin; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.sensors.AsyncSensorManager; +import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.wakelock.WakeLock; import org.junit.Before; @@ -82,7 +84,7 @@ public class DozeSensorsTest extends SysuiTestCase { @Mock private DozeLog mDozeLog; @Mock - private Sensor mProximitySensor; + private ProximitySensor mProximitySensor; private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener; private TestableLooper mTestableLooper; private DozeSensors mDozeSensors; @@ -93,7 +95,6 @@ public class DozeSensorsTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L); when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); - when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProximitySensor); doAnswer(invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; @@ -103,10 +104,9 @@ public class DozeSensorsTest extends SysuiTestCase { @Test public void testRegisterProx() { - // We should not register with the sensor manager initially. - verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt()); + assertFalse(mProximitySensor.isRegistered()); mDozeSensors.setProxListening(true); - verify(mSensorManager).registerListener(any(), any(Sensor.class), anyInt()); + verify(mProximitySensor).resume(); } @Test @@ -169,7 +169,8 @@ public class DozeSensorsTest extends SysuiTestCase { TestableDozeSensors() { super(getContext(), mAlarmManager, mSensorManager, mDozeParameters, - mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog); + mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog, + mProximitySensor); for (TriggerSensor sensor : mSensors) { if (sensor instanceof PluginSensor && ((PluginSensor) sensor).mPluginSensor.getType() diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index debc9d6430e0..73aaeffd6044 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -31,7 +31,6 @@ import android.app.AlarmManager; import android.hardware.Sensor; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Handler; -import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -42,10 +41,12 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.FakeProximitySensor; import com.android.systemui.util.sensors.FakeSensorManager; import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.util.wakelock.WakeLockFake; @@ -75,6 +76,7 @@ public class DozeTriggersTest extends SysuiTestCase { private FakeSensorManager mSensors; private Sensor mTapSensor; private FakeProximitySensor mProximitySensor; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); @Before public void setUp() throws Exception { @@ -89,7 +91,7 @@ public class DozeTriggersTest extends SysuiTestCase { mProximitySensor = new FakeProximitySensor(getContext().getResources(), asyncSensorManager); mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, config, parameters, - asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true, + asyncSensorManager, mFakeExecutor, wakeLock, true, mDockManager, mProximitySensor, mock(DozeLog.class), mBroadcastDispatcher); waitForSensorManager(); } @@ -111,9 +113,8 @@ public class DozeTriggersTest extends SysuiTestCase { verify(mMachine, never()).requestState(any()); verify(mMachine, never()).requestPulse(anyInt()); - captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */); - waitForSensorManager(); mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(false, 2)); + captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */); mProximitySensor.alertListeners(); verify(mMachine).requestPulse(anyInt()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index ee7733aa2be7..4a8598009381 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -53,6 +53,7 @@ import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.controls.controller.ControlsController; import com.android.systemui.controls.management.ControlsListingController; import com.android.systemui.controls.ui.ControlsUiController; +import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.statusbar.BlurUtils; @@ -107,6 +108,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock private UiEventLogger mUiEventLogger; @Mock private RingerModeTracker mRingerModeTracker; @Mock private RingerModeLiveData mRingerModeLiveData; + @Mock private SysUiState mSysUiState; @Mock private Handler mHandler; private TestableLooper mTestableLooper; @@ -150,6 +152,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mControlsController, mUiEventLogger, mRingerModeTracker, + mSysUiState, mHandler ); mGlobalActionsDialog.setZeroDialogPressDelayForTesting(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt new file mode 100644 index 000000000000..e8fb41a18ce9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -0,0 +1,210 @@ +/* + * 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.media + +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.RippleDrawable +import android.media.MediaMetadata +import android.media.session.MediaSession +import android.media.session.PlaybackState +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.SeekBar +import android.widget.TextView + +import androidx.constraintlayout.motion.widget.MotionLayout +import androidx.constraintlayout.motion.widget.MotionScene +import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.filters.SmallTest + +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock + +import com.google.common.truth.Truth.assertThat + +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` as whenever + +import java.util.ArrayList + +private const val KEY = "TEST_KEY" +private const val APP = "APP" +private const val BG_COLOR = Color.RED +private const val PACKAGE = "PKG" +private const val ARTIST = "ARTIST" +private const val TITLE = "TITLE" +private const val DEVICE_NAME = "DEVICE_NAME" +private const val SESSION_KEY = "SESSION_KEY" +private const val SESSION_ARTIST = "SESSION_ARTIST" +private const val SESSION_TITLE = "SESSION_TITLE" + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class MediaControlPanelTest : SysuiTestCase() { + + private lateinit var player: MediaControlPanel + + private lateinit var fgExecutor: FakeExecutor + private lateinit var bgExecutor: FakeExecutor + @Mock private lateinit var activityStarter: ActivityStarter + + @Mock private lateinit var holder: PlayerViewHolder + @Mock private lateinit var motion: MotionLayout + private lateinit var background: TextView + private lateinit var appIcon: ImageView + private lateinit var appName: TextView + private lateinit var albumView: ImageView + private lateinit var titleText: TextView + private lateinit var artistText: TextView + private lateinit var seamless: ViewGroup + private lateinit var seamlessIcon: ImageView + private lateinit var seamlessText: TextView + private lateinit var seekBar: SeekBar + private lateinit var elapsedTimeView: TextView + private lateinit var totalTimeView: TextView + private lateinit var action0: ImageButton + private lateinit var action1: ImageButton + private lateinit var action2: ImageButton + private lateinit var action3: ImageButton + private lateinit var action4: ImageButton + + private lateinit var session: MediaSession + + @Before + fun setUp() { + fgExecutor = FakeExecutor(FakeSystemClock()) + bgExecutor = FakeExecutor(FakeSystemClock()) + + activityStarter = mock(ActivityStarter::class.java) + + player = MediaControlPanel(context, fgExecutor, bgExecutor, activityStarter) + + // Mock out a view holder for the player to attach to. + holder = mock(PlayerViewHolder::class.java) + motion = mock(MotionLayout::class.java) + val trans: ArrayList<MotionScene.Transition> = ArrayList() + trans.add(mock(MotionScene.Transition::class.java)) + whenever(motion.definedTransitions).thenReturn(trans) + val constraintSet = mock(ConstraintSet::class.java) + whenever(motion.getConstraintSet(R.id.expanded)).thenReturn(constraintSet) + whenever(motion.getConstraintSet(R.id.collapsed)).thenReturn(constraintSet) + whenever(holder.player).thenReturn(motion) + background = TextView(context) + whenever(holder.background).thenReturn(background) + appIcon = ImageView(context) + whenever(holder.appIcon).thenReturn(appIcon) + appName = TextView(context) + whenever(holder.appName).thenReturn(appName) + albumView = ImageView(context) + whenever(holder.albumView).thenReturn(albumView) + titleText = TextView(context) + whenever(holder.titleText).thenReturn(titleText) + artistText = TextView(context) + whenever(holder.artistText).thenReturn(artistText) + seamless = FrameLayout(context) + val seamlessBackground = mock(RippleDrawable::class.java) + seamless.setBackground(seamlessBackground) + whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java)) + whenever(holder.seamless).thenReturn(seamless) + seamlessIcon = ImageView(context) + whenever(holder.seamlessIcon).thenReturn(seamlessIcon) + seamlessText = TextView(context) + whenever(holder.seamlessText).thenReturn(seamlessText) + seekBar = SeekBar(context) + whenever(holder.seekBar).thenReturn(seekBar) + elapsedTimeView = TextView(context) + whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView) + totalTimeView = TextView(context) + whenever(holder.totalTimeView).thenReturn(totalTimeView) + action0 = ImageButton(context) + whenever(holder.action0).thenReturn(action0) + action1 = ImageButton(context) + whenever(holder.action1).thenReturn(action1) + action2 = ImageButton(context) + whenever(holder.action2).thenReturn(action2) + action3 = ImageButton(context) + whenever(holder.action3).thenReturn(action3) + action4 = ImageButton(context) + whenever(holder.action4).thenReturn(action4) + + // Create media session + val metadataBuilder = MediaMetadata.Builder().apply { + putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) + putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) + } + val playbackBuilder = PlaybackState.Builder().apply { + setState(PlaybackState.STATE_PAUSED, 6000L, 1f) + setActions(PlaybackState.ACTION_PLAY) + } + session = MediaSession(context, SESSION_KEY).apply { + setMetadata(metadataBuilder.build()) + setPlaybackState(playbackBuilder.build()) + } + session.setActive(true) + } + + @After + fun tearDown() { + session.release() + player.onDestroy() + } + + @Test + fun bindWhenUnattached() { + val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), + emptyList(), PACKAGE, null, null, MediaDeviceData(null, DEVICE_NAME)) + player.bind(state) + assertThat(player.isPlaying()).isFalse() + } + + @Test + fun bindText() { + player.attach(holder) + val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), + emptyList(), PACKAGE, session.getSessionToken(), null, + MediaDeviceData(null, DEVICE_NAME)) + player.bind(state) + assertThat(appName.getText()).isEqualTo(APP) + assertThat(titleText.getText()).isEqualTo(TITLE) + assertThat(artistText.getText()).isEqualTo(ARTIST) + } + + @Test + fun bindBackgroundColor() { + player.attach(holder) + val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), + emptyList(), PACKAGE, session.getSessionToken(), null, + MediaDeviceData(null, DEVICE_NAME)) + player.bind(state) + assertThat(background.getBackgroundTintList()).isEqualTo(ColorStateList.valueOf(BG_COLOR)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java new file mode 100644 index 000000000000..64a180f8aaab --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -0,0 +1,161 @@ +/* + * 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.media; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.graphics.Color; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; + +import java.util.ArrayList; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class MediaDataCombineLatestTest extends SysuiTestCase { + + private static final String KEY = "TEST_KEY"; + private static final String APP = "APP"; + private static final String PACKAGE = "PKG"; + private static final int BG_COLOR = Color.RED; + private static final String ARTIST = "ARTIST"; + private static final String TITLE = "TITLE"; + private static final String DEVICE_NAME = "DEVICE_NAME"; + + private MediaDataCombineLatest mManager; + + @Mock private MediaDataManager mDataSource; + @Mock private MediaDeviceManager mDeviceSource; + @Mock private MediaDataManager.Listener mListener; + + private MediaDataManager.Listener mDataListener; + private MediaDeviceManager.Listener mDeviceListener; + + private MediaData mMediaData; + private MediaDeviceData mDeviceData; + + @Before + public void setUp() { + mDataSource = mock(MediaDataManager.class); + mDeviceSource = mock(MediaDeviceManager.class); + mListener = mock(MediaDataManager.Listener.class); + + mManager = new MediaDataCombineLatest(mDataSource, mDeviceSource); + + mDataListener = captureDataListener(); + mDeviceListener = captureDeviceListener(); + + mManager.addListener(mListener); + + mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, + new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null); + mDeviceData = new MediaDeviceData(null, DEVICE_NAME); + } + + @Test + public void eventNotEmittedWithoutDevice() { + // WHEN data source emits an event without device data + mDataListener.onMediaDataLoaded(KEY, mMediaData); + // THEN an event isn't emitted + verify(mListener, never()).onMediaDataLoaded(eq(KEY), any()); + } + + @Test + public void eventNotEmittedWithoutMedia() { + // WHEN device source emits an event without media data + mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + // THEN an event isn't emitted + verify(mListener, never()).onMediaDataLoaded(eq(KEY), any()); + } + + @Test + public void emitEventAfterDeviceFirst() { + // GIVEN that a device event has already been received + mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + // WHEN media event is received + mDataListener.onMediaDataLoaded(KEY, mMediaData); + // THEN the listener receives a combined event + ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); + verify(mListener).onMediaDataLoaded(eq(KEY), captor.capture()); + assertThat(captor.getValue().getDevice()).isNotNull(); + } + + @Test + public void emitEventAfterMediaFirst() { + // GIVEN that media event has already been received + mDataListener.onMediaDataLoaded(KEY, mMediaData); + // WHEN device event is received + mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + // THEN the listener receives a combined event + ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); + verify(mListener).onMediaDataLoaded(eq(KEY), captor.capture()); + assertThat(captor.getValue().getDevice()).isNotNull(); + } + + @Test + public void mediaDataRemoved() { + // WHEN media data is removed without first receiving device or data + mDataListener.onMediaDataRemoved(KEY); + // THEN a removed event isn't emitted + verify(mListener, never()).onMediaDataRemoved(eq(KEY)); + } + + @Test + public void mediaDataRemovedAfterMediaEvent() { + mDataListener.onMediaDataLoaded(KEY, mMediaData); + mDataListener.onMediaDataRemoved(KEY); + verify(mListener).onMediaDataRemoved(eq(KEY)); + } + + @Test + public void mediaDataRemovedAfterDeviceEvent() { + mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + mDataListener.onMediaDataRemoved(KEY); + verify(mListener).onMediaDataRemoved(eq(KEY)); + } + + private MediaDataManager.Listener captureDataListener() { + ArgumentCaptor<MediaDataManager.Listener> captor = ArgumentCaptor.forClass( + MediaDataManager.Listener.class); + verify(mDataSource).addListener(captor.capture()); + return captor.getValue(); + } + + private MediaDeviceManager.Listener captureDeviceListener() { + ArgumentCaptor<MediaDeviceManager.Listener> captor = ArgumentCaptor.forClass( + MediaDeviceManager.Listener.class); + verify(mDeviceSource).addListener(captor.capture()); + return captor.getValue(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt new file mode 100644 index 000000000000..ac6b5f6bca66 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -0,0 +1,195 @@ +/* + * 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.media + +import android.app.Notification +import android.media.MediaMetadata +import android.media.session.MediaSession +import android.media.session.PlaybackState +import android.os.Process +import android.service.notification.StatusBarNotification +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest + +import com.android.settingslib.media.LocalMediaManager +import com.android.settingslib.media.MediaDevice +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock + +import com.google.common.truth.Truth.assertThat + +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.any +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever + +private const val KEY = "TEST_KEY" +private const val PACKAGE = "PKG" +private const val SESSION_KEY = "SESSION_KEY" +private const val SESSION_ARTIST = "SESSION_ARTIST" +private const val SESSION_TITLE = "SESSION_TITLE" +private const val DEVICE_NAME = "DEVICE_NAME" + +private fun <T> eq(value: T): T = Mockito.eq(value) ?: value + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +public class MediaDeviceManagerTest : SysuiTestCase() { + + private lateinit var manager: MediaDeviceManager + + @Mock private lateinit var lmmFactory: LocalMediaManagerFactory + @Mock private lateinit var lmm: LocalMediaManager + @Mock private lateinit var featureFlag: MediaFeatureFlag + private lateinit var fakeExecutor: FakeExecutor + + @Mock private lateinit var device: MediaDevice + private lateinit var session: MediaSession + private lateinit var metadataBuilder: MediaMetadata.Builder + private lateinit var playbackBuilder: PlaybackState.Builder + private lateinit var notifBuilder: Notification.Builder + private lateinit var sbn: StatusBarNotification + + @Before + fun setup() { + lmmFactory = mock(LocalMediaManagerFactory::class.java) + lmm = mock(LocalMediaManager::class.java) + device = mock(MediaDevice::class.java) + whenever(device.name).thenReturn(DEVICE_NAME) + whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm) + whenever(lmm.getCurrentConnectedDevice()).thenReturn(device) + featureFlag = mock(MediaFeatureFlag::class.java) + whenever(featureFlag.enabled).thenReturn(true) + + fakeExecutor = FakeExecutor(FakeSystemClock()) + + manager = MediaDeviceManager(context, lmmFactory, featureFlag, fakeExecutor) + + // Create a media sesssion and notification for testing. + metadataBuilder = MediaMetadata.Builder().apply { + putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) + putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) + } + playbackBuilder = PlaybackState.Builder().apply { + setState(PlaybackState.STATE_PAUSED, 6000L, 1f) + setActions(PlaybackState.ACTION_PLAY) + } + session = MediaSession(context, SESSION_KEY).apply { + setMetadata(metadataBuilder.build()) + setPlaybackState(playbackBuilder.build()) + } + session.setActive(true) + notifBuilder = Notification.Builder(context, "NONE").apply { + setContentTitle(SESSION_TITLE) + setContentText(SESSION_ARTIST) + setSmallIcon(android.R.drawable.ic_media_pause) + setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken())) + } + sbn = StatusBarNotification(PACKAGE, PACKAGE, 0, "TAG", Process.myUid(), 0, 0, + notifBuilder.build(), Process.myUserHandle(), 0) + } + + @After + fun tearDown() { + session.release() + } + + @Test + fun removeUnknown() { + manager.onNotificationRemoved("unknown") + } + + @Test + fun addNotification() { + manager.onNotificationAdded(KEY, sbn) + verify(lmmFactory).create(PACKAGE) + } + + @Test + fun featureDisabled() { + whenever(featureFlag.enabled).thenReturn(false) + manager.onNotificationAdded(KEY, sbn) + verify(lmmFactory, never()).create(PACKAGE) + } + + @Test + fun addAndRemoveNotification() { + manager.onNotificationAdded(KEY, sbn) + manager.onNotificationRemoved(KEY) + verify(lmm).unregisterCallback(any()) + } + + @Test + fun deviceListUpdate() { + val listener = mock(MediaDeviceManager.Listener::class.java) + manager.addListener(listener) + manager.onNotificationAdded(KEY, sbn) + val deviceCallback = captureCallback() + // WHEN the device list changes + deviceCallback.onDeviceListUpdate(mutableListOf(device)) + assertThat(fakeExecutor.runAllReady()).isEqualTo(1) + // THEN the update is dispatched to the listener + val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java) + verify(listener).onMediaDeviceChanged(eq(KEY), captor.capture()) + val data = captor.getValue() + assertThat(data.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun selectedDeviceStateChanged() { + val listener = mock(MediaDeviceManager.Listener::class.java) + manager.addListener(listener) + manager.onNotificationAdded(KEY, sbn) + val deviceCallback = captureCallback() + // WHEN the selected device changes state + deviceCallback.onSelectedDeviceStateChanged(device, 1) + assertThat(fakeExecutor.runAllReady()).isEqualTo(1) + // THEN the update is dispatched to the listener + val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java) + verify(listener).onMediaDeviceChanged(eq(KEY), captor.capture()) + val data = captor.getValue() + assertThat(data.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun listenerReceivesKeyRemoved() { + manager.onNotificationAdded(KEY, sbn) + val listener = mock(MediaDeviceManager.Listener::class.java) + manager.addListener(listener) + // WHEN the notification is removed + manager.onNotificationRemoved(KEY) + // THEN the listener receives key removed event + verify(listener).onKeyRemoved(eq(KEY)) + } + + fun captureCallback(): LocalMediaManager.DeviceCallback { + val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java) + verify(lmm).registerCallback(captor.capture()) + return captor.getValue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt new file mode 100644 index 000000000000..767852582dc3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt @@ -0,0 +1,62 @@ +/* + * 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.media + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.FrameLayout + +import androidx.test.filters.SmallTest + +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Tests for PlayerViewHolder. + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class PlayerViewHolderTest : SysuiTestCase() { + + private lateinit var inflater: LayoutInflater + private lateinit var parent: ViewGroup + + @Before + fun setUp() { + inflater = LayoutInflater.from(context) + parent = FrameLayout(context) + } + + @Test + fun create() { + val holder = PlayerViewHolder.create(inflater, parent) + assertThat(holder.player).isNotNull() + } + + @Test + fun backgroundIsIlluminationDrawable() { + val holder = PlayerViewHolder.create(inflater, parent) + assertThat(holder.background.background as IlluminationDrawable).isNotNull() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt index 58ee79e39279..75018df023cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt @@ -22,7 +22,6 @@ import android.view.View import android.widget.SeekBar import android.widget.TextView import androidx.test.filters.SmallTest -import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -38,23 +37,21 @@ import org.mockito.Mockito.`when` as whenever public class SeekBarObserverTest : SysuiTestCase() { private lateinit var observer: SeekBarObserver - @Mock private lateinit var mockView: View + @Mock private lateinit var mockHolder: PlayerViewHolder private lateinit var seekBarView: SeekBar private lateinit var elapsedTimeView: TextView private lateinit var totalTimeView: TextView @Before fun setUp() { - mockView = mock(View::class.java) + mockHolder = mock(PlayerViewHolder::class.java) seekBarView = SeekBar(context) elapsedTimeView = TextView(context) totalTimeView = TextView(context) - whenever<SeekBar>( - mockView.findViewById(R.id.media_progress_bar)).thenReturn(seekBarView) - whenever<TextView>( - mockView.findViewById(R.id.media_elapsed_time)).thenReturn(elapsedTimeView) - whenever<TextView>(mockView.findViewById(R.id.media_total_time)).thenReturn(totalTimeView) - observer = SeekBarObserver(mockView) + whenever(mockHolder.seekBar).thenReturn(seekBarView) + whenever(mockHolder.elapsedTimeView).thenReturn(elapsedTimeView) + whenever(mockHolder.totalTimeView).thenReturn(totalTimeView) + observer = SeekBarObserver(mockHolder) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index a5a5f81bdffe..d583048fbb26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -210,28 +210,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test - public void testAddNotification_noDuplicateEntriesCreated() { - // GIVEN a notification has been added - mEntryManager.addNotification(mSbn, mRankingMap); - - // WHEN the same notification is added multiple times before the previous entry (with - // the same key) didn't finish inflating - mEntryManager.addNotification(mSbn, mRankingMap); - mEntryManager.addNotification(mSbn, mRankingMap); - mEntryManager.addNotification(mSbn, mRankingMap); - - // THEN getAllNotifs() only contains exactly one notification with this key - int count = 0; - for (NotificationEntry entry : mEntryManager.getAllNotifs()) { - if (entry.getKey().equals(mSbn.getKey())) { - count++; - } - } - assertEquals("Should only be one entry with key=" + mSbn.getKey() + " in mAllNotifs. " - + "Instead there are " + count, 1, count); - } - - @Test public void testAddNotification_setsUserSentiment() { mEntryManager.addNotification(mSbn, mRankingMap); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.java new file mode 100644 index 000000000000..a14d57556360 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.java @@ -0,0 +1,168 @@ +/* + * 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.phone; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AlarmManager; +import android.app.IActivityManager; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.UserManager; +import android.telecom.TelecomManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.media.MediaDataManager; +import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.policy.BluetoothController; +import com.android.systemui.statusbar.policy.CastController; +import com.android.systemui.statusbar.policy.DataSaverController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.HotspotController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.policy.LocationController; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.RotationLockController; +import com.android.systemui.statusbar.policy.SensorPrivacyController; +import com.android.systemui.statusbar.policy.UserInfoController; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.RingerModeLiveData; +import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.time.DateFormatUtil; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.concurrent.Executor; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class PhoneStatusBarPolicyTest extends SysuiTestCase { + + private static final int DISPLAY_ID = 0; + @Mock + private StatusBarIconController mIconController; + @Mock + private CommandQueue mCommandQueue; + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private Executor mBackgroundExecutor; + @Mock + private CastController mCastController; + @Mock + private HotspotController mHotSpotController; + @Mock + private BluetoothController mBluetoothController; + @Mock + private NextAlarmController mNextAlarmController; + @Mock + private UserInfoController mUserInfoController; + @Mock + private RotationLockController mRotationLockController; + @Mock + private DataSaverController mDataSaverController; + @Mock + private ZenModeController mZenModeController; + @Mock + private DeviceProvisionedController mDeviceProvisionerController; + @Mock + private KeyguardStateController mKeyguardStateController; + @Mock + private LocationController mLocationController; + @Mock + private SensorPrivacyController mSensorPrivacyController; + @Mock + private IActivityManager mIActivityManager; + @Mock + private AlarmManager mAlarmManager; + @Mock + private UserManager mUserManager; + @Mock + private RecordingController mRecordingController; + @Mock + private MediaDataManager mMediaDataManager; + @Mock + private TelecomManager mTelecomManager; + @Mock + private SharedPreferences mSharedPreferences; + @Mock + private DateFormatUtil mDateFormatUtil; + @Mock + private RingerModeTracker mRingerModeTracker; + @Mock + private RingerModeLiveData mRingerModeLiveData; + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + private Resources mResources; + private PhoneStatusBarPolicy mPhoneStatusBarPolicy; + + @Before + public void setup() { + mResources = spy(getContext().getResources()); + mPhoneStatusBarPolicy = new PhoneStatusBarPolicy(mIconController, mCommandQueue, + mBroadcastDispatcher, mBackgroundExecutor, mResources, mCastController, + mHotSpotController, mBluetoothController, mNextAlarmController, mUserInfoController, + mRotationLockController, mDataSaverController, mZenModeController, + mDeviceProvisionerController, mKeyguardStateController, mLocationController, + mSensorPrivacyController, mIActivityManager, mAlarmManager, mUserManager, + mRecordingController, mMediaDataManager, mTelecomManager, DISPLAY_ID, + mSharedPreferences, mDateFormatUtil, mRingerModeTracker); + when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); + when(mRingerModeTracker.getRingerModeInternal()).thenReturn(mRingerModeLiveData); + clearInvocations(mIconController); + } + + @Test + public void testInit_registerMediaCallback() { + mPhoneStatusBarPolicy.init(); + verify(mMediaDataManager).addListener(eq(mPhoneStatusBarPolicy)); + } + + @Test + public void testOnMediaDataLoaded_updatesIcon_hasMedia() { + String mediaSlot = mResources.getString(com.android.internal.R.string.status_bar_media); + when(mMediaDataManager.hasActiveMedia()).thenReturn(true); + mPhoneStatusBarPolicy.onMediaDataLoaded(null, null); + verify(mMediaDataManager).hasActiveMedia(); + verify(mIconController).setIconVisibility(eq(mediaSlot), eq(true)); + } + + @Test + public void testOnMediaDataRemoved_updatesIcon_noMedia() { + String mediaSlot = mResources.getString(com.android.internal.R.string.status_bar_media); + mPhoneStatusBarPolicy.onMediaDataRemoved(null); + verify(mMediaDataManager).hasActiveMedia(); + verify(mIconController).setIconVisibility(eq(mediaSlot), eq(false)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java index 31d884c38f58..bd697fe394b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java @@ -20,6 +20,7 @@ import android.content.res.Resources; public class FakeProximitySensor extends ProximitySensor { private boolean mAvailable; + private boolean mRegistered; public FakeProximitySensor(Resources resources, AsyncSensorManager sensorManager) { super(resources, sensorManager); @@ -35,17 +36,22 @@ public class FakeProximitySensor extends ProximitySensor { } @Override + public boolean isRegistered() { + return mRegistered; + } + + @Override public boolean getSensorAvailable() { return mAvailable; } @Override protected void registerInternal() { - // no-op + mRegistered = !mPaused; } @Override protected void unregisterInternal() { - // no-op + mRegistered = false; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java new file mode 100644 index 000000000000..7221095a1eaf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.sensors; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.os.Handler; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ProximityCheckTest extends SysuiTestCase { + + private FakeProximitySensor mFakeProximitySensor; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + + private TestableCallback mTestableCallback = new TestableCallback(); + + private ProximitySensor.ProximityCheck mProximityCheck; + + @Before + public void setUp() throws Exception { + AsyncSensorManager asyncSensorManager = + new AsyncSensorManager(new FakeSensorManager(mContext), null, new Handler()); + mFakeProximitySensor = new FakeProximitySensor(mContext.getResources(), asyncSensorManager); + + mProximityCheck = new ProximitySensor.ProximityCheck(mFakeProximitySensor, mFakeExecutor); + } + + @Test + public void testCheck() { + mProximityCheck.check(100, mTestableCallback); + + assertNull(mTestableCallback.mLastResult); + + mFakeProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(true, 0)); + mFakeProximitySensor.alertListeners(); + + assertTrue(mTestableCallback.mLastResult); + } + + @Test + public void testTimeout() { + mProximityCheck.check(100, mTestableCallback); + + assertTrue(mFakeProximitySensor.isRegistered()); + + mFakeExecutor.advanceClockToNext(); + mFakeExecutor.runAllReady(); + + assertFalse(mFakeProximitySensor.isRegistered()); + } + + private static class TestableCallback implements Consumer<Boolean> { + Boolean mLastResult; + @Override + public void accept(Boolean result) { + mLastResult = result; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java index 526fba726e9d..914790b53a82 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java @@ -219,6 +219,26 @@ public class ProximitySensorTest extends SysuiTestCase { waitForSensorManager(); } + @Test + public void testPreventRecursiveAlert() { + TestableListener listenerA = new TestableListener() { + @Override + public void onSensorEvent(ProximitySensor.ProximityEvent proximityEvent) { + super.onSensorEvent(proximityEvent); + if (mCallCount < 2) { + mProximitySensor.alertListeners(); + } + } + }; + + mProximitySensor.register(listenerA); + + mProximitySensor.alertListeners(); + + assertEquals(1, listenerA.mCallCount); + } + + class TestableListener implements ProximitySensor.ProximitySensorListener { ProximitySensor.ProximityEvent mLastEvent; int mCallCount = 0; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2b86d7fe057e..4ace676eca7e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -337,6 +337,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.function.HexFunction; import com.android.internal.util.function.QuadFunction; import com.android.internal.util.function.TriFunction; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.AlarmManagerInternal; import com.android.server.AttributeCache; import com.android.server.DeviceIdleInternal; @@ -2098,6 +2099,7 @@ public class ActivityManagerService extends IActivityManager.Stub } ServiceManager.addService("permission", new PermissionController(this)); ServiceManager.addService("processinfo", new ProcessInfoService(this)); + ServiceManager.addService("cacheinfo", new CacheBinder(this)); ApplicationInfo info = mContext.getPackageManager().getApplicationInfo( "android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY); @@ -2191,16 +2193,18 @@ public class ActivityManagerService extends IActivityManager.Stub @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { - Process.enableFreezer(false); - } - - if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext, - "meminfo", pw)) return; - PriorityDump.dump(mPriorityDumper, fd, pw, args); + try { + if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { + Process.enableFreezer(false); + } - if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { - Process.enableFreezer(true); + if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext, + "meminfo", pw)) return; + PriorityDump.dump(mPriorityDumper, fd, pw, args); + } finally { + if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { + Process.enableFreezer(true); + } } } } @@ -2213,16 +2217,18 @@ public class ActivityManagerService extends IActivityManager.Stub @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { - Process.enableFreezer(false); - } - - if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext, - "gfxinfo", pw)) return; - mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args); + try { + if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { + Process.enableFreezer(false); + } - if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { - Process.enableFreezer(true); + if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext, + "gfxinfo", pw)) return; + mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args); + } finally { + if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { + Process.enableFreezer(true); + } } } } @@ -2235,16 +2241,18 @@ public class ActivityManagerService extends IActivityManager.Stub @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { - Process.enableFreezer(false); - } - - if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext, - "dbinfo", pw)) return; - mActivityManagerService.dumpDbInfo(fd, pw, args); + try { + if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { + Process.enableFreezer(false); + } - if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { - Process.enableFreezer(true); + if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext, + "dbinfo", pw)) return; + mActivityManagerService.dumpDbInfo(fd, pw, args); + } finally { + if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { + Process.enableFreezer(true); + } } } } @@ -2280,6 +2288,34 @@ public class ActivityManagerService extends IActivityManager.Stub } } + static class CacheBinder extends Binder { + ActivityManagerService mActivityManagerService; + + CacheBinder(ActivityManagerService activityManagerService) { + mActivityManagerService = activityManagerService; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + try { + if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { + Process.enableFreezer(false); + } + + if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext, + "cacheinfo", pw)) { + return; + } + + mActivityManagerService.dumpBinderCacheContents(fd, pw, args); + } finally { + if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) { + Process.enableFreezer(true); + } + } + } + } + public static final class Lifecycle extends SystemService { private final ActivityManagerService mService; private static ActivityTaskManagerService sAtm; @@ -12722,6 +12758,39 @@ public class ActivityManagerService extends IActivityManager.Stub } } + final void dumpBinderCacheContents(FileDescriptor fd, PrintWriter pw, String[] args) { + ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, false, args); + if (procs == null) { + pw.println("No process found for: " + args[0]); + return; + } + + pw.println("Per-process Binder Cache Contents"); + + for (int i = procs.size() - 1; i >= 0; i--) { + ProcessRecord r = procs.get(i); + if (r.thread != null) { + pw.println("\n\n** Cache info for pid " + r.pid + " [" + r.processName + "] **"); + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.thread.dumpCacheInfo(tp.getWriteFd(), args); + tp.go(fd); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println("Failure while dumping the app " + r); + pw.flush(); + } catch (RemoteException e) { + pw.println("Got a RemoteException while dumping the app " + r); + pw.flush(); + } + } + } + } + final void dumpDbInfo(FileDescriptor fd, PrintWriter pw, String[] args) { ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, false, args); if (procs == null) { @@ -18763,28 +18832,39 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public int checkContentProviderUriPermission(Uri uri, int userId, int callingUid, int modeFlags) { - // We can find ourselves needing to check Uri permissions while - // already holding the WM lock, which means reaching back here for - // the AM lock would cause an inversion. The WM team has requested - // that we use the strategy below instead of shifting where Uri - // grants are calculated. - - // Since we could also arrive here while holding the AM lock, we - // can't always delegate the call through the handler, and we need - // to delicately dance between the deadlocks. - if (Thread.currentThread().holdsLock(ActivityManagerService.this)) { + final Object wmLock = mActivityTaskManager.getGlobalLock(); + if (Thread.currentThread().holdsLock(wmLock) + && !Thread.currentThread().holdsLock(ActivityManagerService.this)) { + // We can find ourselves needing to check Uri permissions while already holding the + // WM lock, which means reaching back here for the AM lock would cause an inversion. + // The WM team has requested that we use the strategy below instead of shifting + // where Uri grants are calculated. + synchronized (wmLock) { + final int[] result = new int[1]; + final Message msg = PooledLambda.obtainMessage( + LocalService::checkContentProviderUriPermission, + this, uri, userId, callingUid, modeFlags, wmLock, result); + mHandler.sendMessage(msg); + try { + wmLock.wait(); + } catch (InterruptedException ignore) { + + } + return result[0]; + } + } else { return ActivityManagerService.this.checkContentProviderUriPermission(uri, userId, callingUid, modeFlags); - } else { - final CompletableFuture<Integer> res = new CompletableFuture<>(); - mHandler.post(() -> { - res.complete(ActivityManagerService.this.checkContentProviderUriPermission(uri, - userId, callingUid, modeFlags)); - }); - try { - return res.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); + } + } + + void checkContentProviderUriPermission( + Uri uri, int userId, int callingUid, int modeFlags, Object wmLock, int[] result) { + synchronized (ActivityManagerService.this) { + synchronized (wmLock) { + result[0] = ActivityManagerService.this.checkContentProviderUriPermission( + uri, userId, callingUid, modeFlags); + wmLock.notify(); } } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index f9d204fa008e..43e3a04ad032 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -45,6 +45,7 @@ import com.android.server.ServiceThread; import java.io.FileOutputStream; import java.io.FileReader; +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -114,6 +115,14 @@ public final class CachedAppOptimizer { } private PropertyChangedCallbackForTest mTestCallback; + // This interface is for functions related to the Process object that need a different + // implementation in the tests as we are not creating real processes when testing compaction. + @VisibleForTesting + interface ProcessDependencies { + long[] getRss(int pid); + void performCompaction(String action, int pid) throws IOException; + } + // Handler constants. static final int COMPACT_PROCESS_SOME = 1; static final int COMPACT_PROCESS_FULL = 2; @@ -215,13 +224,16 @@ public final class CachedAppOptimizer { @VisibleForTesting final Set<Integer> mProcStateThrottle; // Handler on which compaction runs. - private Handler mCompactionHandler; + @VisibleForTesting + Handler mCompactionHandler; private Handler mFreezeHandler; // Maps process ID to last compaction statistics for processes that we've fully compacted. Used // when evaluating throttles that we only consider for "full" compaction, so we don't store - // data for "some" compactions. - private Map<Integer, LastCompactionStats> mLastCompactionStats = + // data for "some" compactions. Uses LinkedHashMap to ensure insertion order is kept and + // facilitate removal of the oldest entry. + @VisibleForTesting + LinkedHashMap<Integer, LastCompactionStats> mLastCompactionStats = new LinkedHashMap<Integer, LastCompactionStats>() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { @@ -233,17 +245,20 @@ public final class CachedAppOptimizer { private int mFullCompactionCount; private int mPersistentCompactionCount; private int mBfgsCompactionCount; + private final ProcessDependencies mProcessDependencies; public CachedAppOptimizer(ActivityManagerService am) { - mAm = am; - mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread", - THREAD_PRIORITY_FOREGROUND, true); - mProcStateThrottle = new HashSet<>(); + this(am, null, new DefaultProcessDependencies()); } @VisibleForTesting - CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback) { - this(am); + CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback, + ProcessDependencies processDependencies) { + mAm = am; + mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread", + THREAD_PRIORITY_FOREGROUND, true); + mProcStateThrottle = new HashSet<>(); + mProcessDependencies = processDependencies; mTestCallback = callback; } @@ -659,7 +674,8 @@ public final class CachedAppOptimizer { } } - private static final class LastCompactionStats { + @VisibleForTesting + static final class LastCompactionStats { private final long[] mRssAfterCompaction; LastCompactionStats(long[] rss) { @@ -712,9 +728,7 @@ public final class CachedAppOptimizer { lastCompactAction = proc.lastCompactAction; lastCompactTime = proc.lastCompactTime; - // remove rather than get so that insertion order will be updated when we - // put the post-compaction stats back into the map. - lastCompactionStats = mLastCompactionStats.remove(pid); + lastCompactionStats = mLastCompactionStats.get(pid); } if (pid == 0) { @@ -806,7 +820,7 @@ public final class CachedAppOptimizer { return; } - long[] rssBefore = Process.getRss(pid); + long[] rssBefore = mProcessDependencies.getRss(pid); long anonRssBefore = rssBefore[2]; if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0 @@ -863,16 +877,13 @@ public final class CachedAppOptimizer { default: break; } - try { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + ": " + name); long zramFreeKbBefore = Debug.getZramFreeKb(); - FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); - fos.write(action.getBytes()); - fos.close(); - long[] rssAfter = Process.getRss(pid); + mProcessDependencies.performCompaction(action, pid); + long[] rssAfter = mProcessDependencies.getRss(pid); long end = SystemClock.uptimeMillis(); long time = end - start; long zramFreeKbAfter = Debug.getZramFreeKb(); @@ -882,7 +893,6 @@ public final class CachedAppOptimizer { rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time, lastCompactAction, lastCompactTime, lastOomAdj, procState, zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore); - // Note that as above not taking mPhenoTypeFlagLock here to avoid locking // on every single compaction for a flag that will seldom change and the // impact of reading the wrong value here is low. @@ -894,14 +904,14 @@ public final class CachedAppOptimizer { lastOomAdj, ActivityManager.processStateAmToProto(procState), zramFreeKbBefore, zramFreeKbAfter); } - synchronized (mAm) { proc.lastCompactTime = end; proc.lastCompactAction = pendingAction; } - if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) { + // Remove entry and insert again to update insertion order. + mLastCompactionStats.remove(pid); mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter)); } } catch (Exception e) { @@ -1018,4 +1028,23 @@ public final class CachedAppOptimizer { } } } + + /** + * Default implementation for ProcessDependencies, public vor visibility to OomAdjuster class. + */ + private static final class DefaultProcessDependencies implements ProcessDependencies { + // Get memory RSS from process. + @Override + public long[] getRss(int pid) { + return Process.getRss(pid); + } + + // Compact process. + @Override + public void performCompaction(String action, int pid) throws IOException { + try (FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim")) { + fos.write(action.getBytes()); + } + } + } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 98d662a1a9b5..2423f43d8283 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -955,6 +955,8 @@ public class AudioService extends IAudioService.Stub mMicMuteFromSystemCached = mAudioSystem.isMicrophoneMuted(); setMicMuteFromSwitchInput(); + + initMinStreamVolumeWithoutModifyAudioSettings(); } RoleObserver mRoleObserver; @@ -1308,7 +1310,7 @@ public class AudioService extends IAudioService.Stub mStreamStates[streamType].setIndex( mStreamStates[mStreamVolumeAlias[streamType]] .getIndex(AudioSystem.DEVICE_OUT_DEFAULT), - device, caller); + device, caller, true /*hasModifyAudioSettings*/); } mStreamStates[streamType].checkFixedVolumeDevices(); } @@ -1873,13 +1875,16 @@ public class AudioService extends IAudioService.Stub direction, 0 /*ignored*/, extVolCtlr, 0 /*delay*/); } else { + final boolean hasModifyAudioSettings = + mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED; adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage, - caller, Binder.getCallingUid()); + caller, Binder.getCallingUid(), hasModifyAudioSettings); } } private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, - String callingPackage, String caller, int uid) { + String callingPackage, String caller, int uid, boolean hasModifyAudioSettings) { if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType + ", flags=" + flags + ", caller=" + caller + ", volControlStream=" + mVolumeControlStream @@ -1933,10 +1938,12 @@ public class AudioService extends IAudioService.Stub if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment"); } - adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid); + adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid, + hasModifyAudioSettings); } - /** @see AudioManager#adjustStreamVolume(int, int, int) */ + /** @see AudioManager#adjustStreamVolume(int, int, int) + * Part of service interface, check permissions here */ public void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage) { if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) { @@ -1944,14 +1951,17 @@ public class AudioService extends IAudioService.Stub + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage); return; } + final boolean hasModifyAudioSettings = + mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED; sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType, direction/*val1*/, flags/*val2*/, callingPackage)); adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, - Binder.getCallingUid()); + Binder.getCallingUid(), hasModifyAudioSettings); } protected void adjustStreamVolume(int streamType, int direction, int flags, - String callingPackage, String caller, int uid) { + String callingPackage, String caller, int uid, boolean hasModifyAudioSettings) { if (mUseFixedVolume) { return; } @@ -2105,7 +2115,8 @@ public class AudioService extends IAudioService.Stub Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex); mVolumeController.postDisplaySafeVolumeWarning(flags); } else if (!isFullVolumeDevice(device) - && (streamState.adjustIndex(direction * step, device, caller) + && (streamState.adjustIndex(direction * step, device, caller, + hasModifyAudioSettings) || streamState.mIsMuted)) { // Post message to set system volume (it in turn will post a // message to persist). @@ -2327,9 +2338,9 @@ public class AudioService extends IAudioService.Stub } private void onSetStreamVolume(int streamType, int index, int flags, int device, - String caller) { + String caller, boolean hasModifyAudioSettings) { final int stream = mStreamVolumeAlias[streamType]; - setStreamVolumeInt(stream, index, device, false, caller); + setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings); // setting volume on ui sounds stream type also controls silent mode if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (stream == getUiSoundsStreamType())) { @@ -2377,7 +2388,7 @@ public class AudioService extends IAudioService.Stub continue; } setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage, - Binder.getCallingUid()); + Binder.getCallingUid(), true /*hasModifyAudioSettings*/); } } @@ -2419,7 +2430,8 @@ public class AudioService extends IAudioService.Stub return AudioSystem.getMinVolumeIndexForAttributes(attr); } - /** @see AudioManager#setStreamVolume(int, int, int) */ + /** @see AudioManager#setStreamVolume(int, int, int) + * Part of service interface, check permissions here */ public void setStreamVolume(int streamType, int index, int flags, String callingPackage) { if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) { Log.w(TAG, "Trying to call setStreamVolume() for a11y without" @@ -2442,10 +2454,13 @@ public class AudioService extends IAudioService.Stub + " MODIFY_AUDIO_ROUTING callingPackage=" + callingPackage); return; } + final boolean hasModifyAudioSettings = + mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED; sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, index/*val1*/, flags/*val2*/, callingPackage)); setStreamVolume(streamType, index, flags, callingPackage, callingPackage, - Binder.getCallingUid()); + Binder.getCallingUid(), hasModifyAudioSettings); } private boolean canChangeAccessibilityVolume() { @@ -2569,7 +2584,7 @@ public class AudioService extends IAudioService.Stub } private void setStreamVolume(int streamType, int index, int flags, String callingPackage, - String caller, int uid) { + String caller, int uid, boolean hasModifyAudioSettings) { if (DEBUG_VOL) { Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index + ", calling=" + callingPackage + ")"); @@ -2660,7 +2675,7 @@ public class AudioService extends IAudioService.Stub mPendingVolumeCommand = new StreamVolumeCommand( streamType, index, flags, device); } else { - onSetStreamVolume(streamType, index, flags, device, caller); + onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings); index = mStreamStates[streamType].getIndex(device); } } @@ -2872,19 +2887,22 @@ public class AudioService extends IAudioService.Stub * @param index Desired volume index of the stream * @param device the device whose volume must be changed * @param force If true, set the volume even if the desired volume is same + * @param caller + * @param hasModifyAudioSettings true if the caller is granted MODIFY_AUDIO_SETTINGS or + * MODIFY_AUDIO_ROUTING permission * as the current volume. */ private void setStreamVolumeInt(int streamType, int index, int device, boolean force, - String caller) { + String caller, boolean hasModifyAudioSettings) { if (isFullVolumeDevice(device)) { return; } VolumeStreamState streamState = mStreamStates[streamType]; - if (streamState.setIndex(index, device, caller) || force) { + if (streamState.setIndex(index, device, caller, hasModifyAudioSettings) || force) { // Post message to set system volume (it in turn will post a message // to persist). sendMsg(mAudioHandler, @@ -3417,7 +3435,7 @@ public class AudioService extends IAudioService.Stub int device = vss.mIndexMap.keyAt(i); int value = vss.mIndexMap.valueAt(i); if (value == 0) { - vss.setIndex(10, device, TAG); + vss.setIndex(10, device, TAG, true /*hasModifyAudioSettings*/); } } // Persist volume for stream ring when it is changed here @@ -3762,7 +3780,8 @@ public class AudioService extends IAudioService.Stub int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE); int device = getDeviceForStream(streamType); int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device); - setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller); + setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller, + true /*hasModifyAudioSettings*/); updateStreamVolumeAlias(true /*updateVolumes*/, caller); @@ -4671,6 +4690,44 @@ public class AudioService extends IAudioService.Stub return false; } + /** + * Minimum attenuation that can be set for alarms over speaker by an application that + * doesn't have the MODIFY_AUDIO_SETTINGS permission. + */ + protected static final float MIN_ALARM_ATTENUATION_NON_PRIVILEGED_DB = -36.0f; + + /** + * Configures the VolumeStreamState instances for minimum stream index that can be accessed + * without MODIFY_AUDIO_SETTINGS permission. + * Can only be done successfully once audio policy has finished reading its configuration files + * for the volume curves. If not, getStreamVolumeDB will return NaN, and the min value will + * remain at the stream min index value. + */ + protected void initMinStreamVolumeWithoutModifyAudioSettings() { + int idx; + int deviceForAlarm = AudioSystem.DEVICE_OUT_SPEAKER_SAFE; + if (AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_ALARM, + MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM], deviceForAlarm) == Float.NaN) { + deviceForAlarm = AudioSystem.DEVICE_OUT_SPEAKER; + } + for (idx = MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]; + idx >= MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]; idx--) { + if (AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_ALARM, idx, deviceForAlarm) + < MIN_ALARM_ATTENUATION_NON_PRIVILEGED_DB) { + break; + } + } + final int safeIndex = idx <= MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM] + ? MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM] + : Math.min(idx + 1, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]); + // update the VolumeStreamState for STREAM_ALARM and its aliases + for (int stream : mStreamVolumeAlias) { + if (mStreamVolumeAlias[stream] == AudioSystem.STREAM_ALARM) { + mStreamStates[stream].updateNoPermMinIndex(safeIndex); + } + } + } + /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public int getDeviceForStream(int stream) { @@ -5335,6 +5392,8 @@ public class AudioService extends IAudioService.Stub private class VolumeStreamState { private final int mStreamType; private int mIndexMin; + // min index when user doesn't have permission to change audio settings + private int mIndexMinNoPerm; private int mIndexMax; private boolean mIsMuted; @@ -5376,6 +5435,7 @@ public class AudioService extends IAudioService.Stub mStreamType = streamType; mIndexMin = MIN_STREAM_VOLUME[streamType] * 10; + mIndexMinNoPerm = mIndexMin; // may be overwritten later in updateNoPermMinIndex() mIndexMax = MAX_STREAM_VOLUME[streamType] * 10; AudioSystem.initStreamVolume(streamType, mIndexMin / 10, mIndexMax / 10); @@ -5386,6 +5446,18 @@ public class AudioService extends IAudioService.Stub mStreamDevicesChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); } + /** + * Update the minimum index that can be used without MODIFY_AUDIO_SETTINGS permission + * @param index minimum index expressed in "UI units", i.e. no 10x factor + */ + public void updateNoPermMinIndex(int index) { + mIndexMinNoPerm = index * 10; + if (mIndexMinNoPerm < mIndexMin) { + Log.e(TAG, "Invalid mIndexMinNoPerm for stream " + mStreamType); + mIndexMinNoPerm = mIndexMin; + } + } + public int observeDevicesForStream_syncVSS(boolean checkOthers) { if (!mSystemServer.isPrivileged()) { return AudioSystem.DEVICE_NONE; @@ -5467,7 +5539,8 @@ public class AudioService extends IAudioService.Stub continue; } - mIndexMap.put(device, getValidIndex(10 * index)); + mIndexMap.put(device, getValidIndex(10 * index, + true /*hasModifyAudioSettings*/)); } } } @@ -5555,17 +5628,20 @@ public class AudioService extends IAudioService.Stub } } - public boolean adjustIndex(int deltaIndex, int device, String caller) { - return setIndex(getIndex(device) + deltaIndex, device, caller); + public boolean adjustIndex(int deltaIndex, int device, String caller, + boolean hasModifyAudioSettings) { + return setIndex(getIndex(device) + deltaIndex, device, caller, + hasModifyAudioSettings); } - public boolean setIndex(int index, int device, String caller) { + public boolean setIndex(int index, int device, String caller, + boolean hasModifyAudioSettings) { boolean changed; int oldIndex; synchronized (mSettingsLock) { synchronized (VolumeStreamState.class) { oldIndex = getIndex(device); - index = getValidIndex(index); + index = getValidIndex(index, hasModifyAudioSettings); if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) { index = mIndexMax; } @@ -5585,10 +5661,12 @@ public class AudioService extends IAudioService.Stub mStreamVolumeAlias[streamType] == mStreamType && (changed || !aliasStreamState.hasIndexForDevice(device))) { final int scaledIndex = rescaleIndex(index, mStreamType, streamType); - aliasStreamState.setIndex(scaledIndex, device, caller); + aliasStreamState.setIndex(scaledIndex, device, caller, + hasModifyAudioSettings); if (isCurrentDevice) { aliasStreamState.setIndex(scaledIndex, - getDeviceForStream(streamType), caller); + getDeviceForStream(streamType), caller, + hasModifyAudioSettings); } } } @@ -5678,7 +5756,7 @@ public class AudioService extends IAudioService.Stub index = srcMap.valueAt(i); index = rescaleIndex(index, srcStreamType, mStreamType); - setIndex(index, device, caller); + setIndex(index, device, caller, true /*hasModifyAudioSettings*/); } } @@ -5745,9 +5823,10 @@ public class AudioService extends IAudioService.Stub } } - private int getValidIndex(int index) { - if (index < mIndexMin) { - return mIndexMin; + private int getValidIndex(int index, boolean hasModifyAudioSettings) { + final int indexMin = hasModifyAudioSettings ? mIndexMin : mIndexMinNoPerm; + if (index < indexMin) { + return indexMin; } else if (mUseFixedVolume || index > mIndexMax) { return mIndexMax; } @@ -5759,7 +5838,13 @@ public class AudioService extends IAudioService.Stub pw.print(" Muted: "); pw.println(mIsMuted); pw.print(" Min: "); - pw.println((mIndexMin + 5) / 10); + pw.print((mIndexMin + 5) / 10); + if (mIndexMin != mIndexMinNoPerm) { + pw.print(" w/o perm:"); + pw.println((mIndexMinNoPerm + 5) / 10); + } else { + pw.println(); + } pw.print(" Max: "); pw.println((mIndexMax + 5) / 10); pw.print(" streamVolume:"); pw.println(getStreamVolume(mStreamType)); @@ -5880,7 +5965,9 @@ public class AudioService extends IAudioService.Stub final VolumeStreamState streamState = mStreamStates[update.mStreamType]; if (update.hasVolumeIndex()) { final int index = update.getVolumeIndex(); - streamState.setIndex(index, update.mDevice, update.mCaller); + streamState.setIndex(index, update.mDevice, update.mCaller, + // trusted as index is always validated before message is posted + true /*hasModifyAudioSettings*/); sVolumeLogger.log(new AudioEventLogger.StringEvent(update.mCaller + " dev:0x" + Integer.toHexString(update.mDevice) + " volIdx:" + index)); } else { @@ -6823,7 +6910,8 @@ public class AudioService extends IAudioService.Stub for (int device : devices) { int index = streamState.getIndex(device); if (index > safeMediaVolumeIndex(device)) { - streamState.setIndex(safeMediaVolumeIndex(device), device, caller); + streamState.setIndex(safeMediaVolumeIndex(device), device, caller, + true /*hasModifyAudioSettings*/); sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, @@ -6857,7 +6945,7 @@ public class AudioService extends IAudioService.Stub mPendingVolumeCommand.mIndex, mPendingVolumeCommand.mFlags, mPendingVolumeCommand.mDevice, - callingPackage); + callingPackage, true /*hasModifyAudioSettings*/); mPendingVolumeCommand = null; } } @@ -7465,29 +7553,39 @@ public class AudioService extends IAudioService.Stub @Override public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags, - String callingPackage, int uid) { + String callingPackage, int uid, int pid) { + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; // direction and stream type swap here because the public // adjustSuggested has a different order than the other methods. adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage, - callingPackage, uid); + callingPackage, uid, hasModifyAudioSettings); } @Override public void adjustStreamVolumeForUid(int streamType, int direction, int flags, - String callingPackage, int uid) { + String callingPackage, int uid, int pid) { if (direction != AudioManager.ADJUST_SAME) { sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType, direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage) .append(" uid:").append(uid).toString())); } + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; adjustStreamVolume(streamType, direction, flags, callingPackage, - callingPackage, uid); + callingPackage, uid, hasModifyAudioSettings); } @Override public void setStreamVolumeForUid(int streamType, int direction, int flags, - String callingPackage, int uid) { - setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid); + String callingPackage, int uid, int pid) { + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; + setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid, + hasModifyAudioSettings); } @Override diff --git a/services/core/java/com/android/server/compat/TEST_MAPPING b/services/core/java/com/android/server/compat/TEST_MAPPING index 0c30c790c5dd..bc1c7287d04a 100644 --- a/services/core/java/com/android/server/compat/TEST_MAPPING +++ b/services/core/java/com/android/server/compat/TEST_MAPPING @@ -15,7 +15,7 @@ }, // CTS tests { - "name": "CtsAppCompatHostTestCases#" + "name": "CtsAppCompatHostTestCases" } ] }
\ No newline at end of file diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index a099606852d0..b9cd43d0803d 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -51,6 +51,11 @@ public abstract class BrightnessMappingStrategy { private static final float MAX_GRAD = 1.0f; private static final float SHORT_TERM_MODEL_THRESHOLD_RATIO = 0.6f; + // Constant that ensures that each step of the curve can increase by up to at least + // MIN_PERMISSABLE_INCREASE. Otherwise when the brightness is set to 0, the curve will never + // increase and will always be 0. + private static final float MIN_PERMISSABLE_INCREASE = 0.004f; + protected boolean mLoggingEnabled; private static final Plog PLOG = Plog.createSystemPlog(TAG); @@ -400,7 +405,9 @@ public abstract class BrightnessMappingStrategy { for (int i = idx+1; i < lux.length; i++) { float currLux = lux[i]; float currBrightness = brightness[i]; - float maxBrightness = prevBrightness * permissibleRatio(currLux, prevLux); + float maxBrightness = MathUtils.max( + prevBrightness * permissibleRatio(currLux, prevLux), + prevBrightness + MIN_PERMISSABLE_INCREASE); float newBrightness = MathUtils.constrain( currBrightness, prevBrightness, maxBrightness); if (newBrightness == currBrightness) { diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index a435f1e16b80..53205add0b38 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -60,7 +60,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider private Connection mActiveConnection; private boolean mConnectionReady; - private RouteDiscoveryPreference mPendingDiscoveryPreference = null; + private RouteDiscoveryPreference mLastDiscoveryPreference = null; MediaRoute2ProviderServiceProxy(@NonNull Context context, @NonNull ComponentName componentName, int userId) { @@ -98,11 +98,10 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider @Override public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { + mLastDiscoveryPreference = discoveryPreference; if (mConnectionReady) { mActiveConnection.updateDiscoveryPreference(discoveryPreference); updateBinding(); - } else { - mPendingDiscoveryPreference = discoveryPreference; } } @@ -277,9 +276,8 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider private void onConnectionReady(Connection connection) { if (mActiveConnection == connection) { mConnectionReady = true; - if (mPendingDiscoveryPreference != null) { - updateDiscoveryPreference(mPendingDiscoveryPreference); - mPendingDiscoveryPreference = null; + if (mLastDiscoveryPreference != null) { + updateDiscoveryPreference(mLastDiscoveryPreference); } } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 67f9782d1a6d..02b7582a8637 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -328,7 +328,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void run() { try { mAudioManagerInternal.setStreamVolumeForUid(stream, volumeValue, flags, - opPackageName, uid); + opPackageName, uid, pid); } catch (IllegalArgumentException | SecurityException e) { Log.e(TAG, "Cannot set volume: stream=" + stream + ", value=" + volumeValue + ", flags=" + flags, e); @@ -501,12 +501,15 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR // Must use opPackageName for adjusting volumes with UID. final String opPackageName; final int uid; + final int pid; if (asSystemService) { opPackageName = mContext.getOpPackageName(); uid = Process.SYSTEM_UID; + pid = Process.myPid(); } else { opPackageName = callingOpPackageName; uid = callingUid; + pid = callingPid; } mHandler.post(new Runnable() { @Override @@ -515,15 +518,15 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR if (useSuggested) { if (AudioSystem.isStreamActive(stream, 0)) { mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, - direction, flags, opPackageName, uid); + direction, flags, opPackageName, uid, pid); } else { mAudioManagerInternal.adjustSuggestedStreamVolumeForUid( AudioManager.USE_DEFAULT_STREAM_TYPE, direction, - flags | previousFlagPlaySound, opPackageName, uid); + flags | previousFlagPlaySound, opPackageName, uid, pid); } } else { mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags, - opPackageName, uid); + opPackageName, uid, pid); } } catch (IllegalArgumentException | SecurityException e) { Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream=" diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 84ec440dde0e..e16582f8e916 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -2107,16 +2107,19 @@ public class MediaSessionService extends SystemService implements Monitor { public void run() { final String callingOpPackageName; final int callingUid; + final int callingPid; if (asSystemService) { callingOpPackageName = mContext.getOpPackageName(); callingUid = Process.myUid(); + callingPid = Process.myPid(); } else { callingOpPackageName = opPackageName; callingUid = uid; + callingPid = pid; } try { mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(suggestedStream, - direction, flags, callingOpPackageName, callingUid); + direction, flags, callingOpPackageName, callingUid, callingPid); } catch (SecurityException | IllegalArgumentException e) { Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", suggestedStream=" + suggestedStream + ", flags=" + flags diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index fdee9f86bfaf..d7e724780c94 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -45,7 +45,7 @@ import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.text.TextUtils; -import android.util.Log; +import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -58,7 +58,8 @@ import java.util.Objects; // TODO: check thread safety. We may need to use lock to protect variables. class SystemMediaRoute2Provider extends MediaRoute2Provider { private static final String TAG = "MR2SystemProvider"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + // TODO(b/156996903): Revert it when releasing the framework. + private static final boolean DEBUG = true; static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE"; static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE"; @@ -269,7 +270,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { builder.addRoute(route); } } - setProviderState(builder.build()); + MediaRoute2ProviderInfo providerInfo = builder.build(); + setProviderState(providerInfo); + if (DEBUG) { + Slog.d(TAG, "Updating system provider info : " + providerInfo); + } } /** @@ -327,6 +332,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { if (Objects.equals(oldSessionInfo, newSessionInfo)) { return false; } else { + if (DEBUG) { + Slog.d(TAG, "Updating system routing session info : " + newSessionInfo); + } mSessionInfos.clear(); mSessionInfos.add(newSessionInfo); mDefaultSessionInfo = new RoutingSessionInfo.Builder( diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 38c65f11a717..9d56d817440b 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -1936,6 +1936,9 @@ public class PreferencesHelper implements RankingConfig { event.writeInt(channel.getImportance()); event.writeInt(channel.getUserLockedFields()); event.writeBoolean(channel.isDeleted()); + event.writeBoolean(channel.getConversationId() != null); + event.writeBoolean(channel.isDemoted()); + event.writeBoolean(channel.isImportantConversation()); events.add(event.build()); } } diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 70d1adecc6f3..f9d805e57305 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -417,7 +417,7 @@ public class AppsFilter { public void grantImplicitAccess(int recipientUid, int visibleUid) { if (recipientUid != visibleUid && mImplicitlyQueryable.add(recipientUid, visibleUid) && DEBUG_LOGGING) { - Slog.wtf(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid); + Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid); } } @@ -720,7 +720,7 @@ public class AppsFilter { return false; } if (callingSetting == null) { - Slog.wtf(TAG, "No setting found for non system uid " + callingUid); + Slog.w(TAG, "No setting found for non system uid " + callingUid); return true; } final PackageSetting callingPkgSetting; @@ -760,7 +760,7 @@ public class AppsFilter { final AndroidPackage targetPkg = targetPkgSetting.pkg; if (targetPkg == null) { if (DEBUG_LOGGING) { - Slog.wtf(TAG, "shouldFilterApplication: " + "targetPkg is null"); + Slog.w(TAG, "shouldFilterApplication: " + "targetPkg is null"); } return true; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 3e587bf01521..f3619f22d231 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3181,7 +3181,7 @@ public class PackageManagerService extends IPackageManager.Stub } } - final int cachedSystemApps = PackageParser.sCachedPackageReadCount.get(); + final int cachedSystemApps = PackageCacher.sCachedPackageReadCount.get(); // Remove any shared userIDs that have no associated packages mSettings.pruneSharedUsersLPw(); @@ -3315,7 +3315,7 @@ public class PackageManagerService extends IPackageManager.Stub // This must be done last to ensure all stubs are replaced or disabled. installSystemStubPackages(stubSystemApps, scanFlags); - final int cachedNonSystemApps = PackageParser.sCachedPackageReadCount.get() + final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get() - cachedSystemApps; final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime; @@ -13221,7 +13221,9 @@ public class PackageManagerService extends IPackageManager.Stub private void enforceCanSetPackagesSuspendedAsUser(String callingPackage, int callingUid, int userId, String callingMethod) { - if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) { + if (callingUid == Process.ROOT_UID + // Need to compare app-id to allow system dialogs access on secondary users + || UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) { return; } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 9de34a92cdf7..a5b1bf98cdb7 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -5595,10 +5595,7 @@ public final class Settings { userId); } else if (packageSetting.sharedUser == null && !isUpgradeToR) { Slog.w(TAG, "Missing permission state for package: " + packageName); - generateFallbackPermissionsStateLpr( - packageSetting.pkg.getRequestedPermissions(), - packageSetting.pkg.getTargetSdkVersion(), - packageSetting.getPermissionsState(), userId); + packageSetting.getPermissionsState().setMissing(true, userId); } } @@ -5616,22 +5613,7 @@ public final class Settings { userId); } else if (!isUpgradeToR) { Slog.w(TAG, "Missing permission state for shared user: " + sharedUserName); - ArraySet<String> requestedPermissions = new ArraySet<>(); - int targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - int sharedUserPackagesSize = sharedUserSetting.packages.size(); - for (int packagesI = 0; packagesI < sharedUserPackagesSize; packagesI++) { - PackageSetting packageSetting = sharedUserSetting.packages.valueAt( - packagesI); - if (packageSetting == null || packageSetting.pkg == null - || !packageSetting.getInstalled(userId)) { - continue; - } - AndroidPackage pkg = packageSetting.pkg; - requestedPermissions.addAll(pkg.getRequestedPermissions()); - targetSdkVersion = Math.min(targetSdkVersion, pkg.getTargetSdkVersion()); - } - generateFallbackPermissionsStateLpr(requestedPermissions, targetSdkVersion, - sharedUserSetting.getPermissionsState(), userId); + sharedUserSetting.getPermissionsState().setMissing(true, userId); } } } @@ -5663,30 +5645,6 @@ public final class Settings { } } - private void generateFallbackPermissionsStateLpr( - @NonNull Collection<String> requestedPermissions, int targetSdkVersion, - @NonNull PermissionsState permissionsState, @UserIdInt int userId) { - for (String permissionName : requestedPermissions) { - BasePermission permission = mPermissions.getPermission(permissionName); - if (Objects.equals(permission.getSourcePackageName(), PLATFORM_PACKAGE_NAME) - && permission.isRuntime() && !permission.isRemoved()) { - if (permission.isHardOrSoftRestricted() || permission.isImmutablyRestricted()) { - permissionsState.updatePermissionFlags(permission, userId, - PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, - PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT); - } - if (targetSdkVersion < Build.VERSION_CODES.M) { - permissionsState.updatePermissionFlags(permission, userId, - PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED - | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, - PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED - | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT); - permissionsState.grantRuntimePermission(permission, userId); - } - } - } - } - @GuardedBy("Settings.this.mLock") private void readLegacyStateForUserSyncLPr(int userId) { File permissionsFile = getUserRuntimePermissionsFile(userId); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 7d49f788c063..b0d4d957fc21 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -124,6 +124,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.RoSystemProperties; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.DumpUtils; import com.android.internal.util.IntPair; import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; @@ -154,6 +155,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -421,6 +423,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { + return; + } + mContext.getSystemService(PermissionControllerManager.class).dump(fd, args); } @@ -2473,13 +2479,60 @@ public class PermissionManagerService extends IPermissionManager.Stub { } final PermissionsState permissionsState = ps.getPermissionsState(); - PermissionsState origPermissions = permissionsState; final int[] currentUserIds = UserManagerService.getInstance().getUserIds(); boolean runtimePermissionsRevoked = false; int[] updatedUserIds = EMPTY_INT_ARRAY; + for (int userId : currentUserIds) { + if (permissionsState.isMissing(userId)) { + Collection<String> requestedPermissions; + int targetSdkVersion; + if (!ps.isSharedUser()) { + requestedPermissions = pkg.getRequestedPermissions(); + targetSdkVersion = pkg.getTargetSdkVersion(); + } else { + requestedPermissions = new ArraySet<>(); + targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; + List<AndroidPackage> packages = ps.getSharedUser().getPackages(); + int packagesSize = packages.size(); + for (int i = 0; i < packagesSize; i++) { + AndroidPackage sharedUserPackage = packages.get(i); + requestedPermissions.addAll(sharedUserPackage.getRequestedPermissions()); + targetSdkVersion = Math.min(targetSdkVersion, + sharedUserPackage.getTargetSdkVersion()); + } + } + + for (String permissionName : requestedPermissions) { + BasePermission permission = mSettings.getPermission(permissionName); + if (Objects.equals(permission.getSourcePackageName(), PLATFORM_PACKAGE_NAME) + && permission.isRuntime() && !permission.isRemoved()) { + if (permission.isHardOrSoftRestricted() + || permission.isImmutablyRestricted()) { + permissionsState.updatePermissionFlags(permission, userId, + PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, + PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT); + } + if (targetSdkVersion < Build.VERSION_CODES.M) { + permissionsState.updatePermissionFlags(permission, userId, + PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED + | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, + PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED + | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT); + permissionsState.grantRuntimePermission(permission, userId); + } + } + } + + permissionsState.setMissing(false, userId); + updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); + } + } + + PermissionsState origPermissions = permissionsState; + boolean changedInstallPermission = false; if (replace) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java index 11e29a02068c..bad59cb1b567 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionsState.java +++ b/services/core/java/com/android/server/pm/permission/PermissionsState.java @@ -16,6 +16,8 @@ package com.android.server.pm.permission; +import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.content.pm.PackageManager; import android.os.UserHandle; import android.util.ArrayMap; @@ -30,6 +32,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -70,6 +73,9 @@ public final class PermissionsState { private int[] mGlobalGids = NO_GIDS; + @Nullable + private SparseBooleanArray mMissing; + private SparseBooleanArray mPermissionReviewRequired; public PermissionsState() { @@ -132,6 +138,23 @@ public final class PermissionsState { other.mGlobalGids.length); } + if (mMissing != null) { + if (other.mMissing == null) { + mMissing = null; + } else { + mMissing.clear(); + } + } + if (other.mMissing != null) { + if (mMissing == null) { + mMissing = new SparseBooleanArray(); + } + final int missingSize = other.mMissing.size(); + for (int i = 0; i < missingSize; i++) { + mMissing.put(other.mMissing.keyAt(i), other.mMissing.valueAt(i)); + } + } + if (mPermissionReviewRequired != null) { if (other.mPermissionReviewRequired == null) { mPermissionReviewRequired = null; @@ -175,6 +198,10 @@ public final class PermissionsState { } } + if (!Objects.equals(mMissing, other.mMissing)) { + return false; + } + if (mPermissionReviewRequired == null) { if (other.mPermissionReviewRequired != null) { return false; @@ -185,6 +212,35 @@ public final class PermissionsState { return Arrays.equals(mGlobalGids, other.mGlobalGids); } + /** + * Check whether the permissions state is missing for a user. This can happen if permission + * state is rolled back and we'll need to generate a reasonable default state to keep the app + * usable. + */ + public boolean isMissing(@UserIdInt int userId) { + return mMissing != null && mMissing.get(userId); + } + + /** + * Set whether the permissions state is missing for a user. This can happen if permission state + * is rolled back and we'll need to generate a reasonable default state to keep the app usable. + */ + public void setMissing(boolean missing, @UserIdInt int userId) { + if (missing) { + if (mMissing == null) { + mMissing = new SparseBooleanArray(); + } + mMissing.put(userId, true); + } else { + if (mMissing != null) { + mMissing.delete(userId); + if (mMissing.size() == 0) { + mMissing = null; + } + } + } + } + public boolean isPermissionReviewRequired(int userId) { return mPermissionReviewRequired != null && mPermissionReviewRequired.get(userId); } @@ -569,6 +625,7 @@ public final class PermissionsState { invalidateCache(); } + mMissing = null; mPermissionReviewRequired = null; } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 3336697ef359..f075790a2fa0 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -301,10 +301,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { String packageName = intent.getData().getSchemeSpecificPart(); - if (LOCAL_LOGV) { - Slog.v(TAG, "broadcast=ACTION_PACKAGE_FULLY_REMOVED" - + " pkg=" + packageName); - } + Slog.i(TAG, "broadcast=ACTION_PACKAGE_FULLY_REMOVED pkg=" + packageName); onPackageFullyRemoved(packageName); } } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index a859a42d8a8f..9871623becf5 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -321,17 +321,10 @@ public class StatsPullAtomService extends SystemService { try { switch (atomTag) { case FrameworkStatsLog.WIFI_BYTES_TRANSFER: - return pullDataBytesTransfer(atomTag, data, TRANSPORT_WIFI, - /*withFgbg=*/ false); case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: - return pullDataBytesTransfer(atomTag, data, TRANSPORT_WIFI, - /*withFgbg=*/ true); case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: - return pullDataBytesTransfer(atomTag, data, TRANSPORT_CELLULAR, - /*withFgbg=*/ false); case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: - return pullDataBytesTransfer(atomTag, data, TRANSPORT_CELLULAR, - /*withFgbg=*/ true); + return pullDataBytesTransfer(atomTag, data); case FrameworkStatsLog.BLUETOOTH_BYTES_TRANSFER: return pullBluetoothBytesTransfer(atomTag, data); case FrameworkStatsLog.KERNEL_WAKELOCK: @@ -639,10 +632,14 @@ public class StatsPullAtomService extends SystemService { Slog.d(TAG, "Registering NetworkStats pullers with statsd"); } // Initialize NetworkStats baselines. - mNetworkStatsBaselines.addAll(collectWifiBytesTransferSnapshot(/*withFgbg=*/ false)); - mNetworkStatsBaselines.addAll(collectWifiBytesTransferSnapshot(/*withFgbg=*/ true)); - mNetworkStatsBaselines.addAll(collectMobileBytesTransferSnapshot(/*withFgbg=*/ false)); - mNetworkStatsBaselines.addAll(collectMobileBytesTransferSnapshot(/*withFgbg=*/ true)); + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER)); + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG)); + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER)); + mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG)); registerWifiBytesTransfer(); registerWifiBytesTransferBackground(); @@ -800,36 +797,42 @@ public class StatsPullAtomService extends SystemService { } @NonNull - private List<NetworkStatsExt> collectWifiBytesTransferSnapshot(boolean withFgbg) { - final List<NetworkStatsExt> ret = new ArrayList<>(); - final NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard(); - final NetworkStats stats = getUidNetworkStatsSnapshot(template, withFgbg); - if (stats != null) { - ret.add(new NetworkStatsExt(stats, TRANSPORT_WIFI, withFgbg)); - } - return ret; - } - - // Get a snapshot of mobile data usage. The snapshot contains NetworkStats with its associated + private List<NetworkStatsExt> collectNetworkStatsSnapshotForAtom(int atomTag) { + switch(atomTag) { + case FrameworkStatsLog.WIFI_BYTES_TRANSFER: + return collectUidNetworkStatsSnapshot(TRANSPORT_WIFI, /*withFgbg=*/false); + case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: + return collectUidNetworkStatsSnapshot(TRANSPORT_WIFI, /*withFgbg=*/true); + case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: + return collectUidNetworkStatsSnapshot(TRANSPORT_CELLULAR, /*withFgbg=*/false); + case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: + return collectUidNetworkStatsSnapshot(TRANSPORT_CELLULAR, /*withFgbg=*/true); + default: + throw new IllegalArgumentException("Unknown atomTag " + atomTag); + } + } + + // Get a snapshot of Uid NetworkStats. The snapshot contains NetworkStats with its associated // information, and wrapped by a list since multiple NetworkStatsExt objects might be collected. - // TODO: Slice NetworkStats to multiple objects by RAT type or subscription. @NonNull - private List<NetworkStatsExt> collectMobileBytesTransferSnapshot(boolean withFgbg) { + private List<NetworkStatsExt> collectUidNetworkStatsSnapshot(int transport, boolean withFgbg) { final List<NetworkStatsExt> ret = new ArrayList<>(); - final NetworkTemplate template = - NetworkTemplate.buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL); + final NetworkTemplate template = (transport == TRANSPORT_CELLULAR + ? NetworkTemplate.buildTemplateMobileWithRatType( + /*subscriptionId=*/null, NETWORK_TYPE_ALL) + : NetworkTemplate.buildTemplateWifiWildcard()); + final NetworkStats stats = getUidNetworkStatsSnapshot(template, withFgbg); if (stats != null) { - ret.add(new NetworkStatsExt(stats, TRANSPORT_CELLULAR, withFgbg)); + ret.add(new NetworkStatsExt(stats, transport, withFgbg)); } return ret; } + private int pullDataBytesTransfer( - int atomTag, @NonNull List<StatsEvent> pulledData, int transport, boolean withFgbg) { - final List<NetworkStatsExt> current = - (transport == TRANSPORT_CELLULAR ? collectMobileBytesTransferSnapshot(withFgbg) - : collectWifiBytesTransferSnapshot(withFgbg)); + int atomTag, @NonNull List<StatsEvent> pulledData) { + final List<NetworkStatsExt> current = collectNetworkStatsSnapshotForAtom(atomTag); if (current == null) { Slog.e(TAG, "current snapshot is null for " + atomTag + ", return."); @@ -844,7 +847,7 @@ public class StatsPullAtomService extends SystemService { // skip reporting anything since the snapshot is invalid. if (baseline == null) { Slog.e(TAG, "baseline is null for " + atomTag + ", transport=" - + item.transport + " , withFgbg=" + withFgbg + ", return."); + + item.transport + " , withFgbg=" + item.withFgbg + ", return."); return StatsManager.PULL_SKIP; } final NetworkStatsExt diff = new NetworkStatsExt(item.stats.subtract( diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index 72cdf4afb007..15b6a8df0151 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -114,7 +114,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { private static final String TAG = "UriGrantsManagerService"; // Maximum number of persisted Uri grants a package is allowed private static final int MAX_PERSISTED_URI_GRANTS = 128; - private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false; + private static final boolean ENABLE_DYNAMIC_PERMISSIONS = true; private final Object mLock = new Object(); private final H mH; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0598680f8c5d..b5b82d39b921 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3589,7 +3589,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom, - WindowContainer boundary) { + ActivityRecord boundary) { return callback.test(this) ? this : null; } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 2f868d949970..9b9b61340332 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -2708,7 +2708,9 @@ class ActivityStack extends Task { */ @Nullable private ActivityRecord getOccludingActivityAbove(ActivityRecord activity) { - return getActivity((ar) -> ar.occludesParent(), true /* traverseTopToBottom */, activity); + ActivityRecord top = getActivity((ar) -> ar.occludesParent(), + true /* traverseTopToBottom */, activity); + return top != activity ? top : null; } boolean willActivityBeVisible(IBinder token) { diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index dfa3fe088770..c28d47cdbe80 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.app.ActivityManager.START_SUCCESS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL; @@ -193,9 +192,7 @@ public class ActivityStartController { final ActivityStack homeStack; try { // Make sure home stack exists on display area. - // TODO(b/153624902): Replace with TaskDisplayArea#getOrCreateRootHomeTask() - homeStack = taskDisplayArea.getOrCreateStack(WINDOWING_MODE_UNDEFINED, - ACTIVITY_TYPE_HOME, ON_TOP); + homeStack = taskDisplayArea.getOrCreateRootHomeTask(ON_TOP); } finally { mSupervisor.endDeferResume(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 78e4237eb4a7..fdbb2b25bd39 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3319,7 +3319,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public void resizeTask(int taskId, Rect bounds, int resizeMode) { + public boolean resizeTask(int taskId, Rect bounds, int resizeMode) { mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeTask()"); long ident = Binder.clearCallingIdentity(); try { @@ -3328,10 +3328,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { MATCH_TASK_IN_STACKS_ONLY); if (task == null) { Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found"); - return; + return false; } if (!task.getWindowConfiguration().canResizeTask()) { - throw new IllegalArgumentException("resizeTask not allowed on task=" + task); + Slog.w(TAG, "resizeTask not allowed on task=" + task); + return false; } // Reparent the task to the right stack if necessary @@ -3339,7 +3340,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // After reparenting (which only resizes the task to the stack bounds), resize the // task to the actual bounds provided - task.resize(bounds, resizeMode, preserveWindow); + return task.resize(bounds, resizeMode, preserveWindow); } } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 345cfb0aad71..a45a15ba2012 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -66,6 +66,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { final int mFeatureId; private final DisplayAreaOrganizerController mOrganizerController; IDisplayAreaOrganizer mOrganizer; + private final Configuration mTmpConfiguration = new Configuration(); DisplayArea(WindowManagerService wms, Type type, String name) { this(wms, type, name, FEATURE_UNDEFINED); @@ -162,8 +163,10 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { @Override public void onConfigurationChanged(Configuration newParentConfig) { + mTmpConfiguration.setTo(getConfiguration()); super.onConfigurationChanged(newParentConfig); - if (mOrganizer != null) { + + if (mOrganizer != null && getConfiguration().diff(mTmpConfiguration) != 0) { mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 0b2bd811bb84..0efb9698f4b0 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -498,8 +498,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ ActivityRecord mFixedRotationLaunchingApp; - FixedRotationAnimationController mFixedRotationAnimationController; - final FixedRotationTransitionListener mFixedRotationTransitionListener = new FixedRotationTransitionListener(); @@ -1540,11 +1538,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } private void startFixedRotationTransform(WindowToken token, int rotation) { - if (mFixedRotationAnimationController == null) { - mFixedRotationAnimationController = new FixedRotationAnimationController( - this); - } - mFixedRotationAnimationController.hide(rotation); mTmpConfiguration.unset(); final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation); final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation); @@ -1566,13 +1559,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - void finishFixedRotationAnimation() { - if (mFixedRotationAnimationController != null - && mFixedRotationAnimationController.show()) { - mFixedRotationAnimationController = null; - } - } - /** * Update rotation of the display. * @@ -4700,9 +4686,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo boolean supportsSystemDecorations() { return (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this) || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0 - || (mWmService.mForceDesktopModeOnExternalDisplays && !isUntrustedVirtualDisplay())) + || mWmService.mForceDesktopModeOnExternalDisplays) // VR virtual display will be used to run and render 2D app within a VR experience. - && mDisplayId != mWmService.mVr2dDisplayId; + && mDisplayId != mWmService.mVr2dDisplayId + // Do not show system decorations on untrusted virtual display. + && !isUntrustedVirtualDisplay(); } /** diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index c3f906135a00..ebfe70c0c371 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -560,7 +560,6 @@ public class DisplayRotation { }, true /* traverseTopToBottom */); mSeamlessRotationCount = 0; mRotatingSeamlessly = false; - mDisplayContent.finishFixedRotationAnimation(); } private void prepareSeamlessRotation() { @@ -647,7 +646,6 @@ public class DisplayRotation { "Performing post-rotate rotation after seamless rotation"); // Finish seamless rotation. mRotatingSeamlessly = false; - mDisplayContent.finishFixedRotationAnimation(); updateRotationAndSendNewConfigIfChanged(); } diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java deleted file mode 100644 index 7aca63774889..000000000000 --- a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java +++ /dev/null @@ -1,197 +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. - */ - -package com.android.server.wm; - -import static com.android.server.wm.AnimationSpecProto.WINDOW; -import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM; -import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION; - -import android.content.res.Configuration; -import android.util.proto.ProtoOutputStream; -import android.view.SurfaceControl; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.animation.Transformation; - -import com.android.internal.R; - -import java.io.PrintWriter; -import java.util.ArrayList; - -/** - * Controller to fade out and in system ui when applying a fixed rotation transform to a window - * token. - * - * The system bars will be fade out when the fixed rotation transform starts and will be fade in - * once all surfaces have been rotated. - */ -public class FixedRotationAnimationController { - - private final WindowManagerService mWmService; - private boolean mShowRequested = true; - private int mTargetRotation = Configuration.ORIENTATION_UNDEFINED; - private final ArrayList<WindowState> mAnimatedWindowStates = new ArrayList<>(2); - private final Runnable[] mDeferredFinishCallbacks; - - public FixedRotationAnimationController(DisplayContent displayContent) { - mWmService = displayContent.mWmService; - addAnimatedWindow(displayContent.getDisplayPolicy().getStatusBar()); - addAnimatedWindow(displayContent.getDisplayPolicy().getNavigationBar()); - mDeferredFinishCallbacks = new Runnable[mAnimatedWindowStates.size()]; - } - - private void addAnimatedWindow(WindowState windowState) { - if (windowState != null) { - mAnimatedWindowStates.add(windowState); - } - } - - /** - * Show the previously hidden {@link WindowToken} if their surfaces have already been rotated. - * - * @return True if the show animation has been started, in which case the caller no longer needs - * this {@link FixedRotationAnimationController}. - */ - boolean show() { - if (!mShowRequested && readyToShow()) { - mShowRequested = true; - for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) { - WindowState windowState = mAnimatedWindowStates.get(i); - fadeWindowToken(true, windowState.getParent(), i); - } - return true; - } - return false; - } - - void hide(int targetRotation) { - mTargetRotation = targetRotation; - if (mShowRequested) { - mShowRequested = false; - for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) { - WindowState windowState = mAnimatedWindowStates.get(i); - fadeWindowToken(false /* show */, windowState.getParent(), i); - } - } - } - - void cancel() { - for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) { - WindowState windowState = mAnimatedWindowStates.get(i); - mShowRequested = true; - fadeWindowToken(true /* show */, windowState.getParent(), i); - } - } - - private void fadeWindowToken(boolean show, WindowContainer<WindowToken> windowToken, - int index) { - Animation animation = AnimationUtils.loadAnimation(mWmService.mContext, - show ? R.anim.fade_in : R.anim.fade_out); - LocalAnimationAdapter.AnimationSpec windowAnimationSpec = createAnimationSpec(animation); - - FixedRotationAnimationAdapter animationAdapter = new FixedRotationAnimationAdapter( - windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, index); - - // We deferred the end of the animation when hiding the token, so we need to end it now that - // it's shown again. - SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> { - if (mDeferredFinishCallbacks[index] != null) { - mDeferredFinishCallbacks[index].run(); - mDeferredFinishCallbacks[index] = null; - } - } : null; - windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter, - mShowRequested, ANIMATION_TYPE_FIXED_TRANSFORM, finishedCallback); - } - - /** - * Check if all the mAnimatedWindowState's surfaces have been rotated to the - * mTargetRotation. - */ - private boolean readyToShow() { - for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) { - WindowState windowState = mAnimatedWindowStates.get(i); - if (windowState.getConfiguration().windowConfiguration.getRotation() - != mTargetRotation || windowState.mPendingSeamlessRotate != null) { - return false; - } - } - return true; - } - - - private LocalAnimationAdapter.AnimationSpec createAnimationSpec(Animation animation) { - return new LocalAnimationAdapter.AnimationSpec() { - - Transformation mTransformation = new Transformation(); - - @Override - public boolean getShowWallpaper() { - return true; - } - - @Override - public long getDuration() { - return animation.getDuration(); - } - - @Override - public void apply(SurfaceControl.Transaction t, SurfaceControl leash, - long currentPlayTime) { - mTransformation.clear(); - animation.getTransformation(currentPlayTime, mTransformation); - t.setAlpha(leash, mTransformation.getAlpha()); - } - - @Override - public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); - pw.println(animation); - } - - @Override - public void dumpDebugInner(ProtoOutputStream proto) { - final long token = proto.start(WINDOW); - proto.write(ANIMATION, animation.toString()); - proto.end(token); - } - }; - } - - private class FixedRotationAnimationAdapter extends LocalAnimationAdapter { - private final boolean mShow; - private final int mIndex; - - FixedRotationAnimationAdapter(AnimationSpec windowAnimationSpec, - SurfaceAnimationRunner surfaceAnimationRunner, boolean show, int index) { - super(windowAnimationSpec, surfaceAnimationRunner); - mShow = show; - mIndex = index; - } - - @Override - public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) { - // We defer the end of the hide animation to ensure the tokens stay hidden until - // we show them again. - if (!mShow) { - mDeferredFinishCallbacks[mIndex] = endDeferFinishCallback; - return true; - } - return false; - } - } -} diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index c02e0a11a0c5..c7f78342c829 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -387,9 +387,11 @@ class RemoteAnimationController implements DeathRecipient { int getMode() { final DisplayContent dc = mWindowContainer.getDisplayContent(); final ActivityRecord topActivity = mWindowContainer.getTopMostActivity(); + // Note that opening/closing transitions are per-activity while changing transitions + // are per-task. if (dc.mOpeningApps.contains(topActivity)) { return RemoteAnimationTarget.MODE_OPENING; - } else if (dc.mChangingContainers.contains(topActivity)) { + } else if (dc.mChangingContainers.contains(mWindowContainer)) { return RemoteAnimationTarget.MODE_CHANGING; } else { return RemoteAnimationTarget.MODE_CLOSING; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 888a6e986e88..0ecde72cc566 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1369,7 +1369,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> calculateDefaultMinimalSizeOfResizeableTasks(); final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea(); - defaultTaskDisplayArea.getOrCreateRootHomeTask(); + defaultTaskDisplayArea.getOrCreateRootHomeTask(ON_TOP); positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent, false /* includingParents */); } diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 0143eb1abe03..42342a60ba16 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -489,12 +489,6 @@ class SurfaceAnimator { static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5; /** - * Animation when a fixed rotation transform is applied to a window token. - * @hide - */ - static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6; - - /** * Bitmask to include all animation types. This is NOT an {@link AnimationType} * @hide */ @@ -511,8 +505,7 @@ class SurfaceAnimator { ANIMATION_TYPE_DIMMER, ANIMATION_TYPE_RECENTS, ANIMATION_TYPE_WINDOW_ANIMATION, - ANIMATION_TYPE_INSETS_CONTROL, - ANIMATION_TYPE_FIXED_TRANSFORM + ANIMATION_TYPE_INSETS_CONTROL }) @Retention(RetentionPolicy.SOURCE) @interface AnimationType {} diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 37a4c1f6849b..6ce36f1a3eb6 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1461,16 +1461,23 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { return mChildren.get(index); } + @Nullable + ActivityStack getOrCreateRootHomeTask() { + return getOrCreateRootHomeTask(false /* onTop */); + } + /** * Returns the existing home stack or creates and returns a new one if it should exist for the * display. + * @param onTop Only be used when there is no existing home stack. If true the home stack will + * be created at the top of the display, else at the bottom. */ @Nullable - ActivityStack getOrCreateRootHomeTask() { + ActivityStack getOrCreateRootHomeTask(boolean onTop) { ActivityStack homeTask = getRootHomeTask(); if (homeTask == null && mDisplayContent.supportsSystemDecorations() && !mDisplayContent.isUntrustedVirtualDisplay()) { - homeTask = createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, false /* onTop */); + homeTask = createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, onTop); } return homeTask; } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java index 45023acf4466..c6e1c954be12 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java @@ -28,12 +28,14 @@ import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.os.Process; import android.os.SystemClock; +import android.os.UserManagerInternal; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto; import java.io.File; @@ -72,6 +74,7 @@ class TaskSnapshotPersister { private final float mLowResScaleFactor; private boolean mEnableLowResSnapshots; private final boolean mUse16BitFormat; + private final UserManagerInternal mUserManagerInternal; /** * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was @@ -82,6 +85,8 @@ class TaskSnapshotPersister { TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) { mDirectoryResolver = resolver; + mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); + final float highResTaskSnapshotScale = service.mContext.getResources().getFloat( com.android.internal.R.dimen.config_highResTaskSnapshotScale); final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat( @@ -191,7 +196,7 @@ class TaskSnapshotPersister { return; } } - SystemClock.sleep(100); + SystemClock.sleep(DELAY_MS); } } @@ -233,7 +238,7 @@ class TaskSnapshotPersister { private boolean createDirectory(int userId) { final File dir = getDirectory(userId); - return dir.exists() || dir.mkdirs(); + return dir.exists() || dir.mkdir(); } private void deleteSnapshot(int taskId, int userId) { @@ -258,18 +263,26 @@ class TaskSnapshotPersister { android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { WriteQueueItem next; + boolean isReadyToWrite = false; synchronized (mLock) { if (mPaused) { next = null; } else { next = mWriteQueue.poll(); if (next != null) { - next.onDequeuedLocked(); + if (next.isReady()) { + isReadyToWrite = true; + next.onDequeuedLocked(); + } else { + mWriteQueue.addLast(next); + } } } } if (next != null) { - next.write(); + if (isReadyToWrite) { + next.write(); + } SystemClock.sleep(DELAY_MS); } synchronized (mLock) { @@ -289,6 +302,13 @@ class TaskSnapshotPersister { }; private abstract class WriteQueueItem { + /** + * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called + */ + boolean isReady() { + return true; + } + abstract void write(); /** @@ -328,6 +348,11 @@ class TaskSnapshotPersister { } @Override + boolean isReady() { + return mUserManagerInternal.isUserUnlocked(mUserId); + } + + @Override void write() { if (!createDirectory(mUserId)) { Slog.e(TAG, "Unable to create snapshot directory for user dir=" diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 7bfddd79f8a4..7757b3a50941 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1415,11 +1415,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom, - WindowContainer boundary) { + ActivityRecord boundary) { if (traverseTopToBottom) { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); - if (wc == boundary) return null; + // TODO(b/156986561): Improve the correctness of the boundary check. + if (wc == boundary) return boundary; final ActivityRecord r = wc.getActivity(callback, traverseTopToBottom, boundary); if (r != null) { @@ -1430,7 +1431,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< final int count = mChildren.size(); for (int i = 0; i < count; i++) { final WindowContainer wc = mChildren.get(i); - if (wc == boundary) return null; + // TODO(b/156986561): Improve the correctness of the boundary check. + if (wc == boundary) return boundary; final ActivityRecord r = wc.getActivity(callback, traverseTopToBottom, boundary); if (r != null) { @@ -2182,7 +2184,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< final int appStackClipMode = getDisplayContent().mAppTransition.getAppStackClipMode(); // Separate position and size for use in animators. - mTmpRect.set(getAnimationBounds(appStackClipMode)); + final Rect screenBounds = getAnimationBounds(appStackClipMode); + mTmpRect.set(screenBounds); getAnimationPosition(mTmpPoint); if (!sHierarchicalAnimations) { // Non-hierarchical animation uses position in global coordinates. @@ -2201,7 +2204,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y); final RemoteAnimationController.RemoteAnimationRecord adapters = controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds, - mTmpRect, (isChanging ? mSurfaceFreezer.mFreezeBounds : null)); + screenBounds, (isChanging ? mSurfaceFreezer.mFreezeBounds : null)); resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter); } else if (isChanging) { final float durationScale = mWmService.getTransitionAnimationScaleLocked(); diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 8739bad4398b..768f89eff774 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -642,9 +642,6 @@ class WindowToken extends WindowContainer<WindowState> { final int originalRotation = getWindowConfiguration().getRotation(); onConfigurationChanged(parent.getConfiguration()); onCancelFixedRotationTransform(originalRotation); - if (mDisplayContent.mFixedRotationAnimationController != null) { - mDisplayContent.mFixedRotationAnimationController.cancel(); - } } /** diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 78439dba2724..f0dca772adaa 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -63,6 +63,7 @@ struct Constants { static constexpr auto libDir = "lib"sv; static constexpr auto libSuffix = ".so"sv; static constexpr auto blockSize = 4096; + static constexpr auto systemPackage = "android"sv; }; static const Constants& constants() { @@ -377,7 +378,8 @@ void IncrementalService::onSystemReady() { std::lock_guard l(mLock); mounts.reserve(mMounts.size()); for (auto&& [id, ifs] : mMounts) { - if (ifs->mountId == id) { + if (ifs->mountId == id && + ifs->dataLoaderStub->params().packageName == Constants::systemPackage) { mounts.push_back(ifs); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java index e5ec1f76c554..96a44a46bbaf 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java @@ -16,14 +16,23 @@ package com.android.server.am; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + import static com.android.server.am.ActivityManagerService.Injector; import static com.android.server.am.CachedAppOptimizer.compactActionIntToString; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.ComponentName; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; import android.os.Handler; import android.os.HandlerThread; +import android.os.MessageQueue; import android.os.Process; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; @@ -31,9 +40,11 @@ import android.text.TextUtils; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.appop.AppOpsService; import com.android.server.testables.TestableDeviceConfig; +import com.android.server.wm.ActivityTaskManagerService; import org.junit.After; import org.junit.Assume; @@ -45,6 +56,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.io.File; +import java.io.IOException; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -68,25 +80,36 @@ public final class CachedAppOptimizerTest { private HandlerThread mHandlerThread; private Handler mHandler; private CountDownLatch mCountDown; + private ActivityManagerService mAms; + private Context mContext; + private TestInjector mInjector; + private TestProcessDependencies mProcessDependencies; + + @Mock + private PackageManagerInternal mPackageManagerInt; @Rule - public TestableDeviceConfig.TestableDeviceConfigRule + public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule(); + @Rule + public final ApplicationExitInfoTest.ServiceThreadRule + mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); @Before public void setUp() { mHandlerThread = new HandlerThread(""); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); - mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */); mThread.start(); - - ActivityManagerService ams = new ActivityManagerService( - new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()), - mThread); - mCachedAppOptimizerUnderTest = new CachedAppOptimizer(ams, + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + mInjector = new TestInjector(mContext); + mAms = new ActivityManagerService( + new TestInjector(mContext), mServiceThreadRule.getThread()); + doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); + mProcessDependencies = new TestProcessDependencies(); + mCachedAppOptimizerUnderTest = new CachedAppOptimizer(mAms, new CachedAppOptimizer.PropertyChangedCallbackForTest() { @Override public void onPropertyChanged() { @@ -94,7 +117,9 @@ public final class CachedAppOptimizerTest { mCountDown.countDown(); } } - }); + }, mProcessDependencies); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); } @After @@ -104,6 +129,19 @@ public final class CachedAppOptimizerTest { mCountDown = null; } + private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, String processName, + String packageName) { + ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = packageName; + ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid); + app.pid = pid; + app.info.uid = packageUid; + // Exact value does not mater, it can be any state for which compaction is allowed. + app.setProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + app.setAdj = 905; + return app; + } + @Test public void init_setsDefaults() { mCachedAppOptimizerUnderTest.init(); @@ -197,7 +235,7 @@ public final class CachedAppOptimizerTest { CachedAppOptimizer.DEFAULT_USE_FREEZER); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, CachedAppOptimizer.KEY_USE_FREEZER, CachedAppOptimizer.DEFAULT_USE_FREEZER - ? "false" : "true" , false); + ? "false" : "true", false); // Then calling init will read and set that flag. mCachedAppOptimizerUnderTest.init(); @@ -790,6 +828,174 @@ public final class CachedAppOptimizerTest { .containsExactlyElementsIn(expected); } + @Test + public void processWithDeltaRSSTooSmall_notFullCompacted() throws Exception { + // Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS + // throttle to 12000. + mCachedAppOptimizerUnderTest.init(); + setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true); + setFlag(CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "12000", false); + initActivityManagerService(); + + // Simulate RSS anon memory larger than throttle. + long[] rssBefore1 = + new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 10000, /*anonRSS*/ 12000, /*swap*/ + 10000}; + long[] rssAfter1 = + new long[]{/*totalRSS*/ 9000, /*fileRSS*/ 9000, /*anonRSS*/ 11000, /*swap*/9000}; + // Delta between rssAfter1 and rssBefore2 is below threshold (500). + long[] rssBefore2 = + new long[]{/*totalRSS*/ 9500, /*fileRSS*/ 9500, /*anonRSS*/ 11500, /*swap*/9500}; + long[] rssAfter2 = + new long[]{/*totalRSS*/ 8000, /*fileRSS*/ 8000, /*anonRSS*/ 9000, /*swap*/8000}; + // Delta between rssAfter1 and rssBefore3 is above threshold (13000). + long[] rssBefore3 = + new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 18000, /*anonRSS*/ 13000, /*swap*/ 7000}; + long[] rssAfter3 = + new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 11000, /*anonRSS*/ 10000, /*swap*/ 6000}; + long[] valuesAfter = {}; + // Process that passes properties. + int pid = 1; + ProcessRecord processRecord = makeProcessRecord(pid, 2, 3, "p1", "app1"); + + // GIVEN we simulate RSS memory before above thresholds and it is the first time 'p1' is + // compacted. + mProcessDependencies.setRss(rssBefore1); + mProcessDependencies.setRssAfterCompaction(rssAfter1); // + // WHEN we try to run compaction + mCachedAppOptimizerUnderTest.compactAppFull(processRecord); + waitForHandler(); + // THEN process IS compacted. + assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); + valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( + pid).getRssAfterCompaction(); + assertThat(valuesAfter).isEqualTo(rssAfter1); + + // WHEN delta is below threshold (500). + mProcessDependencies.setRss(rssBefore2); + mProcessDependencies.setRssAfterCompaction(rssAfter2); + // This is to avoid throttle of compacting too soon. + processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000; + // WHEN we try to run compaction. + mCachedAppOptimizerUnderTest.compactAppFull(processRecord); + waitForHandler(); + // THEN process IS NOT compacted - values after compaction for process 1 should remain the + // same as from the last compaction. + assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); + valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( + pid).getRssAfterCompaction(); + assertThat(valuesAfter).isEqualTo(rssAfter1); + + // WHEN delta is above threshold (13000). + mProcessDependencies.setRss(rssBefore3); + mProcessDependencies.setRssAfterCompaction(rssAfter3); + // This is to avoid throttle of compacting too soon. + processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000; + // WHEN we try to run compaction + mCachedAppOptimizerUnderTest.compactAppFull(processRecord); + waitForHandler(); + // THEN process IS compacted - values after compaction for process 1 should be updated. + assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); + valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( + pid).getRssAfterCompaction(); + assertThat(valuesAfter).isEqualTo(rssAfter3); + + } + + @Test + public void processWithAnonRSSTooSmall_notFullCompacted() throws Exception { + // Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS + // throttle to 8000. + mCachedAppOptimizerUnderTest.init(); + setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true); + setFlag(CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "8000", false); + initActivityManagerService(); + + // Simulate RSS anon memory larger than throttle. + long[] rssBelowThreshold = + new long[]{/*Total RSS*/ 10000, /*File RSS*/ 10000, /*Anon RSS*/ 7000, /*Swap*/ + 10000}; + long[] rssBelowThresholdAfter = + new long[]{/*Total RSS*/ 9000, /*File RSS*/ 7000, /*Anon RSS*/ 4000, /*Swap*/ + 8000}; + long[] rssAboveThreshold = + new long[]{/*Total RSS*/ 10000, /*File RSS*/ 10000, /*Anon RSS*/ 9000, /*Swap*/ + 10000}; + long[] rssAboveThresholdAfter = + new long[]{/*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/5000}; + // Process that passes properties. + int pid = 1; + ProcessRecord processRecord = + makeProcessRecord(pid, 2, 3, "p1", + "app1"); + + // GIVEN we simulate RSS memory before below threshold. + mProcessDependencies.setRss(rssBelowThreshold); + mProcessDependencies.setRssAfterCompaction(rssBelowThresholdAfter); + // WHEN we try to run compaction + mCachedAppOptimizerUnderTest.compactAppFull(processRecord); + waitForHandler(); + // THEN process IS NOT compacted. + assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull(); + + // GIVEN we simulate RSS memory before above threshold. + mProcessDependencies.setRss(rssAboveThreshold); + mProcessDependencies.setRssAfterCompaction(rssAboveThresholdAfter); + // WHEN we try to run compaction + mCachedAppOptimizerUnderTest.compactAppFull(processRecord); + waitForHandler(); + // THEN process IS compacted. + assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); + long[] valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( + pid).getRssAfterCompaction(); + assertThat(valuesAfter).isEqualTo(rssAboveThresholdAfter); + } + + + private void setFlag(String key, String value, boolean defaultValue) throws Exception { + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, key, value, defaultValue); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + } + + private void waitForHandler() { + Idle idle = new Idle(); + mCachedAppOptimizerUnderTest.mCompactionHandler.getLooper().getQueue().addIdleHandler(idle); + mCachedAppOptimizerUnderTest.mCompactionHandler.post(() -> { }); + idle.waitForIdle(); + } + + private void initActivityManagerService() { + mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread()); + mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); + mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); + mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal()); + mAms.mPackageManagerInt = mPackageManagerInt; + } + + private static final class Idle implements MessageQueue.IdleHandler { + private boolean mIdle; + + @Override + public boolean queueIdle() { + synchronized (this) { + mIdle = true; + notifyAll(); + } + return false; + } + + public synchronized void waitForIdle() { + while (!mIdle) { + try { + // Wait with a timeout of 10s. + wait(10000); + } catch (InterruptedException e) { + } + } + } + } + private class TestInjector extends Injector { TestInjector(Context context) { @@ -806,4 +1012,29 @@ public final class CachedAppOptimizerTest { return mHandler; } } + + // Test implementation for ProcessDependencies. + private static final class TestProcessDependencies + implements CachedAppOptimizer.ProcessDependencies { + private long[] mRss; + private long[] mRssAfterCompaction; + + @Override + public long[] getRss(int pid) { + return mRss; + } + + @Override + public void performCompaction(String action, int pid) throws IOException { + mRss = mRssAfterCompaction; + } + + public void setRss(long[] newValues) { + mRss = newValues; + } + + public void setRssAfterCompaction(long[] newValues) { + mRssAfterCompaction = newValues; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index efa25bd7721b..320dacff4888 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -1582,6 +1582,41 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { "s2"); } + public void testCachedShortcuts_canPassShortcutLimit() { + // Change the max number of shortcuts. + mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=4"); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeLongLivedShortcut("s1"), + makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"), + makeLongLivedShortcut("s4")))); + }); + + // Cache All + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"), + HANDLE_USER_0); + }); + + setCaller(CALLING_PACKAGE_1); + + // Get dynamic shortcuts + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), + "s1", "s2", "s3", "s4"); + // Get cached shortcuts + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), + "s1", "s2", "s3", "s4"); + + assertTrue(mManager.setDynamicShortcuts(makeShortcuts("sx1", "sx2", "sx3", "sx4"))); + + // Get dynamic shortcuts + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), + "sx1", "sx2", "sx3", "sx4"); + // Get cached shortcuts + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), + "s1", "s2", "s3", "s4"); + } + // === Test for launcher side APIs === public void testGetShortcuts() { diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index e4102205ddbb..2d45f9ea40c7 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -387,7 +387,7 @@ public class AppStandbyControllerTests { @Test public void testBoundWidgetPackageExempt() throws Exception { assumeTrue(mInjector.getContext().getSystemService(AppWidgetManager.class) != null); - assertEquals(STANDBY_BUCKET_EXEMPTED, + assertEquals(STANDBY_BUCKET_ACTIVE, mController.getAppStandbyBucket(PACKAGE_EXEMPTED_1, USER_ID, mInjector.mElapsedRealtime, false)); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index f4e5d569512a..078c21e04512 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -81,6 +81,7 @@ import android.testing.TestableContentResolver; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; +import android.util.StatsEvent; import android.util.Xml; import androidx.test.InstrumentationRegistry; @@ -89,6 +90,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.FastXmlSerializer; import com.android.server.UiServiceTestCase; + import org.json.JSONArray; import org.json.JSONObject; import org.junit.Before; @@ -2996,6 +2998,31 @@ public class PreferencesHelperTest extends UiServiceTestCase { PKG_O, UID_O, parent.getId(), conversationId, false, false), conversationId); } + + @Test + public void testPullConversationNotificationChannel() { + String conversationId = "friend"; + + NotificationChannel parent = + new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT); + mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); + + String channelId = String.format( + CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId); + NotificationChannel friend = new NotificationChannel(channelId, + "messages", IMPORTANCE_DEFAULT); + friend.setConversationId(parent.getId(), conversationId); + mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false); + ArrayList<StatsEvent> events = new ArrayList<>(); + mHelper.pullPackageChannelPreferencesStats(events); + boolean found = false; + for (StatsEvent event : events) { + // TODO(b/153195691): inspect the content once it is possible to do so + found = true; + } + assertTrue("conversation was not in the pull", found); + } + @Test public void testGetNotificationChannel_conversationProvidedByNotCustomizedYet() { String conversationId = "friend"; diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java index 6a1f50d7e58a..f4b50dc6b553 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; import android.os.RemoteException; @@ -91,5 +92,11 @@ public class DisplayAreaOrganizerTest extends WindowTestsBase { mDisplayContent.setBounds(new Rect(0, 0, 1000, 1000)); verify(organizer).onDisplayAreaInfoChanged(any()); + + Configuration tmpConfiguration = new Configuration(); + tmpConfiguration.setTo(mDisplayContent.getRequestedOverrideConfiguration()); + mDisplayContent.onRequestedOverrideConfigurationChanged(tmpConfiguration); + // Ensure it was still only called once if the bounds didn't change + verify(organizer).onDisplayAreaInfoChanged(any()); } } 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 7b23bfb48a1a..ac95a817bec9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -57,7 +57,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.same; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; @@ -1060,8 +1059,6 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testApplyTopFixedRotationTransform() { mWm.mIsFixedRotationTransformEnabled = true; - mDisplayContent.getDisplayPolicy().addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs); - mDisplayContent.getDisplayPolicy().addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs); final Configuration config90 = new Configuration(); mDisplayContent.computeScreenConfiguration(config90, ROTATION_90); @@ -1082,12 +1079,6 @@ public class DisplayContentTests extends WindowTestsBase { ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */, false /* forceUpdate */)); - assertNotNull(mDisplayContent.mFixedRotationAnimationController); - assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS, - ANIMATION_TYPE_FIXED_TRANSFORM)); - assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS, - ANIMATION_TYPE_FIXED_TRANSFORM)); - final Rect outFrame = new Rect(); final Rect outInsets = new Rect(); final Rect outStableInsets = new Rect(); @@ -1140,9 +1131,6 @@ public class DisplayContentTests extends WindowTestsBase { assertFalse(app.hasFixedRotationTransform()); assertFalse(app2.hasFixedRotationTransform()); assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation); - - mDisplayContent.finishFixedRotationAnimation(); - assertNull(mDisplayContent.mFixedRotationAnimationController); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java index 88de34dd36b0..bdcae481b378 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java @@ -24,6 +24,7 @@ import static android.graphics.GraphicBuffer.USAGE_SW_READ_RARELY; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -39,10 +40,15 @@ import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.os.UserManager; +import android.os.UserManagerInternal; import android.view.Surface; +import com.android.server.LocalServices; + import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import java.io.File; import java.util.function.Predicate; @@ -70,11 +76,26 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase { mLowResScale = lowResScale; } + @BeforeClass + public static void setUpOnce() { + final UserManagerInternal userManager = mock(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, userManager); + } + + @AfterClass + public static void tearDownOnce() { + LocalServices.removeServiceForTest(UserManagerInternal.class); + } + @Before public void setUp() { final UserManager um = UserManager.get(getInstrumentation().getTargetContext()); mTestUserId = um.getUserHandle(); + final UserManagerInternal userManagerInternal = + LocalServices.getService(UserManagerInternal.class); + when(userManagerInternal.isUserUnlocked(mTestUserId)).thenReturn(true); + mContextSpy = spy(new ContextWrapper(mWm.mContext)); mResourcesSpy = spy(mContextSpy.getResources()); when(mContextSpy.getResources()).thenReturn(mResourcesSpy); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 42e2bbf08834..6c13cd799bc2 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -346,12 +346,15 @@ public class SoundTriggerService extends SystemService { sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = " + soundModelId)); - // Unload the model if it is loaded. - mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid()); - mDbHelper.deleteGenericSoundModel(soundModelId.getUuid()); + if (isInitialized()) { + // Unload the model if it is loaded. + mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid()); - // Stop recognition if it is started. - mSoundModelStatTracker.onStop(soundModelId.getUuid()); + // Stop tracking recognition if it is started. + mSoundModelStatTracker.onStop(soundModelId.getUuid()); + } + + mDbHelper.deleteGenericSoundModel(soundModelId.getUuid()); } @Override diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index bb6f154335a9..b35b3236afc6 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -536,13 +536,16 @@ public final class SmsApplication { // Assign permission to special system apps assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, - PHONE_PACKAGE_NAME); + PHONE_PACKAGE_NAME, true); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, - BLUETOOTH_PACKAGE_NAME); + BLUETOOTH_PACKAGE_NAME, true); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, - MMS_SERVICE_PACKAGE_NAME); + MMS_SERVICE_PACKAGE_NAME, true); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, - TELEPHONY_PROVIDER_PACKAGE_NAME); + TELEPHONY_PROVIDER_PACKAGE_NAME, true); + // CellbroadcastReceiver is a mainline module thus skip signature match. + assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, + CellBroadcastUtils.getDefaultCellBroadcastReceiverPackageName(context), false); // Give AppOps permission to UID 1001 which contains multiple // apps, all of them should be able to write to telephony provider. @@ -744,17 +747,23 @@ public final class SmsApplication { * @param packageManager The package manager instance * @param appOps The AppOps manager instance * @param packageName The package name of the system app + * @param sigatureMatch whether to check signature match */ private static void assignExclusiveSmsPermissionsToSystemApp(Context context, - PackageManager packageManager, AppOpsManager appOps, String packageName) { + PackageManager packageManager, AppOpsManager appOps, String packageName, + boolean sigatureMatch) { // First check package signature matches the caller's package signature. // Since this class is only used internally by the system, this check makes sure // the package signature matches system signature. - final int result = packageManager.checkSignatures(context.getPackageName(), packageName); - if (result != PackageManager.SIGNATURE_MATCH) { - Log.e(LOG_TAG, packageName + " does not have system signature"); - return; + if (sigatureMatch) { + final int result = packageManager.checkSignatures(context.getPackageName(), + packageName); + if (result != PackageManager.SIGNATURE_MATCH) { + Log.e(LOG_TAG, packageName + " does not have system signature"); + return; + } } + try { PackageInfo info = packageManager.getPackageInfo(packageName, 0); int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid, diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 35464340550b..d62cd0a63b44 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -837,20 +837,20 @@ public class SubscriptionInfo implements Parcelable { + " carrierId=" + mCarrierId + " displayName=" + mDisplayName + " carrierName=" + mCarrierName + " nameSource=" + mNameSource + " iconTint=" + mIconTint - + " mNumber=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber) - + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc - + " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded - + " nativeAccessRules " + Arrays.toString(mNativeAccessRules) + + " number=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber) + + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc=" + mMcc + + " mnc=" + mMnc + " countryIso=" + mCountryIso + " isEmbedded=" + mIsEmbedded + + " nativeAccessRules=" + Arrays.toString(mNativeAccessRules) + " cardString=" + cardStringToPrint + " cardId=" + mCardId - + " isOpportunistic=" + mIsOpportunistic + " mGroupUUID=" + mGroupUUID - + " mIsGroupDisabled=" + mIsGroupDisabled + + " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID + + " isGroupDisabled=" + mIsGroupDisabled + " profileClass=" + mProfileClass + " ehplmns=" + Arrays.toString(mEhplmns) + " hplmns=" + Arrays.toString(mHplmns) + " subscriptionType=" + mSubscriptionType - + " mGroupOwner=" + mGroupOwner + + " groupOwner=" + mGroupOwner + " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules) - + " mAreUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled + "}"; + + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled + "}"; } @Override diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java index ede67dd9fd61..94407f1dcd3a 100644 --- a/telephony/java/android/telephony/ims/ImsRcsManager.java +++ b/telephony/java/android/telephony/ims/ImsRcsManager.java @@ -56,14 +56,15 @@ public class ImsRcsManager { * Activity Action: Show the opt-in dialog for enabling or disabling RCS contact discovery * using User Capability Exchange (UCE). * <p> - * An application that depends on contact discovery being enabled may send this intent + * An application that depends on RCS contact discovery being enabled must send this intent * using {@link Context#startActivity(Intent)} to ask the user to opt-in for contacts upload for - * capability exchange if it is currently disabled. Whether or not this setting has been enabled - * can be queried using {@link RcsUceAdapter#isUceSettingEnabled()}. + * capability exchange if it is currently disabled. Whether or not RCS contact discovery has + * been enabled by the user can be queried using {@link RcsUceAdapter#isUceSettingEnabled()}. * <p> - * This intent should only be sent if the carrier supports RCS capability exchange, which can be - * queried using the key {@link CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL}. Otherwise, the - * setting will not be present. + * This intent will always be handled by the system, however the application should only send + * this Intent if the carrier supports RCS contact discovery, which can be queried using the key + * {@link CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL}. Otherwise, the RCS contact discovery + * opt-in dialog will not be shown. * <p> * Input: A mandatory {@link Settings#EXTRA_SUB_ID} extra containing the subscription that the * setting will be be shown for. diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java index 05ab6bd75878..ec112790a144 100644 --- a/telephony/java/android/telephony/ims/RcsUceAdapter.java +++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java @@ -363,9 +363,10 @@ public class RcsUceAdapter { /** * Change the user’s setting for whether or not UCE is enabled for the associated subscription. * <p> - * If an application Requires UCE, they may launch an Activity using the Intent + * If an application Requires UCE, they will launch an Activity using the Intent * {@link ImsRcsManager#ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN}, which will ask the user if - * they wish to enable this feature. + * they wish to enable this feature. This setting should only be enabled after the user has + * opted-in to capability exchange. * <p> * Note: This setting does not affect whether or not the device publishes its service * capabilities if the subscription supports presence publication. |