Merge "Change AtomicFile to use rename-into-place." into rvc-dev
diff --git a/apex/Android.bp b/apex/Android.bp
index e8afa1d..f511af5 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -92,6 +92,9 @@
sdk_version: "module_current",
},
+ // Configure framework module specific metalava options.
+ droiddoc_options: [mainline_stubs_args],
+
// The stub libraries must be visible to frameworks/base so they can be combined
// into API specific libraries.
stubs_library_visibility: [
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 2834ab1..94e5d0b 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 @@
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/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f00a35d..8ba52ef 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -440,6 +440,7 @@
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 @@
}
// 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 @@
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 @@
}
/**
+ * 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 @@
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 @@
}
/**
+ * 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 @@
}
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 21ffff3..d865c21 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -122,11 +122,11 @@
}
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 f9a8842..26b3d3c 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -53,8 +53,8 @@
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 0de92f3..6633659 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -161,13 +161,12 @@
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 @@
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 6f84076..53f0f28 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -55,8 +55,8 @@
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 91c98ea2..5fabb5f 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -182,8 +182,8 @@
};
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.
@@ -442,7 +442,7 @@
bool mIsActive;
// The slice_by_state atom ids defined in statsd_config.
- std::vector<int32_t> mSlicedStateAtoms;
+ const std::vector<int32_t> mSlicedStateAtoms;
// Maps atom ids and state values to group_ids (<atom_id, <value, group_id>>).
const std::unordered_map<int32_t, std::unordered_map<int, int64_t>> mStateGroupMap;
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index d7ad27b..e8c575a 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -71,6 +71,8 @@
mLastReportTimeNs(currentTimeNs),
mLastReportWallClockNs(getWallClockNs()),
mPullerManager(pullerManager),
+ mWhitelistedAtomIds(config.whitelisted_atom_ids().begin(),
+ config.whitelisted_atom_ids().end()),
mShouldPersistHistory(config.persist_locally()) {
// Init the ttl end timestamp.
refreshTtl(timeBaseNs);
@@ -366,11 +368,16 @@
bool MetricsManager::checkLogCredentials(const LogEvent& event) {
+ // TODO(b/154856835): Remove this check once we get whitelist from the config.
if (android::util::AtomsInfo::kWhitelistedAtoms.find(event.GetTagId()) !=
android::util::AtomsInfo::kWhitelistedAtoms.end())
{
return true;
}
+
+ if (mWhitelistedAtomIds.find(event.GetTagId()) != mWhitelistedAtomIds.end()) {
+ return true;
+ }
std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
if (mAllowedLogSources.find(event.GetUid()) == mAllowedLogSources.end()) {
VLOG("log source %d not on the whitelist", event.GetUid());
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index ef03d20..c30532a 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -189,6 +189,8 @@
// To guard access to mAllowedLogSources
mutable std::mutex mAllowedLogSourcesMutex;
+ const std::set<int32_t> mWhitelistedAtomIds;
+
// We can pull any atom from these uids.
std::set<int32_t> mDefaultPullUids;
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index bf636a4..f03ce45 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -182,15 +182,26 @@
}
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 c8dc8cc..751fef2 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -90,7 +90,7 @@
};
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/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 3ab44f4..210d382 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -791,10 +791,19 @@
}
noReportMetricIds.insert(no_report_metric);
}
+
+ const set<int> whitelistedAtomIds(config.whitelisted_atom_ids().begin(),
+ config.whitelisted_atom_ids().end());
for (const auto& it : allMetricProducers) {
// Register metrics to StateTrackers
for (int atomId : it->getSlicedStateAtoms()) {
- StateManager::getInstance().registerListener(atomId, it);
+ // Register listener for non-whitelisted atoms only. Using whitelisted atom as a sliced
+ // state atom is not allowed.
+ if (whitelistedAtomIds.find(atomId) == whitelistedAtomIds.end()) {
+ StateManager::getInstance().registerListener(atomId, it);
+ } else {
+ return false;
+ }
}
}
return true;
diff --git a/cmds/statsd/src/state/StateListener.h b/cmds/statsd/src/state/StateListener.h
index d1af196..6388001 100644
--- a/cmds/statsd/src/state/StateListener.h
+++ b/cmds/statsd/src/state/StateListener.h
@@ -45,8 +45,8 @@
* [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 b63713b..41e525c 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -35,31 +35,30 @@
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 @@
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 @@
// 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::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 @@
// 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 c5f6315..abd579e 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -72,19 +72,19 @@
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 c7407bd..7c0057d 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -489,6 +489,8 @@
repeated PullAtomPackages pull_atom_packages = 23;
+ repeated int32 whitelisted_atom_ids = 24;
+
// Field number 1000 is reserved for later use.
reserved 1000;
}
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 3890fbe..b3b095b 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -29,6 +29,7 @@
#include "src/metrics/MetricProducer.h"
#include "src/metrics/ValueMetricProducer.h"
#include "src/metrics/metrics_manager_util.h"
+#include "src/state/StateManager.h"
#include "statsd_test_util.h"
using namespace testing;
@@ -591,6 +592,58 @@
EXPECT_TRUE(isSubset(defaultPullUids, set<int32_t>(atom3Uids.begin(), atom3Uids.end())));
}
+TEST(MetricsManagerTest, TestCheckLogCredentialsWhitelistedAtom) {
+ sp<UidMap> uidMap;
+ sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
+
+ StatsdConfig config = buildGoodConfig();
+ config.add_whitelisted_atom_ids(3);
+ config.add_whitelisted_atom_ids(4);
+
+ MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+ pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+ LogEvent event(0 /* uid */, 0 /* pid */);
+ CreateNoValuesLogEvent(&event, 10 /* atom id */, 0 /* timestamp */);
+ EXPECT_FALSE(metricsManager.checkLogCredentials(event));
+
+ CreateNoValuesLogEvent(&event, 3 /* atom id */, 0 /* timestamp */);
+ EXPECT_TRUE(metricsManager.checkLogCredentials(event));
+
+ CreateNoValuesLogEvent(&event, 4 /* atom id */, 0 /* timestamp */);
+ EXPECT_TRUE(metricsManager.checkLogCredentials(event));
+}
+
+TEST(MetricsManagerTest, TestWhitelistedAtomStateTracker) {
+ sp<UidMap> uidMap;
+ sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
+
+ StatsdConfig config = buildGoodConfig();
+ config.add_allowed_log_source("AID_SYSTEM");
+ config.add_whitelisted_atom_ids(3);
+ config.add_whitelisted_atom_ids(4);
+
+ State state;
+ state.set_id(1);
+ state.set_atom_id(3);
+
+ *config.add_state() = state;
+
+ config.mutable_count_metric(0)->add_slice_by_state(state.id());
+
+ StateManager::getInstance().clear();
+
+ MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+ pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+ EXPECT_EQ(0, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_FALSE(metricsManager.isConfigValid());
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index b6e1075..474aa22 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -3898,14 +3898,12 @@
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 @@
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(true, it->second[0].hasValue);
- EXPECT_EQ(4, it->second[0].value.long_value);
+ 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);
+
+ // 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}
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 13e8f5c..530ac5e 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -50,8 +50,9 @@
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/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b0ce7d1..97b704c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -601,6 +601,20 @@
@TestApi
public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2;
+ // TODO: remove this when development is done.
+ // These are debug flags used between OomAdjuster and AppOpsService to detect and report absence
+ // of the real flags.
+ /** @hide */
+ public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q = 1 << 27;
+ /** @hide */
+ public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q = 1 << 28;
+ /** @hide */
+ public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 29;
+ /** @hide */
+ public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 30;
+ /** @hide */
+ public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
+
/** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/
@TestApi
public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION
@@ -623,6 +637,51 @@
public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = PROCESS_CAPABILITY_FOREGROUND_CAMERA
| PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+ /**
+ * Print capability bits in human-readable form.
+ * @hide
+ */
+ public static void printCapabilitiesSummary(PrintWriter pw, @ProcessCapability int caps) {
+ pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
+ pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
+ pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-');
+ }
+
+ /**
+ * Print capability bits in human-readable form.
+ * @hide
+ */
+ public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) {
+ printCapabilitiesSummary(pw, caps);
+ if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) {
+ pw.print(" !L");
+ }
+ if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
+ pw.print(" !C");
+ }
+ if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q) != 0) {
+ pw.print(" !Cq");
+ }
+ if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
+ pw.print(" !M");
+ }
+ if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q) != 0) {
+ pw.print(" !Mq");
+ }
+ final int remain = caps & ~(PROCESS_CAPABILITY_FOREGROUND_LOCATION
+ | PROCESS_CAPABILITY_FOREGROUND_CAMERA
+ | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
+ | DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION
+ | DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA
+ | DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q
+ | DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
+ | DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q);
+ if (remain != 0) {
+ pw.print('+');
+ pw.print(remain);
+ }
+ }
+
// NOTE: If PROCESS_STATEs are added, then new fields must be added
// to frameworks/base/core/proto/android/app/enums.proto and the following method must
// be updated to correctly map between them.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index eea1d69..8e43ca3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1538,6 +1538,12 @@
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 @@
}
}
+ /**
+ * 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 @@
}
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 @@
}
/**
- * 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 @@
&& 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 @@
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 3ce7689..be1681b 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -229,7 +229,16 @@
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 1f6e4ca..6e9157e 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -119,6 +119,7 @@
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/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 8dfce14..4c3e888 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -58,7 +58,9 @@
void setShowBadge(String pkg, int uid, boolean showBadge);
boolean canShowBadge(String pkg, int uid);
- boolean hasSentMessage(String pkg, int uid);
+ boolean isInInvalidMsgState(String pkg, int uid);
+ boolean hasUserDemotedInvalidMsgApp(String pkg, int uid);
+ void setInvalidMsgAppDemoted(String pkg, int uid, boolean isDemoted);
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
/**
* Updates the notification's enabled state. Additionally locks importance for all of the
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 3110e18..01cf2b94a 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -26,12 +26,20 @@
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 @@
@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 @@
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 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 @@
return size() > maxEntries;
}
};
+ synchronized (sCorkLock) {
+ sCaches.put(this, null);
+ }
}
/**
@@ -248,6 +273,9 @@
*/
public final void clear() {
synchronized (mLock) {
+ if (DEBUG) {
+ Log.d(TAG, "clearing cache for " + mPropertyName);
+ }
mCache.clear();
}
}
@@ -710,4 +738,87 @@
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 0d4e16b..8b52242 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -19,6 +19,7 @@
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 @@
@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 @@
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 @@
@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 f6d3dbd..9a457a3 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -18,6 +18,7 @@
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 @@
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 @@
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 @@
@Override
public void recycle() {
mTargetDisplayId = 0;
- mConfiguration = null;
+ mConfiguration = Configuration.EMPTY;
ObjectPool.recycle(this);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1e9cddbb..2f488cd 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -51,7 +51,9 @@
import android.content.pm.parsing.PackageInfoWithoutStateUtils;
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Rect;
@@ -383,6 +385,9 @@
* <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;
@@ -6061,7 +6066,8 @@
boolean collectCertificates = (flags & PackageManager.GET_SIGNATURES) != 0
|| (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0;
- ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefaultOneTime(
+ ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset();
+ ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input,
new File(archiveFilePath), 0, collectCertificates);
if (result.isError()) {
return null;
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index cb29431..5a79475 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -131,14 +131,23 @@
public static final String TAG = ParsingUtils.TAG;
/**
- * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off
- * request, without caching the input object and without querying the internal system state
- * for feature support.
+ * @see #parseDefault(ParseInput, File, int, boolean)
*/
@NonNull
public static ParseResult<ParsingPackage> parseDefaultOneTime(File file,
@PackageParser.ParseFlags int parseFlags, boolean collectCertificates) {
ParseInput input = ParseTypeImpl.forDefaultParsing().reset();
+ return parseDefault(input, file, parseFlags, collectCertificates);
+ }
+
+ /**
+ * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off
+ * request, without caching the input object and without querying the internal system state
+ * for feature support.
+ */
+ @NonNull
+ public static ParseResult<ParsingPackage> parseDefault(ParseInput input, File file,
+ @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) {
ParseResult<ParsingPackage> result;
ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, new Callback() {
diff --git a/core/java/android/content/pm/parsing/result/ParseInput.java b/core/java/android/content/pm/parsing/result/ParseInput.java
index d5898b7..0fb18ae 100644
--- a/core/java/android/content/pm/parsing/result/ParseInput.java
+++ b/core/java/android/content/pm/parsing/result/ParseInput.java
@@ -16,6 +16,7 @@
package android.content.pm.parsing.result;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
@@ -69,6 +70,25 @@
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
public static final long RESOURCES_ARSC_COMPRESSED = 132742131;
+
+ /**
+ * TODO(chiuwinson): This is required because PackageManager#getPackageArchiveInfo
+ * cannot read the targetSdk info from the changeId because it requires the
+ * READ_COMPAT_CHANGE_CONFIG which cannot be obtained automatically without entering the
+ * server process. This should be removed once an alternative is found, or if the API
+ * is removed.
+ * @return the targetSdk that this change is gated on (> check), or -1 if disabled
+ */
+ @IntRange(from = -1, to = Integer.MAX_VALUE)
+ public static int getTargetSdkForChange(long changeId) {
+ if (changeId == MISSING_APP_TAG
+ || changeId == EMPTY_INTENT_ACTION_CATEGORY
+ || changeId == RESOURCES_ARSC_COMPRESSED) {
+ return Build.VERSION_CODES.Q;
+ }
+
+ return -1;
+ }
}
<ResultType> ParseResult<ResultType> success(ResultType result);
diff --git a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
index 91e571b..14992fb 100644
--- a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
+++ b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
@@ -65,6 +65,21 @@
private Integer mTargetSdkVersion;
/**
+ * Specifically for {@link PackageManager#getPackageArchiveInfo(String, int)} where
+ * {@link IPlatformCompat} cannot be used because the cross-package READ_COMPAT_CHANGE_CONFIG
+ * permission cannot be obtained.
+ */
+ public static ParseTypeImpl forParsingWithoutPlatformCompat() {
+ return new ParseTypeImpl((changeId, packageName, targetSdkVersion) -> {
+ int gateSdkVersion = DeferredError.getTargetSdkForChange(changeId);
+ if (gateSdkVersion == -1) {
+ return false;
+ }
+ return targetSdkVersion > gateSdkVersion;
+ });
+ }
+
+ /**
* Assumes {@link Context#PLATFORM_COMPAT_SERVICE} is available to the caller. For use
* with {@link android.content.pm.parsing.ApkLiteParseUtils} or similar where parsing is
* done outside of {@link com.android.server.pm.PackageManagerService}.
diff --git a/core/java/android/debug/AdbNotifications.java b/core/java/android/debug/AdbNotifications.java
index fed5f5f..9f1a5f8 100644
--- a/core/java/android/debug/AdbNotifications.java
+++ b/core/java/android/debug/AdbNotifications.java
@@ -17,11 +17,13 @@
package android.debug;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.UserHandle;
import android.provider.Settings;
@@ -42,8 +44,9 @@
* Builds a notification to show connected state for adb over a transport type.
* @param context the context
* @param transportType the adb transport type.
- * @return a newly created Notification for the transport type.
+ * @return a newly created Notification for the transport type, or null on error.
*/
+ @Nullable
public static Notification createNotification(@NonNull Context context,
byte transportType) {
Resources resources = context.getResources();
@@ -66,10 +69,16 @@
Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- intent.setPackage(context.getPackageManager().resolveActivity(intent,
- PackageManager.MATCH_SYSTEM_ONLY).activityInfo.packageName);
- PendingIntent pIntent = PendingIntent.getActivityAsUser(context, 0, intent,
- PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
+ ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
+ PackageManager.MATCH_SYSTEM_ONLY);
+ // Settings app may not be available (e.g. device policy manager removes it)
+ PendingIntent pIntent = null;
+ if (resolveInfo != null) {
+ intent.setPackage(resolveInfo.activityInfo.packageName);
+ pIntent = PendingIntent.getActivityAsUser(context, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
+ }
+
return new Notification.Builder(context, SystemNotificationChannels.DEVELOPER_IMPORTANT)
.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 4bed985..f9ed2f8 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -314,6 +314,92 @@
}
@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/os/UserManager.java b/core/java/android/os/UserManager.java
index 25bf430..4832e56 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1729,9 +1729,15 @@
* <p>As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this method can
* now automatically identify goats using advanced goat recognition technology.</p>
*
- * @return Returns true if the user making this call is a goat.
+ * <p>As of {@link android.os.Build.VERSION_CODES#R}, this method always returns
+ * {@code false} in order to protect goat privacy.</p>
+ *
+ * @return Returns whether the user making this call is a goat.
*/
public boolean isUserAGoat() {
+ if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R) {
+ return false;
+ }
return mContext.getPackageManager()
.isPackageAvailable("com.coffeestainstudios.goatsimulator");
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index a1a11ed..16e5156 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -226,9 +226,10 @@
* <p>
* This intent should be launched using
* {@link Activity#startActivityForResult(Intent, int)} so that the user
- * knows which app is requesting to clear cache. The returned result will
- * be {@link Activity#RESULT_OK} if the activity was launched and the user accepted to clear
- * cache, or {@link Activity#RESULT_CANCELED} otherwise.
+ * knows which app is requesting to clear cache. The returned result will be:
+ * {@link Activity#RESULT_OK} if the activity was launched and all cache was cleared,
+ * {@link OsConstants#EIO} if an error occurred while clearing the cache or
+ * {@link Activity#RESULT_CANCELED} otherwise.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
@SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index e224e84..91b9390 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1121,6 +1121,7 @@
* @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 @@
* @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/BrightnessSynchronizer.java b/core/java/com/android/internal/BrightnessSynchronizer.java
index aa23251..42724be 100644
--- a/core/java/com/android/internal/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/BrightnessSynchronizer.java
@@ -84,17 +84,17 @@
* Converts between the int brightness system and the float brightness system.
*/
public static float brightnessIntToFloat(Context context, int brightnessInt) {
- PowerManager pm = context.getSystemService(PowerManager.class);
- float pmMinBrightness = pm.getBrightnessConstraint(
+ final PowerManager pm = context.getSystemService(PowerManager.class);
+ final float pmMinBrightness = pm.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
- float pmMaxBrightness = pm.getBrightnessConstraint(
+ final float pmMaxBrightness = pm.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
- int minBrightnessInt = brightnessFloatToInt(pmMinBrightness, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
- PowerManager.BRIGHTNESS_ON);
- int maxBrightnessInt = brightnessFloatToInt(pmMaxBrightness, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
- PowerManager.BRIGHTNESS_ON);
+ final int minBrightnessInt = Math.round(brightnessFloatToIntRange(pmMinBrightness,
+ PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+ PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON));
+ final int maxBrightnessInt = Math.round(brightnessFloatToIntRange(pmMaxBrightness,
+ PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+ PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON));
return brightnessIntToFloat(brightnessInt, minBrightnessInt, maxBrightnessInt,
pmMinBrightness, pmMaxBrightness);
@@ -119,34 +119,43 @@
* Converts between the float brightness system and the int brightness system.
*/
public static int brightnessFloatToInt(Context context, float brightnessFloat) {
- PowerManager pm = context.getSystemService(PowerManager.class);
- float pmMinBrightness = pm.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
- float pmMaxBrightness = pm.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
- int minBrightnessInt = brightnessFloatToInt(pmMinBrightness, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
- PowerManager.BRIGHTNESS_ON);
- int maxBrightnessInt = brightnessFloatToInt(pmMaxBrightness, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
- PowerManager.BRIGHTNESS_ON);
-
- return brightnessFloatToInt(brightnessFloat, pmMinBrightness, pmMaxBrightness,
- minBrightnessInt, maxBrightnessInt);
+ return Math.round(brightnessFloatToIntRange(context, brightnessFloat));
}
/**
- * Converts between the float brightness system and the int brightness system.
+ * Converts between the float brightness system and the int brightness system, but returns
+ * the converted value as a float within the int-system's range. This method helps with
+ * conversions from one system to the other without losing the floating-point precision.
*/
- public static int brightnessFloatToInt(float brightnessFloat, float minFloat, float maxFloat,
- int minInt, int maxInt) {
+ public static float brightnessFloatToIntRange(Context context, float brightnessFloat) {
+ final PowerManager pm = context.getSystemService(PowerManager.class);
+ final float minFloat = pm.getBrightnessConstraint(
+ PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+ final float maxFloat = pm.getBrightnessConstraint(
+ PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+ final float minInt = brightnessFloatToIntRange(minFloat,
+ PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+ PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON);
+ final float maxInt = brightnessFloatToIntRange(maxFloat,
+ PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+ PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON);
+ return brightnessFloatToIntRange(brightnessFloat, minFloat, maxFloat, minInt, maxInt);
+ }
+
+ /**
+ * Translates specified value from the float brightness system to the int brightness system,
+ * given the min/max of each range. Accounts for special values such as OFF and invalid values.
+ * Value returned as a float privimite (to preserve precision), but is a value within the
+ * int-system range.
+ */
+ private static float brightnessFloatToIntRange(float brightnessFloat, float minFloat,
+ float maxFloat, float minInt, float maxInt) {
if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
return PowerManager.BRIGHTNESS_OFF;
} else if (Float.isNaN(brightnessFloat)) {
return PowerManager.BRIGHTNESS_INVALID;
} else {
- return Math.round(MathUtils.constrainedMap((float) minInt, (float) maxInt, minFloat,
- maxFloat, brightnessFloat));
+ return MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat, brightnessFloat);
}
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index cff669e..9950e55 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -3506,13 +3506,11 @@
}
/**
- * Only expand direct share area if there is a minimum number of shortcuts,
- * which will help reduce the amount of visible shuffling due to older-style
- * direct share targets.
+ * Only expand direct share area if there is a minimum number of targets.
*/
private boolean canExpandDirectShare() {
int orientation = getResources().getConfiguration().orientation;
- return mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
+ return mChooserListAdapter.getNumServiceTargetsForExpand() > getMaxTargetsPerRow()
&& orientation == Configuration.ORIENTATION_PORTRAIT
&& !isInMultiWindowMode();
}
@@ -3721,8 +3719,12 @@
// only expand if we have more than maxTargetsPerRow, and delay that decision
// until they start to scroll
- if (mChooserMultiProfilePagerAdapter.getActiveListAdapter()
- .getSelectableServiceTargetCount() <= maxTargetsPerRow) {
+ ChooserListAdapter adapter =
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter();
+ int validTargets =
+ mAppendDirectShareEnabled ? adapter.getNumServiceTargetsForExpand()
+ : adapter.getSelectableServiceTargetCount();
+ if (validTargets <= maxTargetsPerRow) {
mHideDirectShareExpansion = true;
return;
}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 14a2d37..f1b7161 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -30,6 +30,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.UserHandle;
import android.os.UserManager;
@@ -78,6 +79,7 @@
private static final int MAX_SUGGESTED_APP_TARGETS = 4;
private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
+ private static final int MAX_SERVICE_TARGET_APP = 8;
static final int MAX_SERVICE_TARGETS = 8;
@@ -97,13 +99,14 @@
private ChooserTargetInfo
mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo();
private int mValidServiceTargetsNum = 0;
+ private int mAvailableServiceTargetsNum = 0;
private final Map<ComponentName, Pair<List<ChooserTargetInfo>, Integer>>
mParkingDirectShareTargets = new HashMap<>();
private final Map<ComponentName, Map<String, Integer>> mChooserTargetScores = new HashMap<>();
private Set<ComponentName> mPendingChooserTargetService = new HashSet<>();
private Set<ComponentName> mShortcutComponents = new HashSet<>();
private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
- private final List<TargetInfo> mCallerTargets = new ArrayList<>();
+ private final List<DisplayResolveInfo> mCallerTargets = new ArrayList<>();
private final ChooserActivity.BaseChooserTargetComparator mBaseTargetComparator =
new ChooserActivity.BaseChooserTargetComparator();
@@ -235,8 +238,9 @@
}
@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();
@@ -253,14 +257,32 @@
holder.text.setBackground(null);
holder.itemView.setBackground(holder.defaultItemViewBackground);
}
+
+ 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);
+ }
}
void updateAlphabeticalList() {
mSortedList.clear();
+ List<DisplayResolveInfo> tempList = new ArrayList<>();
+ tempList.addAll(mDisplayList);
+ tempList.addAll(mCallerTargets);
if (mEnableStackedApps) {
// Consolidate multiple targets from same app.
Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
- for (DisplayResolveInfo info : mDisplayList) {
+ for (DisplayResolveInfo info : tempList) {
String packageName = info.getResolvedComponentName().getPackageName();
DisplayResolveInfo multiDri = consolidated.get(packageName);
if (multiDri == null) {
@@ -277,7 +299,7 @@
}
mSortedList.addAll(consolidated.values());
} else {
- mSortedList.addAll(mDisplayList);
+ mSortedList.addAll(tempList);
}
Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext));
}
@@ -329,7 +351,10 @@
return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0;
}
- int getRankedTargetCount() {
+ /**
+ * Fetch ranked app target count
+ */
+ public int getRankedTargetCount() {
int spacesAvailable =
mChooserListCommunicator.getMaxRankedTargets() - getCallerTargetCount();
return Math.min(spacesAvailable, super.getCount());
@@ -414,6 +439,19 @@
return null;
}
+ // Check whether {@code dri} should be added into mDisplayList.
+ @Override
+ protected boolean shouldAddResolveInfo(DisplayResolveInfo dri) {
+ // Checks if this info is already listed in callerTargets.
+ for (TargetInfo existingInfo : mCallerTargets) {
+ if (mResolverListCommunicator
+ .resolveInfoMatch(dri.getResolveInfo(), existingInfo.getResolveInfo())) {
+ return false;
+ }
+ }
+ return super.shouldAddResolveInfo(dri);
+ }
+
/**
* Fetch surfaced direct share target info
*/
@@ -573,7 +611,13 @@
Pair<List<ChooserTargetInfo>, Integer> parkingTargetInfoPair =
mParkingDirectShareTargets.getOrDefault(origComponentName,
new Pair<>(new ArrayList<>(), 0));
- parkingTargetInfoPair.first.addAll(parkingTargetInfos);
+ for (ChooserTargetInfo target : parkingTargetInfos) {
+ if (!checkDuplicateTarget(target, parkingTargetInfoPair.first)
+ && !checkDuplicateTarget(target, mServiceTargets)) {
+ parkingTargetInfoPair.first.add(target);
+ mAvailableServiceTargetsNum++;
+ }
+ }
mParkingDirectShareTargets.put(origComponentName, parkingTargetInfoPair);
rankTargetsWithinComponent(origComponentName);
if (isShortcutResult) {
@@ -618,7 +662,7 @@
List<ChooserTargetInfo> parkingTargets = parkingTargetsItem.first;
int insertedNum = parkingTargetsItem.second;
while (insertedNum < quota && !parkingTargets.isEmpty()) {
- if (!checkDuplicateTarget(parkingTargets.get(0))) {
+ if (!checkDuplicateTarget(parkingTargets.get(0), mServiceTargets)) {
mServiceTargets.add(mValidServiceTargetsNum, parkingTargets.get(0));
mValidServiceTargetsNum++;
insertedNum++;
@@ -633,9 +677,6 @@
+ " totalScore=" + totalScore
+ " quota=" + quota);
}
- if (mShortcutComponents.contains(component)) {
- mNumShortcutResults += insertedNum - parkingTargetsItem.second;
- }
mParkingDirectShareTargets.put(component, new Pair<>(parkingTargets, insertedNum));
}
if (!shouldWaitPendingService) {
@@ -651,19 +692,15 @@
return;
}
Log.i(TAG, " fillAllServiceTargets");
- int maxRankedTargets = mChooserListCommunicator.getMaxRankedTargets();
- List<ComponentName> topComponentNames = getTopComponentNames(maxRankedTargets);
+ List<ComponentName> topComponentNames = getTopComponentNames(MAX_SERVICE_TARGET_APP);
// Append all remaining targets of top recommended components into direct share row.
for (ComponentName component : topComponentNames) {
if (!mParkingDirectShareTargets.containsKey(component)) {
continue;
}
mParkingDirectShareTargets.get(component).first.stream()
- .filter(target -> !checkDuplicateTarget(target))
+ .filter(target -> !checkDuplicateTarget(target, mServiceTargets))
.forEach(target -> {
- if (mShortcutComponents.contains(component)) {
- mNumShortcutResults++;
- }
mServiceTargets.add(mValidServiceTargetsNum, target);
mValidServiceTargetsNum++;
});
@@ -676,28 +713,34 @@
.map(pair -> pair.first)
.forEach(targets -> {
for (ChooserTargetInfo target : targets) {
- if (!checkDuplicateTarget(target)) {
+ if (!checkDuplicateTarget(target, mServiceTargets)) {
mServiceTargets.add(mValidServiceTargetsNum, target);
mValidServiceTargetsNum++;
- mNumShortcutResults++;
}
}
});
mParkingDirectShareTargets.clear();
}
- private boolean checkDuplicateTarget(ChooserTargetInfo chooserTargetInfo) {
+ private boolean checkDuplicateTarget(ChooserTargetInfo target,
+ List<ChooserTargetInfo> destination) {
// Check for duplicates and abort if found
- for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
- if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
+ for (ChooserTargetInfo otherTargetInfo : destination) {
+ if (target.isSimilar(otherTargetInfo)) {
return true;
}
}
return false;
}
- int getNumShortcutResults() {
- return mNumShortcutResults;
+ /**
+ * The return number have to exceed a minimum limit to make direct share area expandable. When
+ * append direct share targets is enabled, return count of all available targets parking in the
+ * memory; otherwise, it is shortcuts count which will help reduce the amount of visible
+ * shuffling due to older-style direct share targets.
+ */
+ int getNumServiceTargetsForExpand() {
+ return mAppendDirectShareEnabled ? mAvailableServiceTargetsNum : mNumShortcutResults;
}
/**
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 24bf98b..d942e85 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -85,7 +85,7 @@
private int mLastChosenPosition = -1;
private boolean mFilterLastUsed;
- private final ResolverListCommunicator mResolverListCommunicator;
+ final ResolverListCommunicator mResolverListCommunicator;
private Runnable mPostListReadyRunnable;
private final boolean mIsAudioCaptureDevice;
private boolean mIsTabLoaded;
@@ -443,17 +443,24 @@
// TODO(arangelov): Is that UserHandle.USER_CURRENT check okay?
if (dri != null && dri.getResolveInfo() != null
&& dri.getResolveInfo().targetUserId == UserHandle.USER_CURRENT) {
- // Checks if this info is already listed in display.
- for (DisplayResolveInfo existingInfo : mDisplayList) {
- if (mResolverListCommunicator
- .resolveInfoMatch(dri.getResolveInfo(), existingInfo.getResolveInfo())) {
- return;
- }
+ if (shouldAddResolveInfo(dri)) {
+ mDisplayList.add(dri);
}
- mDisplayList.add(dri);
}
}
+ // Check whether {@code dri} should be added into mDisplayList.
+ protected boolean shouldAddResolveInfo(DisplayResolveInfo dri) {
+ // Checks if this info is already listed in display.
+ for (DisplayResolveInfo existingInfo : mDisplayList) {
+ if (mResolverListCommunicator
+ .resolveInfoMatch(dri.getResolveInfo(), existingInfo.getResolveInfo())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@Nullable
public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
TargetInfo target = targetInfoForPosition(position, filtered);
@@ -517,7 +524,7 @@
if (view == null) {
view = createView(parent);
}
- onBindView(view, getItem(position));
+ onBindView(view, getItem(position), position);
return view;
}
@@ -534,10 +541,10 @@
}
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/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 6f33096..d238d0e 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -61,22 +61,6 @@
public static final String SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS =
"screenshot_notification_smart_actions_timeout_ms";
- // Flags related to controls
-
- /**
- * (boolean) Whether to have split behavior when opening QS
- */
- public static final String QS_SPLIT_ENABLED = "qs_split_enabled";
-
- /**
- * (int) Open settings panels for WiFi and BT tiles
- * 0 - default behavior, link to settings
- * 1 - open panel on long press, click remains the same
- * 2 - open panel on click, long press remains the same
- * 3 - use details on long press
- */
- public static final String QS_USE_SETTINGS_PANELS = "qs_use_settings_panels";
-
// Flags related to Smart Suggestions - these are read in SmartReplyConstants.
/** (boolean) Whether to enable smart suggestions in notifications. */
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index a420ba6..7b708ef 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -652,6 +652,7 @@
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 @@
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/res/res/drawable/chooser_group_background.xml b/core/res/res/drawable/chooser_group_background.xml
new file mode 100644
index 0000000..036028d
--- /dev/null
+++ b/core/res/res/drawable/chooser_group_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_group_arrow"
+ android:gravity="end|center_vertical"
+ android:width="12dp"
+ android:height="12dp"
+ android:start="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 0000000..fbbe8c1
--- /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_group_arrow.xml b/core/res/res/drawable/ic_chooser_group_arrow.xml
new file mode 100644
index 0000000..d42bb97
--- /dev/null
+++ b/core/res/res/drawable/ic_chooser_group_arrow.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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="M2,4L6,8L10,4L2,4Z"
+ android:fillColor="#FF000000"/>
+</vector>
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 0000000..47851dcb
--- /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/car_user_switching_dialog.xml b/core/res/res/layout/car_user_switching_dialog.xml
deleted file mode 100644
index d727434..0000000
--- a/core/res/res/layout/car_user_switching_dialog.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:fitsSystemWindows="true"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/user_loading_avatar"
- android:layout_width="@dimen/car_fullscreen_user_pod_image_avatar_width"
- android:layout_height="@dimen/car_fullscreen_user_pod_image_avatar_height"
- android:layout_centerHorizontal="true"/>
-
- <TextView android:id="@+id/user_loading"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/car_padding_4"
- android:textSize="@dimen/car_body1_size"
- android:textColor="@color/car_body1"
- android:layout_below="@id/user_loading_avatar"
- android:gravity="center"/>
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index fdd965f..50e6f33 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 003c0da..fe7b0ae 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -44,6 +44,7 @@
<item><xliff:g id="id">@string/status_bar_secure</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 +60,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>
@@ -3329,6 +3329,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>
@@ -4165,9 +4176,21 @@
and a second time clipped to the fill level to indicate charge -->
<bool name="config_batterymeterDualTone">false</bool>
- <!-- The default peak refresh rate for a given device. Change this value if you want to allow
- for higher refresh rates to be automatically used out of the box -->
- <integer name="config_defaultPeakRefreshRate">60</integer>
+ <!-- The default refresh rate for a given device. Change this value to set a higher default
+ refresh rate. If the hardware composer on the device supports display modes with a higher
+ refresh rate than the default value specified here, the framework may use those higher
+ refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
+ setFrameRate().
+ If a non-zero value is set for config_defaultPeakRefreshRate, then
+ config_defaultRefreshRate may be set to 0, in which case the value set for
+ config_defaultPeakRefreshRate will act as the default frame rate. -->
+ <integer name="config_defaultRefreshRate">60</integer>
+
+ <!-- The default peak refresh rate for a given device. Change this value if you want to prevent
+ the framework from using higher refresh rates, even if display modes with higher refresh
+ rates are available from hardware composer. Only has an effect if the value is
+ non-zero. -->
+ <integer name="config_defaultPeakRefreshRate">0</integer>
<!-- The display uses different gamma curves for different refresh rates. It's hard for panel
vendor to tune the curves to have exact same brightness for different refresh rate. So
diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml
index bd4c484..2c4f4c8 100644
--- a/core/res/res/values/dimens_car.xml
+++ b/core/res/res/values/dimens_car.xml
@@ -16,14 +16,7 @@
*/
-->
<resources>
- <dimen name="car_fullscreen_user_pod_icon_text_size">64sp</dimen>
- <dimen name="car_fullscreen_user_pod_width">243dp</dimen>
- <dimen name="car_fullscreen_user_pod_height">356dp</dimen>
- <dimen name="car_fullscreen_user_pod_image_avatar_width">96dp</dimen>
- <dimen name="car_fullscreen_user_pod_image_avatar_height">96dp</dimen>
<dimen name="car_large_avatar_size">96dp</dimen>
-
-
<!-- Application Bar -->
<dimen name="car_app_bar_height">80dp</dimen>
<!-- Margin -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 54d14f8..233f72ea 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -451,10 +451,14 @@
<string name="personal_apps_suspension_text">
Your personal apps are blocked until you turn on your work profile</string>
<!-- Notification text. This notification lets a user know that their apps will be blocked
- tomorrow due to a work policy from their IT admin, and that they need to turn on their work
- profile to prevent the apps from being blocked. [CHAR LIMIT=NONE] -->
- <string name="personal_apps_suspension_tomorrow_text">
- Your personal apps will be blocked tomorrow</string>
+ at a particular time due to a work policy from their IT admin, and that they need to turn on
+ their work profile to prevent the apps from being blocked. It also explains for how many
+ days the profile is allowed to be off and this number is at least 3. [CHAR LIMIT=NONE] -->
+ <string name="personal_apps_suspension_soon_text">
+ Personal apps will be blocked on <xliff:g id="date" example="May 29">%1$s</xliff:g> at
+ <xliff:g id="time" example="5:20 PM">%2$s</xliff:g>. Your work profile can\u2019t stay off
+ for more than <xliff:g id="number" example="3">%3$d</xliff:g> days.
+ </string>
<!-- Title for the button that turns work profile on. To be used in a notification
[CHAR LIMIT=NONE] -->
<string name="personal_apps_suspended_turn_profile_on">Turn on work profile</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 52aab77..7744e9e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1200,7 +1200,7 @@
<java-symbol type="string" name="location_changed_notification_title" />
<java-symbol type="string" name="location_changed_notification_text" />
<java-symbol type="string" name="personal_apps_suspension_title" />
- <java-symbol type="string" name="personal_apps_suspension_tomorrow_text" />
+ <java-symbol type="string" name="personal_apps_suspension_soon_text" />
<java-symbol type="string" name="personal_apps_suspension_text" />
<java-symbol type="string" name="personal_apps_suspended_turn_profile_on" />
<java-symbol type="string" name="notification_work_profile_content_description" />
@@ -2745,6 +2745,10 @@
<java-symbol type="id" name="chooser_row_text_option" />
<java-symbol type="dimen" name="chooser_row_text_option_translate" />
<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" />
@@ -2814,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" />
@@ -3639,13 +3644,6 @@
<java-symbol type="color" name="car_card_dark" />
<java-symbol type="dimen" name="car_body1_size" />
<java-symbol type="dimen" name="car_padding_4" />
- <java-symbol type="dimen" name="car_fullscreen_user_pod_icon_text_size" />
- <java-symbol type="dimen" name="car_fullscreen_user_pod_image_avatar_height" />
- <java-symbol type="dimen" name="car_fullscreen_user_pod_image_avatar_width" />
- <java-symbol type="dimen" name="car_large_avatar_size" />
- <java-symbol type="layout" name="car_user_switching_dialog" />
- <java-symbol type="id" name="user_loading_avatar" />
- <java-symbol type="id" name="user_loading" />
<java-symbol type="style" name="Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog" />
<java-symbol type="string" name="battery_saver_description_with_learn_more" />
@@ -3777,6 +3775,7 @@
<java-symbol type="string" name="bluetooth_airplane_mode_toast" />
<!-- For high refresh rate displays -->
+ <java-symbol type="integer" name="config_defaultRefreshRate" />
<java-symbol type="integer" name="config_defaultPeakRefreshRate" />
<java-symbol type="integer" name="config_defaultRefreshRateInZone" />
<java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index a93dacf..000e870 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_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.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.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 @@
}
@Test
- public void testHandleActivityConfigurationChanged_PickNewerPendingConfiguration() {
+ public void testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending() {
final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
@@ -237,26 +240,91 @@
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
public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration()
throws Exception {
final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
@@ -268,7 +336,7 @@
config.orientation = ORIENTATION_PORTRAIT;
activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
- config, Display.INVALID_DISPLAY);
+ config, INVALID_DISPLAY);
});
final int numOfConfig = activity.mNumOfConfigChanges;
@@ -504,7 +572,7 @@
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 @@
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 @@
}
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 6c23125..4654f63 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 @@
@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 @@
@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 3f8d9ef..f11adef 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 @@
}
@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/coretests/src/android/debug/AdbNotificationsTest.java b/core/tests/coretests/src/android/debug/AdbNotificationsTest.java
index 6c187ea..3496e2c 100644
--- a/core/tests/coretests/src/android/debug/AdbNotificationsTest.java
+++ b/core/tests/coretests/src/android/debug/AdbNotificationsTest.java
@@ -56,8 +56,11 @@
// Verify that the adb notification for usb connections has the correct text.
assertEquals(title, notification.extras.getCharSequence(Notification.EXTRA_TITLE, ""));
assertEquals(message, notification.extras.getCharSequence(Notification.EXTRA_TEXT, ""));
- // Verify the PendingIntent has an explicit intent (b/153356209).
- assertFalse(TextUtils.isEmpty(notification.contentIntent.getIntent().getPackage()));
+ // Verify the PendingIntent has an explicit intent (b/153356209), if there is a
+ // PendingIntent attached.
+ if (notification.contentIntent != null) {
+ assertFalse(TextUtils.isEmpty(notification.contentIntent.getIntent().getPackage()));
+ }
}
@Test
@@ -73,7 +76,10 @@
// Verify that the adb notification for usb connections has the correct text.
assertEquals(title, notification.extras.getCharSequence(Notification.EXTRA_TITLE, ""));
assertEquals(message, notification.extras.getCharSequence(Notification.EXTRA_TEXT, ""));
- // Verify the PendingIntent has an explicit intent (b/153356209).
- assertFalse(TextUtils.isEmpty(notification.contentIntent.getIntent().getPackage()));
+ // Verify the PendingIntent has an explicit intent (b/153356209), if there is a
+ // PendingIntent attached.
+ if (notification.contentIntent != null) {
+ assertFalse(TextUtils.isEmpty(notification.contentIntent.getIntent().getPackage()));
+ }
}
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index cb84ff9..dcecb5f 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -1926,6 +1926,33 @@
.check(matches(isDisplayed()));
}
+ @Test
+ public void testDeduplicateCallerTargetRankedTarget() {
+ // Create 4 ranked app targets.
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(4);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ // Create caller target which is duplicate with one of app targets
+ Intent chooserIntent = createChooserIntent(createSendTextIntent(),
+ new Intent[] {new Intent("action.fake")});
+ sOverrides.packageManager = mock(PackageManager.class);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(0,
+ UserHandle.USER_CURRENT);
+ when(sOverrides.packageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(ri);
+ waitForIdle();
+
+ ChooserWrapperActivity activity = mActivityRule.launchActivity(chooserIntent);
+ waitForIdle();
+
+ // Total 4 targets (1 caller target, 3 ranked targets)
+ assertThat(activity.getAdapter().getCount(), is(4));
+ assertThat(activity.getAdapter().getCallerTargetCount(), is(1));
+ assertThat(activity.getAdapter().getRankedTargetCount(), is(3));
+ }
+
private Intent createChooserIntent(Intent intent, Intent[] initialIntents) {
Intent chooserIntent = new Intent();
chooserIntent.setAction(Intent.ACTION_CHOOSER);
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 72827a9..a5a2221 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/data/keyboards/Vendor_28de_Product_1102.kl b/data/keyboards/Vendor_28de_Product_1102.kl
new file mode 100644
index 0000000..150a17a
--- /dev/null
+++ b/data/keyboards/Vendor_28de_Product_1102.kl
@@ -0,0 +1,74 @@
+# 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.
+
+#
+# Steam Controller - Model 1001 - USB
+#
+
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
+key 304 BUTTON_A
+key 305 BUTTON_B
+key 307 BUTTON_X
+key 308 BUTTON_Y
+
+key 310 BUTTON_L1
+key 311 BUTTON_R1
+key 312 BUTTON_L2
+key 313 BUTTON_R2
+
+# Triggers.
+axis 0x15 LTRIGGER
+axis 0x14 RTRIGGER
+
+# Left and right stick.
+axis 0x00 X
+axis 0x01 Y
+
+# Right stick / mousepad
+axis 0x03 Z
+axis 0x04 RZ
+
+key 317 BUTTON_THUMBL
+key 318 BUTTON_THUMBR
+
+# Hat.
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+# Dpad (clicks)
+key 544 DPAD_UP
+key 545 DPAD_DOWN
+key 546 DPAD_LEFT
+key 547 DPAD_RIGHT
+
+# Touching the dpad (light touch without pressing down)
+key 289 BUTTON_1
+# Touching the "right stick" / mousepad (light touch without pressing down)
+key 290 BUTTON_2
+
+# Pressing the large paddle on the back, left (linux BTN_WHEEL / BTN_GEAR_DOWN)
+key 336 BUTTON_3
+# Pressing the large paddle on the back, right (linux BTN_GEAR_UP)
+key 337 BUTTON_4
+
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+# Left arrow
+key 314 BUTTON_SELECT
+# Right arrow
+key 315 BUTTON_START
+
+# Steam key
+key 316 BUTTON_MODE
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8ea6883..ea7a556 100755
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1505,7 +1505,7 @@
public void setSpeakerphoneOn(boolean on){
final IAudioService service = getService();
try {
- service.setSpeakerphoneOn(on);
+ service.setSpeakerphoneOn(mICallBack, on);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index 98c2d7f..357c333 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -16,6 +16,7 @@
package android.media;
import android.util.IntArray;
+
import com.android.server.LocalServices;
/**
@@ -48,6 +49,18 @@
public abstract void setAccessibilityServiceUids(IntArray uids);
+ /**
+ * Called by {@link com.android.server.inputmethod.InputMethodManagerService} to notify the UID
+ * of the currently used {@link android.inputmethodservice.InputMethodService}.
+ *
+ * <p>The caller is expected to take care of any performance implications, e.g. by using a
+ * background thread to call this method.</p>
+ *
+ * @param uid UID of the currently used {@link android.inputmethodservice.InputMethodService}.
+ * {@link android.os.Process#INVALID_UID} if no IME is active.
+ */
+ public abstract void setInputMethodServiceUid(int uid);
+
public interface RingerModeDelegate {
/** Called when external ringer mode is evaluated, returns the new internal ringer mode */
int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index e3b67f8..8137275 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -150,7 +150,7 @@
oneway void avrcpSupportsAbsoluteVolume(String address, boolean support);
- void setSpeakerphoneOn(boolean on);
+ void setSpeakerphoneOn(IBinder cb, boolean on);
boolean isSpeakerphoneOn();
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index bbd7399..54675d0 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -321,6 +321,21 @@
@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 @@
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 @@
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 @@
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 @@
*/
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 50af60a..a458b16 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.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.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 @@
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 @@
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 @@
profile, new HandlerExecutor(mHandler), mResourceListener, clientId);
mClientId = clientId[0];
+ mUserId = ActivityManager.getCurrentUser();
+
setFrontendInfoList();
setLnbIds();
}
@@ -358,6 +368,9 @@
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 @@
*/
@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 @@
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 @@
*/
@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 @@
}
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 331477f..43cb25f 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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
&errorDetailMsg);
} else {
if (!buffer) {
+ ALOGI("queueLinearBlock: no C2Buffer found");
throwExceptionAsNecessary(env, BAD_VALUE);
return;
}
@@ -2955,13 +2977,13 @@
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 @@
}
}
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 400ce1b..5c34341 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -162,6 +162,8 @@
void selectAudioPresentation(const int32_t presentationId, const int32_t programId);
+ bool hasCryptoOrDescrambler() { return mHasCryptoOrDescrambler; }
+
protected:
virtual ~JMediaCodec();
@@ -181,6 +183,7 @@
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 0843834..8f1d2fa 100644
--- a/media/jni/android_media_MediaCodecLinearBlock.h
+++ b/media/jni/android_media_MediaCodecLinearBlock.h
@@ -31,8 +31,10 @@
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 @@
}
sp<hardware::HidlMemory> toHidlMemory() {
- if (mMemory) {
- return mMemory;
+ if (mHidlMemory) {
+ return mHidlMemory;
}
return nullptr;
}
@@ -65,4 +67,4 @@
} // 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 0c1e9a2..262ec76 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 @@
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/CarSystemUI/AndroidManifest.xml b/packages/CarSystemUI/AndroidManifest.xml
index 261b9f5..1dd0291 100644
--- a/packages/CarSystemUI/AndroidManifest.xml
+++ b/packages/CarSystemUI/AndroidManifest.xml
@@ -25,4 +25,6 @@
<uses-permission android:name="android.car.permission.CAR_ENROLL_TRUST"/>
<!-- This permission is required to get bluetooth broadcast. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
+ <!-- This permission is required to check the foreground user id. -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
</manifest>
diff --git a/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml b/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml
new file mode 100644
index 0000000..0a29424
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml
@@ -0,0 +1,43 @@
+<?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:fitsSystemWindows="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:background="@color/car_user_switching_dialog_background_color">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_gravity="center"
+ android:gravity="center_horizontal">
+ <ImageView
+ android:id="@+id/user_loading_avatar"
+ android:layout_width="@dimen/car_fullscreen_user_pod_image_avatar_width"
+ android:layout_height="@dimen/car_fullscreen_user_pod_image_avatar_height"/>
+
+ <TextView
+ android:id="@+id/user_loading"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/car_user_switching_dialog_loading_text_margin_top"
+ android:textSize="@dimen/car_user_switching_dialog_loading_text_font_size"
+ android:textColor="@color/car_user_switching_dialog_loading_text_color"
+ android:layout_below="@id/user_loading_avatar"/>
+ </LinearLayout>
+</FrameLayout>
diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
index 3542323..2dc499c 100644
--- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
+++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
@@ -39,4 +39,9 @@
android:layout_height="match_parent"
android:layout="@layout/car_fullscreen_user_switcher"/>
+ <ViewStub android:id="@+id/user_switching_dialog_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout="@layout/car_user_switching_dialog"/>
+
</FrameLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index 3e44721..0e84d51 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -55,4 +55,7 @@
<color name="list_divider_color">@*android:color/car_list_divider_light</color>
<color name="car_volume_item_divider_color">@*android:color/car_list_divider</color>
<color name="car_volume_item_background_color">@*android:color/car_card_dark</color>
+
+ <color name="car_user_switching_dialog_background_color">@android:color/black</color>
+ <color name="car_user_switching_dialog_loading_text_color">@*android:color/car_body1</color>
</resources>
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index 67066d7..4bf0fca 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -70,11 +70,13 @@
to a constant alpha percent value using the initial alpha. -->
<integer name="config_finalNotificationBackgroundAlpha">100</integer>
- <!-- Car System UI's OverlayViewsMediator-->
+ <!-- Car System UI's OverlayViewsMediator.
+ Whenever a new class is added, make sure to also add that class to OverlayWindowModule. -->
<string-array name="config_carSystemUIOverlayViewsMediators" translatable="false">
<item>@string/config_notificationPanelViewMediator</item>
<item>com.android.systemui.car.keyguard.CarKeyguardViewMediator</item>
<item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item>
+ <item>com.android.systemui.car.userswitcher.UserSwitchTransitionViewMediator</item>
</string-array>
<!--
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
index 9014eb1..ed0b485 100644
--- a/packages/CarSystemUI/res/values/dimens.xml
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -15,6 +15,32 @@
~ limitations under the License
-->
<resources>
+ <!-- Text size for car -->
+ <dimen name="car_title_size">32sp</dimen>
+ <dimen name="car_title2_size">32sp</dimen>
+ <dimen name="car_headline1_size">45sp</dimen>
+ <dimen name="car_headline2_size">32sp</dimen>
+ <dimen name="car_headline3_size">24sp</dimen>
+ <dimen name="car_headline4_size">20sp</dimen>
+ <dimen name="car_body1_size">32sp</dimen>
+ <dimen name="car_body2_size">28sp</dimen>
+ <dimen name="car_body3_size">26sp</dimen>
+ <dimen name="car_body4_size">24sp</dimen>
+ <!-- car_body5_size is deprecated -->
+ <dimen name="car_body5_size">18sp</dimen>
+ <dimen name="car_label1_size">26sp</dimen>
+ <dimen name="car_label2_size">64sp</dimen>
+ <dimen name="car_action1_size">26sp</dimen>
+ <dimen name="car_action2_size">26sp</dimen>
+ <!-- Paddings -->
+ <dimen name="car_padding_0">4dp</dimen>
+ <dimen name="car_padding_1">8dp</dimen>
+ <dimen name="car_padding_2">16dp</dimen>
+ <dimen name="car_padding_3">24dp</dimen>
+ <dimen name="car_padding_4">32dp</dimen>
+ <dimen name="car_padding_5">64dp</dimen>
+ <dimen name="car_padding_6">96dp</dimen>
+
<!--
Note: status bar height and navigation bar heights are defined
in frameworks/base/core package and thus will have no effect if
@@ -156,4 +182,10 @@
<dimen name="car_user_switcher_container_height">420dp</dimen>
<!-- This must be the negative of car_user_switcher_container_height for the animation. -->
<dimen name="car_user_switcher_container_anim_height">-420dp</dimen>
+
+ <!-- dimensions for car user switching dialog -->
+ <dimen name="car_fullscreen_user_pod_image_avatar_width">96dp</dimen>
+ <dimen name="car_fullscreen_user_pod_image_avatar_height">96dp</dimen>
+ <dimen name="car_user_switching_dialog_loading_text_margin_top">@*android:dimen/car_padding_4</dimen>
+ <dimen name="car_user_switching_dialog_loading_text_font_size">@*android:dimen/car_body1_size</dimen>
</resources>
diff --git a/packages/CarSystemUI/res/values/strings.xml b/packages/CarSystemUI/res/values/strings.xml
index 9fae4b3..67fd5bb 100644
--- a/packages/CarSystemUI/res/values/strings.xml
+++ b/packages/CarSystemUI/res/values/strings.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-<resources>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- String to represent lowest setting of an HVAC system [CHAR LIMIT=10]-->
<string name="hvac_min_text">Min</string>
<!-- String to represent largest setting of an HVAC system [CHAR LIMIT=10]-->
@@ -34,4 +34,8 @@
<string name="user_add_user_message_setup">When you add a new user, that person needs to set up their space.</string>
<!-- Message to inform user that the newly created user will have permissions to update apps for all other users. [CHAR LIMIT=100] -->
<string name="user_add_user_message_update">Any user can update apps for all other users.</string>
+ <!-- Message to inform user that the new user profile is loading. [CHAR LIMIT=20] -->
+ <string name="car_loading_profile">Loading</string>
+ <!-- Message to inform user that the new user profile is loading with additional information on the previous and the next user. [CHAR LIMIT=100] -->
+ <string name="car_loading_profile_developer_message">Loading user (from <xliff:g id="from_user" example="10">%1$d</xliff:g> to <xliff:g id="to_user" example="12">%2$d</xliff:g>)</string>
</resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index f8729c3..fe2be1d 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -30,6 +30,7 @@
import com.android.systemui.car.keyguard.CarKeyguardViewController;
import com.android.systemui.car.statusbar.CarStatusBar;
import com.android.systemui.car.statusbar.CarStatusBarKeyguardViewManager;
+import com.android.systemui.car.statusbar.DozeServiceHost;
import com.android.systemui.car.statusbar.DummyNotificationShadeWindowController;
import com.android.systemui.car.volume.CarVolumeDialogComponent;
import com.android.systemui.dagger.SystemUIRootComponent;
@@ -37,6 +38,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
+import com.android.systemui.doze.DozeHost;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
@@ -174,4 +176,7 @@
@Binds
abstract NotificationShadeWindowController bindNotificationShadeWindowController(
DummyNotificationShadeWindowController notificationShadeWindowController);
+
+ @Binds
+ abstract DozeHost bindDozeHost(DozeServiceHost dozeServiceHost);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index a701e9f..ab61b44 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -188,7 +188,7 @@
stop();
getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false);
mKeyguardStateController.notifyKeyguardDoneFading();
- mViewMediatorCallback.keyguardGone();
+ mHandler.post(mViewMediatorCallback::keyguardGone);
notifyKeyguardUpdateMonitor();
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
index 8e11414..5c6472e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
@@ -16,8 +16,10 @@
package com.android.systemui.car.navigationbar;
+import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_GESTURES;
import static android.view.InsetsState.containsType;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -377,7 +379,7 @@
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.TRANSLUCENT);
lp.setTitle("TopCarNavigationBar");
- lp.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR};
+ lp.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_TOP_GESTURES};
lp.setFitInsetsTypes(0);
lp.windowAnimations = 0;
lp.gravity = Gravity.TOP;
@@ -399,7 +401,7 @@
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.TRANSLUCENT);
lp.setTitle("BottomCarNavigationBar");
- lp.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
+ lp.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR, ITYPE_BOTTOM_GESTURES};
lp.windowAnimations = 0;
lp.gravity = Gravity.BOTTOM;
mWindowManager.addView(mBottomNavigationBarWindow, lp);
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java
index e2eb3fb..d18eadd 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java
@@ -425,6 +425,11 @@
}
@Override
+ public void notifyBiometricAuthModeChanged() {
+ // No op.
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
//When executing dump() function simultaneously, we need to serialize them
//to get mStackScroller's position correctly.
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java
new file mode 100644
index 0000000..d23660c
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java
@@ -0,0 +1,130 @@
+/*
+ * 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.car.statusbar;
+
+import com.android.systemui.doze.DozeHost;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** No-op implementation of {@link DozeHost} for use by car sysui, which does not support dozing. */
+@Singleton
+public class DozeServiceHost implements DozeHost {
+
+ @Inject
+ public DozeServiceHost() {}
+
+ @Override
+ public void addCallback(Callback callback) {
+ // No op.
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ // No op.
+ }
+
+ @Override
+ public void startDozing() {
+ // No op.
+ }
+
+ @Override
+ public void pulseWhileDozing(PulseCallback callback, int reason) {
+ // No op.
+ }
+
+ @Override
+ public void stopDozing() {
+ // No op.
+ }
+
+ @Override
+ public void dozeTimeTick() {
+ // No op.
+ }
+
+ @Override
+ public boolean isPowerSaveActive() {
+ return false;
+ }
+
+ @Override
+ public boolean isPulsingBlocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isProvisioned() {
+ return false;
+ }
+
+ @Override
+ public boolean isBlockingDoze() {
+ return true;
+ }
+
+ @Override
+ public void extendPulse(int reason) {
+ // No op.
+ }
+
+ @Override
+ public void setAnimateWakeup(boolean animateWakeup) {
+ // No op.
+ }
+
+ @Override
+ public void setAnimateScreenOff(boolean animateScreenOff) {
+ // No op.
+ }
+
+ @Override
+ public void onSlpiTap(float x, float y) {
+ // No op.
+ }
+
+ @Override
+ public void setDozeScreenBrightness(int value) {
+ // No op.
+ }
+
+ @Override
+ public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
+ // No op.
+ }
+
+ @Override
+ public void cancelGentleSleep() {
+ // No op.
+ }
+
+ @Override
+ public void onIgnoreTouchWhilePulsing(boolean ignore) {
+ // No op.
+ }
+
+ @Override
+ public void stopPulsing() {
+ // No op.
+ }
+
+ @Override
+ public boolean isDozeSuppressed() {
+ return true;
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
index 346c38c..8b399f8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
@@ -34,16 +34,19 @@
private final StatusBarStateController mStatusBarStateController;
private final FullScreenUserSwitcherViewController mFullScreenUserSwitcherViewController;
private final CarKeyguardViewController mCarKeyguardViewController;
+ private final UserSwitchTransitionViewController mUserSwitchTransitionViewController;
@Inject
public FullscreenUserSwitcherViewMediator(
StatusBarStateController statusBarStateController,
CarKeyguardViewController carKeyguardViewController,
+ UserSwitchTransitionViewController userSwitchTransitionViewController,
FullScreenUserSwitcherViewController fullScreenUserSwitcherViewController) {
mStatusBarStateController = statusBarStateController;
- mFullScreenUserSwitcherViewController = fullScreenUserSwitcherViewController;
mCarKeyguardViewController = carKeyguardViewController;
+ mUserSwitchTransitionViewController = userSwitchTransitionViewController;
+ mFullScreenUserSwitcherViewController = fullScreenUserSwitcherViewController;
}
@Override
@@ -74,6 +77,11 @@
private void onUserSelected(UserGridRecyclerView.UserRecord record) {
if (record.mType != UserGridRecyclerView.UserRecord.FOREGROUND_USER) {
mCarKeyguardViewController.hideKeyguardToPrepareBouncer();
+ // If guest user, we cannot use record.mInfo.id and should listen to the User lifecycle
+ // event instead.
+ if (record.mType != UserGridRecyclerView.UserRecord.START_GUEST) {
+ mUserSwitchTransitionViewController.handleShow(record.mInfo.id);
+ }
}
hide();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
new file mode 100644
index 0000000..775ef81
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
@@ -0,0 +1,126 @@
+/*
+ * 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.car.userswitcher;
+
+import static android.car.settings.CarSettings.Global.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE;
+
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.systemui.R;
+import com.android.systemui.car.window.OverlayViewController;
+import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Handles showing and hiding UserSwitchTransitionView that is mounted to SystemUiOverlayWindow.
+ */
+@Singleton
+public class UserSwitchTransitionViewController extends OverlayViewController {
+ private static final String TAG = "UserSwitchTransitionViewController";
+ private static final String ENABLE_DEVELOPER_MESSAGE_TRUE = "true";
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final Resources mResources;
+ private final UserManager mUserManager;
+
+ @GuardedBy("this")
+ private boolean mShowing;
+ private int mPreviousUserId = UserHandle.USER_NULL;
+
+ @Inject
+ public UserSwitchTransitionViewController(
+ Context context,
+ @Main Handler handler,
+ @Main Resources resources,
+ UserManager userManager,
+ OverlayViewGlobalStateController overlayViewGlobalStateController) {
+
+ super(R.id.user_switching_dialog_stub, overlayViewGlobalStateController);
+
+ mContext = context;
+ mHandler = handler;
+ mResources = resources;
+ mUserManager = userManager;
+ }
+
+ /**
+ * Makes the user switch transition view appear and draws the content inside of it if a user
+ * that is different from the previous user is provided and if the dialog is not already
+ * showing.
+ */
+ void handleShow(@UserIdInt int newUserId) {
+ if (mPreviousUserId == newUserId || mShowing) return;
+ mShowing = true;
+ mHandler.post(() -> {
+ start();
+ populateDialog(mPreviousUserId, newUserId);
+ // next time a new user is selected, this current new user will be the previous user.
+ mPreviousUserId = newUserId;
+ });
+ }
+
+ void handleHide() {
+ if (!mShowing) return;
+ mShowing = false;
+ mHandler.post(this::stop);
+ }
+
+ private void populateDialog(@UserIdInt int previousUserId, @UserIdInt int newUserId) {
+ drawUserIcon(newUserId);
+ populateLoadingText(previousUserId, newUserId);
+ }
+
+ private void drawUserIcon(int newUserId) {
+ Bitmap bitmap = mUserManager.getUserIcon(newUserId);
+ if (bitmap != null) {
+ CircleFramedDrawable drawable = CircleFramedDrawable.getInstance(mContext, bitmap);
+ ((ImageView) getLayout().findViewById(R.id.user_loading_avatar))
+ .setImageDrawable(drawable);
+ }
+ }
+
+ private void populateLoadingText(@UserIdInt int previousUserId, @UserIdInt int newUserId) {
+ TextView msgView = getLayout().findViewById(R.id.user_loading);
+
+ boolean showInfo = ENABLE_DEVELOPER_MESSAGE_TRUE.equals(
+ Settings.Global.getString(mContext.getContentResolver(),
+ ENABLE_USER_SWITCH_DEVELOPER_MESSAGE));
+
+ if (showInfo && mPreviousUserId != UserHandle.USER_NULL) {
+ msgView.setText(
+ mResources.getString(R.string.car_loading_profile_developer_message,
+ previousUserId, newUserId));
+ } else {
+ msgView.setText(mResources.getString(R.string.car_loading_profile));
+ }
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediator.java
new file mode 100644
index 0000000..aea6914
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediator.java
@@ -0,0 +1,85 @@
+/*
+ * 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.car.userswitcher;
+
+import android.app.ActivityManager;
+import android.car.Car;
+import android.car.user.CarUserManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.window.OverlayViewMediator;
+
+import javax.inject.Inject;
+
+/**
+ * Registers listeners that subscribe to events that show or hide CarUserSwitchingDialog that is
+ * mounted to SystemUiOverlayWindow.
+ */
+public class UserSwitchTransitionViewMediator implements OverlayViewMediator,
+ CarUserManager.UserSwitchUiCallback {
+ private static final String TAG = "UserSwitchTransitionViewMediator";
+
+ private final CarServiceProvider mCarServiceProvider;
+ private final UserSwitchTransitionViewController mUserSwitchTransitionViewController;
+
+ @Inject
+ public UserSwitchTransitionViewMediator(
+ CarServiceProvider carServiceProvider,
+ UserSwitchTransitionViewController userSwitchTransitionViewController) {
+ mCarServiceProvider = carServiceProvider;
+ mUserSwitchTransitionViewController = userSwitchTransitionViewController;
+ }
+
+ @Override
+ public void registerListeners() {
+ mCarServiceProvider.addListener(car -> {
+ CarUserManager carUserManager =
+ (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
+
+ if (carUserManager != null) {
+ carUserManager.setUserSwitchUiCallback(this);
+ carUserManager.addListener(Runnable::run, this::handleUserLifecycleEvent);
+ } else {
+ Log.e(TAG, "registerListeners: CarUserManager could not be obtained.");
+ }
+ });
+ }
+
+ @Override
+ public void setupOverlayContentViewControllers() {
+ // no-op.
+ }
+
+ @Override
+ public void showUserSwitchDialog(int userId) {
+ mUserSwitchTransitionViewController.handleShow(userId);
+ }
+
+ @VisibleForTesting
+ void handleUserLifecycleEvent(CarUserManager.UserLifecycleEvent event) {
+ if (event.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING
+ && ActivityManager.getCurrentUser() == event.getUserId()) {
+ mUserSwitchTransitionViewController.handleShow(event.getUserId());
+ }
+
+ if (event.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
+ mUserSwitchTransitionViewController.handleHide();
+ }
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewMediator.java
index ac574ed..3e7b4a2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewMediator.java
@@ -23,6 +23,9 @@
/**
* Register listeners that could use ContentVisibilityAdjuster to show/hide content.
+ *
+ * Note that we do not unregister listeners because SystemUI components are expected to live
+ * for the lifecycle of the device.
*/
void registerListeners();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java
index 484aa63..5a16efa 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java
@@ -21,6 +21,7 @@
import com.android.systemui.car.notification.NotificationPanelViewMediator;
import com.android.systemui.car.notification.TopNotificationPanelViewMediator;
import com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator;
+import com.android.systemui.car.userswitcher.UserSwitchTransitionViewMediator;
import dagger.Binds;
import dagger.Module;
@@ -67,4 +68,11 @@
@ClassKey(FullscreenUserSwitcherViewMediator.class)
public abstract OverlayViewMediator bindFullscreenUserSwitcherViewsMediator(
FullscreenUserSwitcherViewMediator overlayViewsMediator);
+
+ /** Injects CarUserSwitchingDialogMediator. */
+ @Binds
+ @IntoMap
+ @ClassKey(UserSwitchTransitionViewMediator.class)
+ public abstract OverlayViewMediator bindUserSwitchTransitionViewMediator(
+ UserSwitchTransitionViewMediator userSwitchTransitionViewMediator);
}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
new file mode 100644
index 0000000..eab381c
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.car.userswitcher;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.UserManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.window.OverlayViewGlobalStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class UserSwitchTransitionViewControllerTest extends SysuiTestCase {
+ private static final int TEST_USER_1 = 100;
+ private static final int TEST_USER_2 = 110;
+
+ private TestableUserSwitchTransitionViewController mCarUserSwitchingDialogController;
+ private TestableResources mTestableResources;
+ @Mock
+ private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTestableResources = mContext.getOrCreateTestableResources();
+ mCarUserSwitchingDialogController = new TestableUserSwitchTransitionViewController(
+ mContext,
+ Handler.getMain(),
+ mTestableResources.getResources(),
+ (UserManager) mContext.getSystemService(Context.USER_SERVICE),
+ mOverlayViewGlobalStateController
+ );
+
+ mCarUserSwitchingDialogController.inflate((ViewGroup) LayoutInflater.from(mContext).inflate(
+ R.layout.sysui_overlay_window, /* root= */ null));
+ }
+
+ @Test
+ public void onHandleShow_newUserSelected_showsDialog() {
+ mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+
+ verify(mOverlayViewGlobalStateController).showView(eq(mCarUserSwitchingDialogController),
+ any());
+ }
+
+ @Test
+ public void onHandleShow_alreadyShowing_ignoresRequest() {
+ mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+ mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_2);
+
+ // Verify that the request was processed only once.
+ verify(mOverlayViewGlobalStateController).showView(eq(mCarUserSwitchingDialogController),
+ any());
+ }
+
+ @Test
+ public void onHandleShow_sameUserSelected_ignoresRequest() {
+ mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+ mCarUserSwitchingDialogController.handleHide();
+ mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+
+ // Verify that the request was processed only once.
+ verify(mOverlayViewGlobalStateController).showView(eq(mCarUserSwitchingDialogController),
+ any());
+ }
+
+ @Test
+ public void onHide_currentlyShowing_hidesDialog() {
+ mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+ mCarUserSwitchingDialogController.handleHide();
+
+ verify(mOverlayViewGlobalStateController).hideView(eq(mCarUserSwitchingDialogController),
+ any());
+ }
+
+ @Test
+ public void onHide_notShowing_ignoresRequest() {
+ mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+ mCarUserSwitchingDialogController.handleHide();
+ mCarUserSwitchingDialogController.handleHide();
+
+ // Verify that the request was processed only once.
+ verify(mOverlayViewGlobalStateController).hideView(eq(mCarUserSwitchingDialogController),
+ any());
+ }
+
+ private final class TestableUserSwitchTransitionViewController extends
+ UserSwitchTransitionViewController {
+
+ private final Handler mHandler;
+
+ TestableUserSwitchTransitionViewController(Context context, Handler handler,
+ Resources resources, UserManager userManager,
+ OverlayViewGlobalStateController overlayViewGlobalStateController) {
+ super(context, handler, resources, userManager, overlayViewGlobalStateController);
+ mHandler = handler;
+ }
+
+ @Override
+ public void handleShow(int currentUserId) {
+ super.handleShow(currentUserId);
+ waitForIdleSync(mHandler);
+ }
+
+ @Override
+ public void handleHide() {
+ super.handleHide();
+ waitForIdleSync(mHandler);
+ }
+ }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java
new file mode 100644
index 0000000..a808e2d
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.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.car.userswitcher;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.user.CarUserManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.car.CarServiceProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class UserSwitchTransitionViewMediatorTest {
+ private static final int TEST_USER = 100;
+
+ private UserSwitchTransitionViewMediator mUserSwitchTransitionViewMediator;
+ @Mock
+ private CarServiceProvider mCarServiceProvider;
+ @Mock
+ private UserSwitchTransitionViewController mUserSwitchTransitionViewController;
+ @Mock
+ private CarUserManager.UserLifecycleEvent mUserLifecycleEvent;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mUserSwitchTransitionViewMediator = new UserSwitchTransitionViewMediator(
+ mCarServiceProvider, mUserSwitchTransitionViewController);
+
+ }
+
+ @Test
+ public void onUserLifecycleEvent_userStarting_callsHandleShow() {
+ when(mUserLifecycleEvent.getEventType()).thenReturn(
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING);
+ when(mUserLifecycleEvent.getUserId()).thenReturn(TEST_USER);
+ mUserSwitchTransitionViewMediator.handleUserLifecycleEvent(mUserLifecycleEvent);
+
+ verify(mUserSwitchTransitionViewController).handleShow(TEST_USER);
+ }
+
+ @Test
+ public void onUserLifecycleEvent_userSwitching_callsHandleHide() {
+ when(mUserLifecycleEvent.getEventType()).thenReturn(
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ mUserSwitchTransitionViewMediator.handleUserLifecycleEvent(mUserLifecycleEvent);
+
+ verify(mUserSwitchTransitionViewController).handleHide();
+ }
+}
diff --git a/packages/PackageInstaller/res/values-ne/strings.xml b/packages/PackageInstaller/res/values-ne/strings.xml
index d5faac2..60934b1 100644
--- a/packages/PackageInstaller/res/values-ne/strings.xml
+++ b/packages/PackageInstaller/res/values-ne/strings.xml
@@ -24,8 +24,8 @@
<string name="installing_app" msgid="1165095864863849422">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> स्थापना गर्दै…"</string>
<string name="install_done" msgid="5987363587661783896">"एप स्थापना गरियो।"</string>
<string name="install_confirm_question" msgid="8176284075816604590">"तपाईं यो एप स्थापना गर्न चाहनुहुन्छ?"</string>
- <string name="install_confirm_question_update" msgid="7942235418781274635">"तपाईं यो पहिलेदेखि नै विद्यमान अनुप्रयोगको साटो यसको अद्यावधिक संस्करण स्थापना गर्न चाहनुहुन्छ? तपाईंको विद्यमान डेटा गुम्ने छैन।"</string>
- <string name="install_confirm_question_update_system" msgid="4713001702777910263">"तपाईं यो अन्तर्निर्मित अनुप्रयोगको साटो यसको अद्यावधिक संस्करण स्थापना गर्न चाहनुहुन्छ? तपाईंको विद्यमान डेटा गुम्ने छैन।"</string>
+ <string name="install_confirm_question_update" msgid="7942235418781274635">"तपाईं यो पहिलेदेखि नै विद्यमान एपको साटो यसको अद्यावधिक संस्करण स्थापना गर्न चाहनुहुन्छ? तपाईंको विद्यमान डेटा गुम्ने छैन।"</string>
+ <string name="install_confirm_question_update_system" msgid="4713001702777910263">"तपाईं यो अन्तर्निर्मित एपको साटो यसको अद्यावधिक संस्करण स्थापना गर्न चाहनुहुन्छ? तपाईंको विद्यमान डेटा गुम्ने छैन।"</string>
<string name="install_failed" msgid="5777824004474125469">"एप स्थापना गरिएन।"</string>
<string name="install_failed_blocked" msgid="8512284352994752094">"यो प्याकेज स्थापना गर्ने क्रममा अवरोध गरियो।"</string>
<string name="install_failed_conflict" msgid="3493184212162521426">"प्याकेजका रूपमा स्थापना नगरिएको एप विद्यमान प्याकेजसँग मेल खाँदैन।"</string>
@@ -49,16 +49,16 @@
<string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"अनुमति छैन"</string>
<string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"हालका प्रयोगकर्तालाई यो स्थापना रद्द गर्ने कार्य गर्ने अनुमति छैन।"</string>
<string name="generic_error_dlg_title" msgid="5863195085927067752">"त्रुटि"</string>
- <string name="generic_error_dlg_text" msgid="5287861443265795232">"अनुप्रयोगको स्थापना रद्द गर्न सकिएन।"</string>
- <string name="uninstall_application_title" msgid="4045420072401428123">"अनुप्रयोगको स्थापना रद्द गर्नु…"</string>
+ <string name="generic_error_dlg_text" msgid="5287861443265795232">"एपको स्थापना रद्द गर्न सकिएन।"</string>
+ <string name="uninstall_application_title" msgid="4045420072401428123">"एपको स्थापना रद्द गर्नु…"</string>
<string name="uninstall_update_title" msgid="824411791011583031">"अद्यावधिकको स्थापना रद्द गर्नु…"</string>
- <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> निम्न अनुप्रयोगको अंश हो:"</string>
- <string name="uninstall_application_text" msgid="3816830743706143980">"तपाईं यो अनुप्रयोगको स्थापना रद्द गर्न चाहनुहुन्छ?"</string>
+ <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> निम्न एपको अंश हो:"</string>
+ <string name="uninstall_application_text" msgid="3816830743706143980">"तपाईं यो एपको स्थापना रद्द गर्न चाहनुहुन्छ?"</string>
<string name="uninstall_application_text_all_users" msgid="575491774380227119">"तपाईं "<b>"सबै"</b>" प्रयोगकर्ताका लागि यो एपको स्थापना रद्द गर्न चाहनुहुन्छ? यन्त्रका "<b>"सबै"</b>" प्रयोगकर्ताहरूबाट उक्त एप र यसको डेटा हटाइने छ।"</string>
- <string name="uninstall_application_text_user" msgid="498072714173920526">"तपाईं प्रयोगकर्ता <xliff:g id="USERNAME">%1$s</xliff:g> का लागि यो अनुप्रयोगको स्थापना रद्द गर्न चाहनुहुन्छ?"</string>
+ <string name="uninstall_application_text_user" msgid="498072714173920526">"तपाईं प्रयोगकर्ता <xliff:g id="USERNAME">%1$s</xliff:g> का लागि यो एपको स्थापना रद्द गर्न चाहनुहुन्छ?"</string>
<string name="uninstall_update_text" msgid="863648314632448705">"यस एपलाई फ्याक्ट्रीको संस्करणले बदल्ने हो? सबै डेटा हटाइने छ।"</string>
<string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"यस एपलाई फ्याक्ट्रीको संस्करणले बदल्ने हो? सबै डेटा हटाइने छ। यसले यस यन्त्रका कार्य प्रोफाइल भएका लगायत सबै प्रयोगकर्ताहरूमा असर पार्छ।"</string>
- <string name="uninstall_keep_data" msgid="7002379587465487550">"<xliff:g id="SIZE">%1$s</xliff:g> अनुप्रयोगको डेटा राख्नुहोस्।"</string>
+ <string name="uninstall_keep_data" msgid="7002379587465487550">"<xliff:g id="SIZE">%1$s</xliff:g> एपको डेटा राख्नुहोस्।"</string>
<string name="uninstalling_notification_channel" msgid="840153394325714653">"चलिरहेका स्थापना रद्द गर्ने कार्यहरू"</string>
<string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"असफल भएका स्थापना रद्द गर्ने कार्यहरू"</string>
<string name="uninstalling" msgid="8709566347688966845">"स्थापना रद्द गर्दै…"</string>
@@ -67,12 +67,12 @@
<string name="uninstall_done_app" msgid="4588850984473605768">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> को स्थापना रद्द गरियो"</string>
<string name="uninstall_failed" msgid="1847750968168364332">"स्थापना रद्द गर्न सकिएन।"</string>
<string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> को स्थापना रद्द गर्ने कार्य असफल भयो।"</string>
- <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"यन्त्रको सक्रिय प्रशासकीय अनुप्रयोगको स्थापना रद्द गर्न मिल्दैन"</string>
- <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g> को यन्त्रको सक्रिय प्रशासकीय अनुप्रयोगको स्थापना रद्द गर्न मिल्दैन"</string>
- <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"अन्य प्रयोगकर्ताहरूका लागि यस अनुप्रयोगको स्थापना रद्द गरे पनि केही प्रयोगकर्ता वा प्रोफाइलहरूलाई यसको आवश्यकता पर्दछ"</string>
+ <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"यन्त्रको सक्रिय प्रशासकीय एपको स्थापना रद्द गर्न मिल्दैन"</string>
+ <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g> को यन्त्रको सक्रिय प्रशासकीय एपको स्थापना रद्द गर्न मिल्दैन"</string>
+ <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"अन्य प्रयोगकर्ताहरूका लागि यस एपको स्थापना रद्द गरे पनि केही प्रयोगकर्ता वा प्रोफाइलहरूलाई यसको आवश्यकता पर्दछ"</string>
<string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"यो एप तपाईंको प्रोफाइलका लागि आवश्यक छ र यसको स्थापना रद्द गर्न सकिँदैन।"</string>
<string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"यो एप तपाईंको यन्त्रका प्रशासकका लागि आवश्यक छ र यसको स्थापना रद्द गर्न सकिँदैन।"</string>
- <string name="manage_device_administrators" msgid="3092696419363842816">"यन्त्रका व्यवस्थापकीय अनुप्रयोगको व्यवस्थापन गर्नु…"</string>
+ <string name="manage_device_administrators" msgid="3092696419363842816">"यन्त्रका व्यवस्थापकीय एपको व्यवस्थापन गर्नु…"</string>
<string name="manage_users" msgid="1243995386982560813">"प्रयोगकर्ताहरूको व्यवस्थापन गर्नुहोस्"</string>
<string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> को स्थापना रद्द गर्न सकिएन।"</string>
<string name="Parse_error_dlg_text" msgid="1661404001063076789">"प्याकेजलाई पार्स गर्ने क्रममा समस्या भयो।"</string>
@@ -89,7 +89,7 @@
<string name="anonymous_source_continue" msgid="4375745439457209366">"जारी राख्नुहोस्"</string>
<string name="external_sources_settings" msgid="4046964413071713807">"सेटिङहरू"</string>
<string name="wear_app_channel" msgid="1960809674709107850">"वेयर एपहरूको स्थापना/स्थापना रद्द गर्दै"</string>
- <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"अनुप्रयोगको स्थापना गरिएको सूचना"</string>
+ <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"एपको स्थापना गरिएको सूचना"</string>
<string name="notification_installation_success_message" msgid="6450467996056038442">"सफलतापूर्वक स्थापना गरियो"</string>
<string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” सफलतापूर्वक स्थापना गरियो"</string>
</resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
index 1709506..467a60e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -231,11 +231,6 @@
// Add the new printers, i.e. what is left.
printers.addAll(discoveredPrinters.values());
- // Do nothing if the printer list is not changed.
- if (Objects.equals(mPrinters, printers)) {
- return;
- }
-
// Update the list of printers.
mPrinters.clear();
mPrinters.addAll(printers);
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index e42e438..3dbf5a4 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 a860ff1..9e8c70f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -381,6 +381,15 @@
return mInfoMediaManager.getActiveMediaSession();
}
+ /**
+ * Gets the current package name.
+ *
+ * @return current package name
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
private MediaDevice updateCurrentConnectedDevice() {
synchronized (mMediaDevicesLock) {
for (MediaDevice device : mMediaDevices) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 8ea5ff1..9a3ae1b 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 @@
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/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index cefe690..8968340 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -83,7 +83,16 @@
* <p>An AccessPoint, which would be more fittingly named "WifiNetwork", is an aggregation of
* {@link ScanResult ScanResults} along with pertinent metadata (e.g. current connection info,
* network scores) required to successfully render the network to the user.
+ *
+ * @deprecated WifiTracker/AccessPoint is no longer supported, and will be removed in a future
+ * release. Clients that need a dynamic list of available wifi networks should migrate to one of the
+ * newer tracker classes,
+ * {@link com.android.wifitrackerlib.WifiPickerTracker},
+ * {@link com.android.wifitrackerlib.SavedNetworkTracker},
+ * {@link com.android.wifitrackerlib.NetworkDetailsTracker},
+ * in conjunction with {@link com.android.wifitrackerlib.WifiEntry} to represent each wifi network.
*/
+@Deprecated
public class AccessPoint implements Comparable<AccessPoint> {
static final String TAG = "SettingsLib.AccessPoint";
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 586c154..d1cd043 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -77,7 +77,16 @@
/**
* Tracks saved or available wifi networks and their state.
+ *
+ * @deprecated WifiTracker/AccessPoint is no longer supported, and will be removed in a future
+ * release. Clients that need a dynamic list of available wifi networks should migrate to one of the
+ * newer tracker classes,
+ * {@link com.android.wifitrackerlib.WifiPickerTracker},
+ * {@link com.android.wifitrackerlib.SavedNetworkTracker},
+ * {@link com.android.wifitrackerlib.NetworkDetailsTracker},
+ * in conjunction with {@link com.android.wifitrackerlib.WifiEntry} to represent each wifi network.
*/
+@Deprecated
public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
/**
* Default maximum age in millis of cached scored networks in
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 47f6fe3..421e5c0 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 @@
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 83d7218..b85c771 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 5f9563a..0000000
--- 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 bd97561..0000000
--- 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 c4ea2e9..055b2be 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" />
@@ -685,6 +686,7 @@
</activity>
<activity android:name=".controls.management.ControlsEditingActivity"
+ android:label="@string/controls_menu_edit"
android:theme="@style/Theme.ControlsManagement"
android:excludeFromRecents="true"
android:noHistory="true"
@@ -762,7 +764,8 @@
<provider
android:name="com.android.keyguard.clock.ClockOptionsProvider"
android:authorities="com.android.keyguard.clock"
- android:exported="true"
+ android:enabled="false"
+ android:exported="false"
android:grantUriPermissions="true">
</provider>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index aeedc16..e246917 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -66,14 +66,6 @@
void destroy();
- /**
- * return true if the tile supports detail views, and not
- * only boolean states
- */
- default boolean supportsDetailView() {
- return false;
- }
-
CharSequence getTileLabel();
State getState();
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 0000000..62ebbaa
--- /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/layout-land/global_screenshot_preview.xml b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
new file mode 100644
index 0000000..b1f4cb7
--- /dev/null
+++ b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
@@ -0,0 +1,33 @@
+<?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.
+ -->
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/global_screenshot_preview"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/global_screenshot_x_scale"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/screenshot_offset_x"
+ android:layout_marginBottom="@dimen/screenshot_offset_y"
+ android:scaleType="fitStart"
+ android:elevation="@dimen/screenshot_preview_elevation"
+ android:visibility="gone"
+ android:background="@drawable/screenshot_rounded_corners"
+ android:adjustViewBounds="true"
+ android:contentDescription="@string/screenshot_preview_description"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
\ No newline at end of file
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 477ec6a..99b9ced 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
@@ -14,57 +14,39 @@
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"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:paddingTop="@dimen/global_actions_grid_item_vertical_margin"
- android:paddingBottom="@dimen/global_actions_grid_item_vertical_margin"
+ android:layout_height="98dp"
+ android:gravity="bottom|center_horizontal"
+ android:orientation="vertical"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
android:paddingLeft="@dimen/global_actions_grid_item_side_margin"
android:paddingRight="@dimen/global_actions_grid_item_side_margin"
android:layout_marginRight="@dimen/control_base_item_margin"
android:layout_marginLeft="@dimen/control_base_item_margin"
android:stateListAnimator="@anim/control_state_list_animator"
android:background="@drawable/control_background">
- <LinearLayout
- android:layout_width="@dimen/global_actions_grid_item_width"
- android:layout_height="@dimen/global_actions_grid_item_height"
- android:gravity="top|center_horizontal"
- android:orientation="vertical">
<ImageView
android:id="@*android:id/icon"
- android:layout_width="@dimen/global_actions_grid_item_icon_width"
- android:layout_height="@dimen/global_actions_grid_item_icon_height"
- android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin"
- android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin"
- android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin"
- android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:layout_marginBottom="14dp"
android:scaleType="centerInside"
- android:tint="@color/control_default_foreground" />
-
+ android:tint="@color/control_primary_text" />
<TextView
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:singleLine="true"
+ android:maxLines="2"
+ android:textSize="12sp"
android:gravity="center"
- android:textSize="12dp"
- android:textColor="@color/control_default_foreground"
+ android:textColor="@color/control_primary_text"
+ android:breakStrategy="high_quality"
+ android:hyphenationFrequency="full"
android:textAppearance="?android:attr/textAppearanceSmall" />
-
- <TextView
- android:visibility="gone"
- android:id="@*android:id/status"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:textColor="@color/control_default_foreground"
- android:textAppearance="?android:attr/textAppearanceSmall" />
- </LinearLayout>
-</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 e4e9d29..ab4732d 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.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 1dbb38d..d469e0f 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -71,21 +71,7 @@
android:elevation="@dimen/screenshot_preview_elevation"
android:background="@drawable/screenshot_rounded_corners"
android:adjustViewBounds="true"/>
- <ImageView
- android:id="@+id/global_screenshot_preview"
- android:layout_width="@dimen/global_screenshot_x_scale"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginStart="@dimen/screenshot_offset_x"
- android:layout_marginBottom="@dimen/screenshot_offset_y"
- android:scaleType="fitEnd"
- android:elevation="@dimen/screenshot_preview_elevation"
- android:visibility="gone"
- android:background="@drawable/screenshot_rounded_corners"
- android:adjustViewBounds="true"
- android:contentDescription="@string/screenshot_preview_description"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"/>
+ <include layout="@layout/global_screenshot_preview"/>
<FrameLayout
android:id="@+id/global_screenshot_dismiss_button"
android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
diff --git a/packages/SystemUI/res/layout/global_screenshot_preview.xml b/packages/SystemUI/res/layout/global_screenshot_preview.xml
new file mode 100644
index 0000000..e6295f5
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_screenshot_preview.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2011 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.
+ -->
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/global_screenshot_preview"
+ android:layout_width="@dimen/global_screenshot_x_scale"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/screenshot_offset_x"
+ android:layout_marginBottom="@dimen/screenshot_offset_y"
+ android:scaleType="fitEnd"
+ android:elevation="@dimen/screenshot_preview_elevation"
+ android:visibility="gone"
+ android:background="@drawable/screenshot_rounded_corners"
+ android:adjustViewBounds="true"
+ android:contentDescription="@string/screenshot_preview_description"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_tile_detail_text.xml b/packages/SystemUI/res/layout/qs_tile_detail_text.xml
deleted file mode 100644
index bcbf826..0000000
--- a/packages/SystemUI/res/layout/qs_tile_detail_text.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<!-- use 'dp' instead of 'sp' as we do not want the text to increase
- if the user scales the font size -->
-<TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|center_horizontal"
- android:text="..."
- android:textSize="16dp"
- android:fontFamily="@*android:string/config_headlineFontFamily"
- android:singleLine="true"
- android:visibility="gone"
- android:paddingBottom="@dimen/qs_tile_detail_padding"
- android:clickable="false"
- android:focusable="false" />
-
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a2e11a7..a3d32c1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -314,7 +314,7 @@
<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">10dp</dimen>
+ <dimen name="screenshot_action_container_padding_vertical">16dp</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>
@@ -502,7 +502,6 @@
<dimen name="qs_page_indicator_width">16dp</dimen>
<dimen name="qs_page_indicator_height">8dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
- <dimen name="qs_tile_detail_padding">3dp</dimen>
<dimen name="qs_tile_text_size">12sp</dimen>
<dimen name="qs_tile_divider_height">1dp</dimen>
<dimen name="qs_panel_padding">16dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 76ca385..09918e7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -169,5 +169,8 @@
<item type="id" name="screen_recording_options" />
<item type="id" name="screen_recording_dialog_source_text" />
<item type="id" name="screen_recording_dialog_source_description" />
+
+ <item type="id" name="accessibility_action_controls_move_before" />
+ <item type="id" name="accessibility_action_controls_move_after" />
</resources>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index f35f351..b1e91c8 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -33,11 +33,6 @@
<!-- Maximum number of bubbles we allow in overflow before we dismiss the oldest one. -->
<integer name="bubbles_max_overflow">16</integer>
- <!-- Ratio of "left" end of status bar that will swipe to QQS. -->
- <integer name="qqs_split_fraction">3</integer>
- <!-- Ratio of "right" end of status bar that will swipe to QS. -->
- <integer name="qs_split_fraction">2</integer>
-
<integer name="magnification_default_scale">2</integer>
<!-- The position of the volume dialog on the screen.
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8c10f61..8bbcfa0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2711,6 +2711,8 @@
<string name="accessibility_control_change_favorite">favorite</string>
<!-- a11y action to unfavorite a control. It will read as "Double-tap to unfavorite" in screen readers [CHAR LIMIT=NONE] -->
<string name="accessibility_control_change_unfavorite">unfavorite</string>
+ <!-- a11y action to move a control to the position specified by the parameter [CHAR LIMIT=NONE] -->
+ <string name="accessibility_control_move">Move to position <xliff:g id="number" example="1">%d</xliff:g></string>
<!-- Controls management controls screen default title [CHAR LIMIT=30] -->
<string name="controls_favorite_default_title">Controls</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b461295..4f42370 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/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 1369350..5a78c90 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 @@
// 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 @@
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 @@
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 @@
* 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 8cd89dd..525e989 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.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 @@
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 @@
}
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 @@
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/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index f044389..f1b401e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -16,7 +16,6 @@
package com.android.systemui.bubbles;
-import static android.app.Notification.FLAG_BUBBLE;
import static android.os.AsyncTask.Status.FINISHED;
import static android.view.Display.INVALID_DISPLAY;
@@ -28,7 +27,6 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
@@ -57,11 +55,6 @@
class Bubble implements BubbleViewProvider {
private static final String TAG = "Bubble";
- /**
- * NotificationEntry associated with the bubble. A null value implies this bubble is loaded
- * from disk.
- */
- @Nullable
private NotificationEntry mEntry;
private final String mKey;
private final String mGroupId;
@@ -102,56 +95,18 @@
private Bitmap mBadgedImage;
private int mDotColor;
private Path mDotPath;
- private int mFlags;
- /**
- * Extract GroupId from {@link NotificationEntry}. See also {@link #groupId(ShortcutInfo)}.
- */
+
public static String groupId(NotificationEntry entry) {
UserHandle user = entry.getSbn().getUser();
return user.getIdentifier() + "|" + entry.getSbn().getPackageName();
}
- /**
- * Extract GroupId from {@link ShortcutInfo}. This should match the one generated from
- * {@link NotificationEntry}. See also {@link #groupId(NotificationEntry)}.
- */
- @NonNull
- public static String groupId(@NonNull final ShortcutInfo shortcutInfo) {
- return shortcutInfo.getUserId() + "|" + shortcutInfo.getPackage();
- }
-
- /**
- * Generate a unique identifier for this bubble based on given {@link NotificationEntry}. If
- * {@link ShortcutInfo} was found in the notification entry, the identifier would be generated
- * from {@link ShortcutInfo} instead. See also {@link #key(ShortcutInfo)}.
- */
- @NonNull
- public static String key(@NonNull final NotificationEntry entry) {
- final ShortcutInfo shortcutInfo = entry.getRanking().getShortcutInfo();
- if (shortcutInfo != null) return key(shortcutInfo);
- return entry.getKey();
- }
-
- /**
- * Generate a unique identifier for this bubble based on given {@link ShortcutInfo}.
- * See also {@link #key(NotificationEntry)}.
- */
- @NonNull
- public static String key(@NonNull final ShortcutInfo shortcutInfo) {
- return shortcutInfo.getUserId() + "|" + shortcutInfo.getPackage() + "|"
- + shortcutInfo.getId();
- }
-
- /**
- * Create a bubble with limited information based on given {@link ShortcutInfo}.
- * Note: Currently this is only being used when the bubble is persisted to disk.
- */
+ // TODO: Decouple Bubble from NotificationEntry and transform ShortcutInfo into Bubble
Bubble(ShortcutInfo shortcutInfo) {
mShortcutInfo = shortcutInfo;
- mKey = key(shortcutInfo);
- mGroupId = groupId(shortcutInfo);
- mFlags = 0;
+ mKey = shortcutInfo.getId();
+ mGroupId = shortcutInfo.getId();
}
/** Used in tests when no UI is required. */
@@ -159,11 +114,10 @@
Bubble(NotificationEntry e,
BubbleController.NotificationSuppressionChangedListener listener) {
mEntry = e;
- mKey = key(e);
+ mKey = e.getKey();
mLastUpdated = e.getSbn().getPostTime();
mGroupId = groupId(e);
mSuppressionListener = listener;
- mFlags = e.getSbn().getNotification().flags;
}
@Override
@@ -171,26 +125,16 @@
return mKey;
}
- @Nullable
public NotificationEntry getEntry() {
return mEntry;
}
- @Nullable
- public UserHandle getUser() {
- if (mEntry != null) return mEntry.getSbn().getUser();
- if (mShortcutInfo != null) return mShortcutInfo.getUserHandle();
- return null;
- }
-
public String getGroupId() {
return mGroupId;
}
public String getPackageName() {
- return mEntry == null
- ? mShortcutInfo == null ? null : mShortcutInfo.getPackage()
- : mEntry.getSbn().getPackageName();
+ return mEntry.getSbn().getPackageName();
}
@Override
@@ -274,8 +218,7 @@
void inflate(BubbleViewInfoTask.Callback callback,
Context context,
BubbleStackView stackView,
- BubbleIconFactory iconFactory,
- boolean skipInflation) {
+ BubbleIconFactory iconFactory) {
if (isBubbleLoading()) {
mInflationTask.cancel(true /* mayInterruptIfRunning */);
}
@@ -283,7 +226,6 @@
context,
stackView,
iconFactory,
- skipInflation,
callback);
if (mInflateSynchronously) {
mInflationTask.onPostExecute(mInflationTask.doInBackground());
@@ -403,7 +345,6 @@
* Whether this notification should be shown in the shade.
*/
boolean showInShade() {
- if (mEntry == null) return false;
return !shouldSuppressNotification() || !mEntry.isClearable();
}
@@ -411,8 +352,8 @@
* Sets whether this notification should be suppressed in the shade.
*/
void setSuppressNotification(boolean suppressNotification) {
- if (mEntry == null) return;
boolean prevShowInShade = showInShade();
+
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
int flags = data.getFlags();
if (suppressNotification) {
@@ -443,7 +384,6 @@
*/
@Override
public boolean showDot() {
- if (mEntry == null) return false;
return mShowBubbleUpdateDot
&& !mEntry.shouldSuppressNotificationDot()
&& !shouldSuppressNotification();
@@ -453,7 +393,6 @@
* Whether the flyout for the bubble should be shown.
*/
boolean showFlyout() {
- if (mEntry == null) return false;
return !mSuppressFlyout && !mEntry.shouldSuppressPeek()
&& !shouldSuppressNotification()
&& !mEntry.shouldSuppressNotificationList();
@@ -477,13 +416,11 @@
* is an ongoing bubble.
*/
boolean isOngoing() {
- if (mEntry == null) return false;
int flags = mEntry.getSbn().getNotification().flags;
return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
}
float getDesiredHeight(Context context) {
- if (mEntry == null) return 0;
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
boolean useRes = data.getDesiredHeightResId() != 0;
if (useRes) {
@@ -497,7 +434,6 @@
}
String getDesiredHeightString() {
- if (mEntry == null) return String.valueOf(0);
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
boolean useRes = data.getDesiredHeightResId() != 0;
if (useRes) {
@@ -514,13 +450,11 @@
* To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}.
*/
boolean usingShortcutInfo() {
- return mEntry != null && mEntry.getBubbleMetadata().getShortcutId() != null
- || mShortcutInfo != null;
+ return mEntry.getBubbleMetadata().getShortcutId() != null;
}
@Nullable
PendingIntent getBubbleIntent() {
- if (mEntry == null) return null;
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
if (data != null) {
return data.getIntent();
@@ -528,32 +462,16 @@
return null;
}
- Intent getSettingsIntent(final Context context) {
+ Intent getSettingsIntent() {
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
- final int uid = getUid(context);
- if (uid != -1) {
- intent.putExtra(Settings.EXTRA_APP_UID, uid);
- }
+ intent.putExtra(Settings.EXTRA_APP_UID, mEntry.getSbn().getUid());
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return intent;
}
- private int getUid(final Context context) {
- if (mEntry != null) return mEntry.getSbn().getUid();
- final PackageManager pm = context.getPackageManager();
- if (pm == null) return -1;
- try {
- final ApplicationInfo info = pm.getApplicationInfo(mShortcutInfo.getPackage(), 0);
- return info.uid;
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "cannot find uid", e);
- }
- return -1;
- }
-
private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) {
PackageManager pm = context.getPackageManager();
Resources r;
@@ -575,30 +493,15 @@
}
private boolean shouldSuppressNotification() {
- if (mEntry == null) return false;
return mEntry.getBubbleMetadata() != null
&& mEntry.getBubbleMetadata().isNotificationSuppressed();
}
boolean shouldAutoExpand() {
- if (mEntry == null) return false;
Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
return metadata != null && metadata.getAutoExpandBubble();
}
- public boolean isBubble() {
- if (mEntry == null) return (mFlags & FLAG_BUBBLE) != 0;
- return (mEntry.getSbn().getNotification().flags & FLAG_BUBBLE) != 0;
- }
-
- public void enable(int option) {
- mFlags |= option;
- }
-
- public void disable(int option) {
- mFlags &= ~option;
- }
-
@Override
public String toString() {
return "Bubble{" + mKey + '}';
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 8707d38..a578f33 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -42,7 +42,6 @@
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.INotificationManager;
@@ -243,7 +242,7 @@
* This can happen when an app cancels a bubbled notification or when the user dismisses a
* bubble.
*/
- void removeNotification(@NonNull NotificationEntry entry, int reason);
+ void removeNotification(NotificationEntry entry, int reason);
/**
* Called when a bubbled notification has changed whether it should be
@@ -259,7 +258,7 @@
* removes all remnants of the group's summary from the notification pipeline.
* TODO: (b/145659174) Only old pipeline needs this - delete post-migration.
*/
- void maybeCancelSummary(@NonNull NotificationEntry entry);
+ void maybeCancelSummary(NotificationEntry entry);
}
/**
@@ -482,7 +481,7 @@
addNotifCallback(new NotifCallback() {
@Override
- public void removeNotification(@NonNull final NotificationEntry entry, int reason) {
+ public void removeNotification(NotificationEntry entry, int reason) {
mNotificationEntryManager.performRemoveNotification(entry.getSbn(),
reason);
}
@@ -493,7 +492,7 @@
}
@Override
- public void maybeCancelSummary(@NonNull final NotificationEntry entry) {
+ public void maybeCancelSummary(NotificationEntry entry) {
// Check if removed bubble has an associated suppressed group summary that needs
// to be removed now.
final String groupKey = entry.getSbn().getGroupKey();
@@ -702,12 +701,10 @@
mBubbleIconFactory = new BubbleIconFactory(mContext);
// Reload each bubble
for (Bubble b: mBubbleData.getBubbles()) {
- b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory,
- false /* skipInflation */);
+ b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
}
for (Bubble b: mBubbleData.getOverflowBubbles()) {
- b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory,
- false /* skipInflation */);
+ b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
}
}
@@ -806,7 +803,7 @@
if (bubble != null) {
mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
}
- } else if (bubble.isBubble()) {
+ } else if (bubble.getEntry().isBubble()){
mBubbleData.setSelectedBubble(bubble);
}
mBubbleData.setExpanded(true);
@@ -835,33 +832,10 @@
updateBubble(notif, suppressFlyout, true /* showInShade */);
}
- /**
- * Fills the overflow bubbles by loading them from disk.
- */
- void loadOverflowBubblesFromDisk() {
- if (!mBubbleData.getOverflowBubbles().isEmpty()) {
- // we don't need to load overflow bubbles from disk if it is already in memory
- return;
- }
- mDataRepository.loadBubbles((bubbles) -> {
- bubbles.forEach(bubble -> {
- if (mBubbleData.getBubbles().contains(bubble)) {
- // if the bubble is already active, there's no need to push it to overflow
- return;
- }
- bubble.inflate((b) -> mBubbleData.overflowBubble(DISMISS_AGED, bubble),
- mContext, mStackView, mBubbleIconFactory, true /* skipInflation */);
- });
- return null;
- });
- }
-
void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
if (mStackView == null) {
// Lazy init stack view when a bubble is created
ensureStackViewCreated();
- // Lazy load overflow bubbles from disk
- loadOverflowBubblesFromDisk();
}
// If this is an interruptive notif, mark that it's interrupted
if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
@@ -881,11 +855,11 @@
return;
}
mHandler.post(
- () -> removeBubble(bubble.getKey(),
+ () -> removeBubble(bubble.getEntry(),
BubbleController.DISMISS_INVALID_INTENT));
});
},
- mContext, mStackView, mBubbleIconFactory, false /* skipInflation */);
+ mContext, mStackView, mBubbleIconFactory);
}
/**
@@ -897,10 +871,7 @@
* @param entry the notification to change bubble state for.
* @param shouldBubble whether the notification should show as a bubble or not.
*/
- public void onUserChangedBubble(@Nullable final NotificationEntry entry, boolean shouldBubble) {
- if (entry == null) {
- return;
- }
+ public void onUserChangedBubble(NotificationEntry entry, boolean shouldBubble) {
NotificationChannel channel = entry.getChannel();
final String appPkg = entry.getSbn().getPackageName();
final int appUid = entry.getSbn().getUid();
@@ -939,14 +910,14 @@
}
/**
- * Removes the bubble with the given key.
+ * Removes the bubble with the given NotificationEntry.
* <p>
* Must be called from the main thread.
*/
@MainThread
- void removeBubble(String key, int reason) {
- if (mBubbleData.hasAnyBubbleWithKey(key)) {
- mBubbleData.notificationEntryRemoved(key, reason);
+ void removeBubble(NotificationEntry entry, int reason) {
+ if (mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
+ mBubbleData.notificationEntryRemoved(entry, reason);
}
}
@@ -962,7 +933,7 @@
&& canLaunchInActivityView(mContext, entry);
if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
// It was previously a bubble but no longer a bubble -- lets remove it
- removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
+ removeBubble(entry, DISMISS_NO_LONGER_BUBBLE);
} else if (shouldBubble) {
updateBubble(entry);
}
@@ -976,10 +947,10 @@
// Remove any associated bubble children with the summary
final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
for (int i = 0; i < bubbleChildren.size(); i++) {
- removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED);
+ removeBubble(bubbleChildren.get(i).getEntry(), DISMISS_GROUP_CANCELLED);
}
} else {
- removeBubble(entry.getKey(), DISMISS_NOTIF_CANCEL);
+ removeBubble(entry, DISMISS_NOTIF_CANCEL);
}
}
@@ -1001,8 +972,7 @@
rankingMap.getRanking(key, mTmpRanking);
boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
if (isActiveBubble && !mTmpRanking.canBubble()) {
- mBubbleData.notificationEntryRemoved(entry.getKey(),
- BubbleController.DISMISS_BLOCKED);
+ mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED);
} else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
entry.setFlagBubble(true);
onEntryUpdated(entry);
@@ -1012,15 +982,9 @@
private void setIsBubble(Bubble b, boolean isBubble) {
if (isBubble) {
- if (b.getEntry() != null) {
- b.getEntry().getSbn().getNotification().flags |= FLAG_BUBBLE;
- }
- b.enable(FLAG_BUBBLE);
+ b.getEntry().getSbn().getNotification().flags |= FLAG_BUBBLE;
} else {
- if (b.getEntry() != null) {
- b.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
- }
- b.disable(FLAG_BUBBLE);
+ b.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
}
try {
mBarService.onNotificationBubbleChanged(b.getKey(), isBubble, 0);
@@ -1068,27 +1032,23 @@
// The bubble is now gone & the notification is hidden from the shade, so
// time to actually remove it
for (NotifCallback cb : mCallbacks) {
- if (bubble.getEntry() != null) {
- cb.removeNotification(bubble.getEntry(), REASON_CANCEL);
- }
+ cb.removeNotification(bubble.getEntry(), REASON_CANCEL);
}
} else {
- if (bubble.isBubble() && bubble.showInShade()) {
+ if (bubble.getEntry().isBubble() && bubble.showInShade()) {
setIsBubble(bubble, /* isBubble */ false);
}
- if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) {
+ if (bubble.getEntry().getRow() != null) {
bubble.getEntry().getRow().updateBubbleButton();
}
}
}
- if (bubble.getEntry() != null) {
- final String groupKey = bubble.getEntry().getSbn().getGroupKey();
- if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
- // Time to potentially remove the summary
- for (NotifCallback cb : mCallbacks) {
- cb.maybeCancelSummary(bubble.getEntry());
- }
+ final String groupKey = bubble.getEntry().getSbn().getGroupKey();
+ if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
+ // Time to potentially remove the summary
+ for (NotifCallback cb : mCallbacks) {
+ cb.maybeCancelSummary(bubble.getEntry());
}
}
}
@@ -1113,7 +1073,7 @@
if (update.selectionChanged) {
mStackView.setSelectedBubble(update.selectedBubble);
- if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) {
+ if (update.selectedBubble != null) {
mNotificationGroupManager.updateSuppression(
update.selectedBubble.getEntry());
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 7b323ce..65d5beb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -23,7 +23,6 @@
import static java.util.stream.Collectors.toList;
-import android.annotation.NonNull;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
@@ -224,7 +223,7 @@
false, /* showInShade */ true);
setSelectedBubble(bubble);
},
- mContext, stack, factory, false /* skipInflation */);
+ mContext, stack, factory);
dispatchPendingChanges();
}
@@ -279,8 +278,7 @@
}
mPendingBubbles.remove(bubble); // No longer pending once we're here
Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
- suppressFlyout |= bubble.getEntry() == null
- || !bubble.getEntry().getRanking().visuallyInterruptive();
+ suppressFlyout |= !bubble.getEntry().getRanking().visuallyInterruptive();
if (prevBubble == null) {
// Create a new bubble
@@ -309,14 +307,11 @@
dispatchPendingChanges();
}
- /**
- * Called when a notification associated with a bubble is removed.
- */
- public void notificationEntryRemoved(String key, @DismissReason int reason) {
+ public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason);
+ Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
}
- doRemove(key, reason);
+ doRemove(entry.getKey(), reason);
dispatchPendingChanges();
}
@@ -364,7 +359,7 @@
return bubbleChildren;
}
for (Bubble b : mBubbles) {
- if (b.getEntry() != null && groupKey.equals(b.getEntry().getSbn().getGroupKey())) {
+ if (groupKey.equals(b.getEntry().getSbn().getGroupKey())) {
bubbleChildren.add(b);
}
}
@@ -475,9 +470,7 @@
Bubble newSelected = mBubbles.get(newIndex);
setSelectedBubbleInternal(newSelected);
}
- if (bubbleToRemove.getEntry() != null) {
- maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
- }
+ maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
}
void overflowBubble(@DismissReason int reason, Bubble bubble) {
@@ -751,8 +744,7 @@
return true;
}
- private void maybeSendDeleteIntent(@DismissReason int reason,
- @NonNull final NotificationEntry entry) {
+ private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) {
if (reason == BubbleController.DISMISS_USER_GESTURE) {
Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata();
PendingIntent deleteIntent = bubbleMetadata != null
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index 6df3132..ba93f41 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -74,10 +74,7 @@
private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> {
return bubbles.mapNotNull { b ->
- var shortcutId = b.shortcutInfo?.id
- if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId
- if (shortcutId == null) shortcutId = b.entry?.ranking?.shortcutInfo?.id
- if (shortcutId == null) return@mapNotNull null
+ val shortcutId = b.shortcutInfo?.id ?: return@mapNotNull null
BubbleEntity(userId, b.packageName, shortcutId)
}
}
@@ -111,6 +108,7 @@
/**
* Load bubbles from disk.
*/
+ // TODO: call this method from BubbleController and update UI
@SuppressLint("WrongConstant")
fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 0a1a0f7..baf92dc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -65,6 +65,7 @@
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
import com.android.systemui.statusbar.AlphaOptimizedButton;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Container for the expanded bubble view, handles rendering the caret and settings icon.
@@ -160,7 +161,7 @@
// the bubble again so we'll just remove it.
Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ ", " + e.getMessage() + "; removing bubble");
- mBubbleController.removeBubble(getBubbleKey(),
+ mBubbleController.removeBubble(getBubbleEntry(),
BubbleController.DISMISS_INVALID_INTENT);
}
});
@@ -204,7 +205,7 @@
}
if (mBubble != null) {
// Must post because this is called from a binder thread.
- post(() -> mBubbleController.removeBubble(mBubble.getKey(),
+ post(() -> mBubbleController.removeBubble(mBubble.getEntry(),
BubbleController.DISMISS_TASK_FINISHED));
}
}
@@ -291,6 +292,10 @@
return mBubble != null ? mBubble.getKey() : "null";
}
+ private NotificationEntry getBubbleEntry() {
+ return mBubble != null ? mBubble.getEntry() : null;
+ }
+
void setManageClickListener(OnClickListener manageClickListener) {
findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index e35b203..88f5eb0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -49,6 +49,7 @@
import android.graphics.Region;
import android.os.Bundle;
import android.os.Vibrator;
+import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.Choreographer;
import android.view.DisplayCutout;
@@ -918,10 +919,10 @@
showManageMenu(false /* show */);
final Bubble bubble = mBubbleData.getSelectedBubble();
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
- final Intent intent = bubble.getSettingsIntent(mContext);
+ final Intent intent = bubble.getSettingsIntent();
collapseStack(() -> {
-
- mContext.startActivityAsUser(intent, bubble.getUser());
+ mContext.startActivityAsUser(
+ intent, bubble.getEntry().getSbn().getUser());
logBubbleClickEvent(
bubble,
SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
@@ -1178,19 +1179,13 @@
for (int i = 0; i < mBubbleData.getBubbles().size(); i++) {
final Bubble bubble = mBubbleData.getBubbles().get(i);
final String appName = bubble.getAppName();
+ final Notification notification = bubble.getEntry().getSbn().getNotification();
+ final CharSequence titleCharSeq =
+ notification.extras.getCharSequence(Notification.EXTRA_TITLE);
- final CharSequence titleCharSeq;
- if (bubble.getEntry() == null) {
- titleCharSeq = null;
- } else {
- titleCharSeq = bubble.getEntry().getSbn().getNotification().extras.getCharSequence(
- Notification.EXTRA_TITLE);
- }
- final String titleStr;
+ String titleStr = getResources().getString(R.string.notification_bubble_title);
if (titleCharSeq != null) {
titleStr = titleCharSeq.toString();
- } else {
- titleStr = getResources().getString(R.string.notification_bubble_title);
}
if (bubble.getIconView() != null) {
@@ -1803,7 +1798,7 @@
private void dismissBubbleIfExists(@Nullable Bubble bubble) {
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
mBubbleData.notificationEntryRemoved(
- bubble.getKey(), BubbleController.DISMISS_USER_GESTURE);
+ bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
}
}
@@ -2301,12 +2296,18 @@
* @param action the user interaction enum.
*/
private void logBubbleClickEvent(Bubble bubble, int action) {
- bubble.logUIEvent(
+ StatusBarNotification notification = bubble.getEntry().getSbn();
+ SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
+ notification.getPackageName(),
+ notification.getNotification().getChannelId(),
+ notification.getId(),
+ getBubbleIndex(getExpandedBubble()),
getBubbleCount(),
action,
getNormalizedXPosition(),
getNormalizedYPosition(),
- getBubbleIndex(getExpandedBubble())
- );
+ bubble.showInShade(),
+ bubble.isOngoing(),
+ false /* isAppForeground (unused) */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index 525d5b5..8a57a73 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -37,7 +37,6 @@
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.Parcelable;
-import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.Log;
@@ -75,7 +74,6 @@
private WeakReference<Context> mContext;
private WeakReference<BubbleStackView> mStackView;
private BubbleIconFactory mIconFactory;
- private boolean mSkipInflation;
private Callback mCallback;
/**
@@ -86,20 +84,17 @@
Context context,
BubbleStackView stackView,
BubbleIconFactory factory,
- boolean skipInflation,
Callback c) {
mBubble = b;
mContext = new WeakReference<>(context);
mStackView = new WeakReference<>(stackView);
mIconFactory = factory;
- mSkipInflation = skipInflation;
mCallback = c;
}
@Override
protected BubbleViewInfo doInBackground(Void... voids) {
- return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble,
- mSkipInflation);
+ return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble);
}
@Override
@@ -128,36 +123,11 @@
@Nullable
static BubbleViewInfo populate(Context c, BubbleStackView stackView,
- BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) {
- final NotificationEntry entry = b.getEntry();
- if (entry == null) {
- // populate from ShortcutInfo when NotificationEntry is not available
- final ShortcutInfo s = b.getShortcutInfo();
- return populate(c, stackView, iconFactory, skipInflation || b.isInflated(),
- s.getPackage(), s.getUserHandle(), s, null);
- }
- final StatusBarNotification sbn = entry.getSbn();
- final String bubbleShortcutId = entry.getBubbleMetadata().getShortcutId();
- final ShortcutInfo si = bubbleShortcutId == null
- ? null : entry.getRanking().getShortcutInfo();
- return populate(
- c, stackView, iconFactory, skipInflation || b.isInflated(),
- sbn.getPackageName(), sbn.getUser(), si, entry);
- }
-
- private static BubbleViewInfo populate(
- @NonNull final Context c,
- @NonNull final BubbleStackView stackView,
- @NonNull final BubbleIconFactory iconFactory,
- final boolean isInflated,
- @NonNull final String packageName,
- @NonNull final UserHandle user,
- @Nullable final ShortcutInfo shortcutInfo,
- @Nullable final NotificationEntry entry) {
+ BubbleIconFactory iconFactory, Bubble b) {
BubbleViewInfo info = new BubbleViewInfo();
// View inflation: only should do this once per bubble
- if (!isInflated) {
+ if (!b.isInflated()) {
LayoutInflater inflater = LayoutInflater.from(c);
info.imageView = (BadgedImageView) inflater.inflate(
R.layout.bubble_view, stackView, false /* attachToRoot */);
@@ -167,8 +137,12 @@
info.expandedView.setStackView(stackView);
}
- if (shortcutInfo != null) {
- info.shortcutInfo = shortcutInfo;
+ StatusBarNotification sbn = b.getEntry().getSbn();
+ String packageName = sbn.getPackageName();
+
+ String bubbleShortcutId = b.getEntry().getBubbleMetadata().getShortcutId();
+ if (bubbleShortcutId != null) {
+ info.shortcutInfo = b.getEntry().getRanking().getShortcutInfo();
}
// App name & app icon
@@ -187,7 +161,7 @@
info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
}
appIcon = pm.getApplicationIcon(packageName);
- badgedIcon = pm.getUserBadgedIcon(appIcon, user);
+ badgedIcon = pm.getUserBadgedIcon(appIcon, sbn.getUser());
} catch (PackageManager.NameNotFoundException exception) {
// If we can't find package... don't think we should show the bubble.
Log.w(TAG, "Unable to find package: " + packageName);
@@ -196,7 +170,7 @@
// Badged bubble image
Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
- entry == null ? null : entry.getBubbleMetadata());
+ b.getEntry().getBubbleMetadata());
if (bubbleDrawable == null) {
// Default to app icon
bubbleDrawable = appIcon;
@@ -222,9 +196,7 @@
Color.WHITE, WHITE_SCRIM_ALPHA);
// Flyout
- if (entry != null) {
- info.flyoutMessage = extractFlyoutMessage(c, entry);
- }
+ info.flyoutMessage = extractFlyoutMessage(c, b.getEntry());
return info;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
index 175ed06..00a406e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
@@ -48,6 +48,8 @@
private var modified = false
+ override val moveHelper = null
+
override val favorites: List<ControlInfo>
get() = favoriteIds.mapNotNull { id ->
val control = controls.firstOrNull { it.control.controlId == id }?.control
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index 4b283d6..2f91710 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -18,6 +18,7 @@
import android.content.ComponentName
import android.graphics.Rect
+import android.os.Bundle
import android.service.controls.Control
import android.service.controls.DeviceTypes
import android.view.LayoutInflater
@@ -78,7 +79,7 @@
background = parent.context.getDrawable(
R.drawable.control_background_ripple)
},
- model is FavoritesModel // Indicates that position information is needed
+ model?.moveHelper // Indicates that position information is needed
) { id, favorite ->
model?.changeFavoriteStatus(id, favorite)
}
@@ -176,12 +177,14 @@
/**
* Holder for using with [ControlStatusWrapper] to display names of zones.
+ * @param moveHelper a helper interface to facilitate a11y rearranging. Null indicates no
+ * rearranging
* @param favoriteCallback this callback will be called whenever the favorite state of the
* [Control] this view represents changes.
*/
internal class ControlHolder(
view: View,
- val withPosition: Boolean,
+ val moveHelper: ControlsModel.MoveHelper?,
val favoriteCallback: ModelFavoriteChanger
) : Holder(view) {
private val favoriteStateDescription =
@@ -197,7 +200,11 @@
visibility = View.VISIBLE
}
- private val accessibilityDelegate = ControlHolderAccessibilityDelegate(this::stateDescription)
+ private val accessibilityDelegate = ControlHolderAccessibilityDelegate(
+ this::stateDescription,
+ this::getLayoutPosition,
+ moveHelper
+ )
init {
ViewCompat.setAccessibilityDelegate(itemView, accessibilityDelegate)
@@ -207,7 +214,7 @@
private fun stateDescription(favorite: Boolean): CharSequence? {
if (!favorite) {
return notFavoriteStateDescription
- } else if (!withPosition) {
+ } else if (moveHelper == null) {
return favoriteStateDescription
} else {
val position = layoutPosition + 1
@@ -256,15 +263,67 @@
}
}
+/**
+ * Accessibility delegate for [ControlHolder].
+ *
+ * Provides the following functionality:
+ * * Sets the state description indicating whether the controls is Favorited or Unfavorited
+ * * Adds the position to the state description if necessary.
+ * * Adds context action for moving (rearranging) a control.
+ *
+ * @param stateRetriever function to determine the state description based on the favorite state
+ * @param positionRetriever function to obtain the position of this control. It only has to be
+ * correct in controls that are currently favorites (and therefore can
+ * be moved).
+ * @param moveHelper helper interface to determine if a control can be moved and actually move it.
+ */
private class ControlHolderAccessibilityDelegate(
- val stateRetriever: (Boolean) -> CharSequence?
+ val stateRetriever: (Boolean) -> CharSequence?,
+ val positionRetriever: () -> Int,
+ val moveHelper: ControlsModel.MoveHelper?
) : AccessibilityDelegateCompat() {
var isFavorite = false
+ companion object {
+ private val MOVE_BEFORE_ID = R.id.accessibility_action_controls_move_before
+ private val MOVE_AFTER_ID = R.id.accessibility_action_controls_move_after
+ }
+
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
+ info.isContextClickable = false
+ addClickAction(host, info)
+ maybeAddMoveBeforeAction(host, info)
+ maybeAddMoveAfterAction(host, info)
+
+ // Determine the stateDescription based on the holder information
+ info.stateDescription = stateRetriever(isFavorite)
+ // Remove the information at the end indicating row and column.
+ info.setCollectionItemInfo(null)
+
+ info.className = Switch::class.java.name
+ }
+
+ override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ if (super.performAccessibilityAction(host, action, args)) {
+ return true
+ }
+ return when (action) {
+ MOVE_BEFORE_ID -> {
+ moveHelper?.moveBefore(positionRetriever())
+ true
+ }
+ MOVE_AFTER_ID -> {
+ moveHelper?.moveAfter(positionRetriever())
+ true
+ }
+ else -> false
+ }
+ }
+
+ private fun addClickAction(host: View, info: AccessibilityNodeInfoCompat) {
// Change the text for the double-tap action
val clickActionString = if (isFavorite) {
host.context.getString(R.string.accessibility_control_change_unfavorite)
@@ -276,13 +335,30 @@
// “favorite/unfavorite”
clickActionString)
info.addAction(click)
+ }
- // Determine the stateDescription based on the holder information
- info.stateDescription = stateRetriever(isFavorite)
- // Remove the information at the end indicating row and column.
- info.setCollectionItemInfo(null)
+ private fun maybeAddMoveBeforeAction(host: View, info: AccessibilityNodeInfoCompat) {
+ if (moveHelper?.canMoveBefore(positionRetriever()) ?: false) {
+ val newPosition = positionRetriever() + 1 - 1
+ val moveBefore = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ MOVE_BEFORE_ID,
+ host.context.getString(R.string.accessibility_control_move, newPosition)
+ )
+ info.addAction(moveBefore)
+ info.isContextClickable = true
+ }
+ }
- info.className = Switch::class.java.name
+ private fun maybeAddMoveAfterAction(host: View, info: AccessibilityNodeInfoCompat) {
+ if (moveHelper?.canMoveAfter(positionRetriever()) ?: false) {
+ val newPosition = positionRetriever() + 1 + 1
+ val moveAfter = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ MOVE_AFTER_ID,
+ host.context.getString(R.string.accessibility_control_move, newPosition)
+ )
+ info.addAction(moveAfter)
+ info.isContextClickable = true
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
index 37b6d15..2543953 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
@@ -42,6 +42,8 @@
*/
val elements: List<ElementWrapper>
+ val moveHelper: MoveHelper?
+
/**
* Change the favorite status of a particular control.
*/
@@ -69,6 +71,34 @@
*/
fun onFirstChange()
}
+
+ /**
+ * Interface to facilitate moving controls from an [AccessibilityDelegate].
+ *
+ * All positions should be 0 based.
+ */
+ interface MoveHelper {
+
+ /**
+ * Whether the control in `position` can be moved to the position before it.
+ */
+ fun canMoveBefore(position: Int): Boolean
+
+ /**
+ * Whether the control in `position` can be moved to the position after it.
+ */
+ fun canMoveAfter(position: Int): Boolean
+
+ /**
+ * Move the control in `position` to the position before it.
+ */
+ fun moveBefore(position: Int)
+
+ /**
+ * Move the control in `position` to the position after it.
+ */
+ fun moveAfter(position: Int)
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
index 411170cb..5242501 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.controls.management
import android.content.ComponentName
+import android.util.Log
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.controls.ControlInterface
@@ -39,9 +40,39 @@
private val favoritesModelCallback: FavoritesModelCallback
) : ControlsModel {
+ companion object {
+ private const val TAG = "FavoritesModel"
+ }
+
private var adapter: RecyclerView.Adapter<*>? = null
private var modified = false
+ override val moveHelper = object : ControlsModel.MoveHelper {
+ override fun canMoveBefore(position: Int): Boolean {
+ return position > 0 && position < dividerPosition
+ }
+
+ override fun canMoveAfter(position: Int): Boolean {
+ return position >= 0 && position < dividerPosition - 1
+ }
+
+ override fun moveBefore(position: Int) {
+ if (!canMoveBefore(position)) {
+ Log.w(TAG, "Cannot move position $position before")
+ } else {
+ onMoveItem(position, position - 1)
+ }
+ }
+
+ override fun moveAfter(position: Int) {
+ if (!canMoveAfter(position)) {
+ Log.w(TAG, "Cannot move position $position after")
+ } else {
+ onMoveItem(position, position + 1)
+ }
+ }
+ }
+
override fun attachAdapter(adapter: RecyclerView.Adapter<*>) {
this.adapter = adapter
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 17e4234..f979bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -18,10 +18,15 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
import android.animation.ValueAnimator
+import android.annotation.ColorRes
import android.app.Dialog
import android.content.Context
+import android.content.res.ColorStateList
import android.graphics.drawable.ClipDrawable
+import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
@@ -34,6 +39,7 @@
import android.service.controls.templates.ToggleRangeTemplate
import android.service.controls.templates.ToggleTemplate
import android.util.MathUtils
+import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
@@ -63,6 +69,8 @@
private const val UPDATE_DELAY_IN_MILLIS = 3000L
private const val ALPHA_ENABLED = 255
private const val ALPHA_DISABLED = 0
+ private const val STATUS_ALPHA_ENABLED = 1f
+ private const val STATUS_ALPHA_DIMMED = 0.45f
private val FORCE_PANEL_DEVICES = setOf(
DeviceTypes.TYPE_THERMOSTAT,
DeviceTypes.TYPE_CAMERA
@@ -94,9 +102,11 @@
private val toggleBackgroundIntensity: Float = layout.context.resources
.getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1)
private var stateAnimator: ValueAnimator? = null
+ private var statusAnimator: Animator? = null
private val baseLayer: GradientDrawable
val icon: ImageView = layout.requireViewById(R.id.icon)
- val status: TextView = layout.requireViewById(R.id.status)
+ private val status: TextView = layout.requireViewById(R.id.status)
+ private var nextStatusText: CharSequence = ""
val title: TextView = layout.requireViewById(R.id.title)
val subtitle: TextView = layout.requireViewById(R.id.subtitle)
val context: Context = layout.getContext()
@@ -105,6 +115,7 @@
var cancelUpdate: Runnable? = null
var behavior: Behavior? = null
var lastAction: ControlAction? = null
+ var isLoading = false
private var lastChallengeDialog: Dialog? = null
private val onDialogCancel: () -> Unit = { lastChallengeDialog = null }
@@ -144,6 +155,7 @@
})
}
+ isLoading = false
behavior = bindBehavior(behavior, findBehaviorClass(controlStatus, template, deviceType))
updateContentDescription()
}
@@ -189,11 +201,11 @@
val previousText = status.getText()
cancelUpdate = uiExecutor.executeDelayed({
- status.setText(previousText)
- updateContentDescription()
- }, UPDATE_DELAY_IN_MILLIS)
+ setStatusText(previousText)
+ updateContentDescription()
+ }, UPDATE_DELAY_IN_MILLIS)
- status.setText(tempStatus)
+ setStatusText(tempStatus)
updateContentDescription()
}
@@ -231,18 +243,50 @@
}
internal fun applyRenderInfo(enabled: Boolean, offset: Int, animated: Boolean = true) {
- setEnabled(enabled)
-
val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset)
-
val fg = context.resources.getColorStateList(ri.foreground, context.theme)
+ val newText = nextStatusText
+ nextStatusText = ""
+ val control = cws.control
+
+ var shouldAnimate = animated
+ if (newText == status.text) {
+ shouldAnimate = false
+ }
+ animateStatusChange(shouldAnimate) {
+ updateStatusRow(enabled, newText, ri.icon, fg, control)
+ }
+
+ animateBackgroundChange(shouldAnimate, enabled, ri.enabledBackground)
+ }
+
+ fun getStatusText() = status.text
+
+ fun setStatusTextSize(textSize: Float) =
+ status.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
+
+ fun setStatusText(text: CharSequence, immediately: Boolean = false) {
+ if (immediately) {
+ status.alpha = STATUS_ALPHA_ENABLED
+ status.text = text
+ nextStatusText = ""
+ } else {
+ nextStatusText = text
+ }
+ }
+
+ private fun animateBackgroundChange(
+ animated: Boolean,
+ enabled: Boolean,
+ @ColorRes bgColor: Int
+ ) {
val bg = context.resources.getColor(R.color.control_default_background, context.theme)
var (newClipColor, newAlpha) = if (enabled) {
// allow color overrides for the enabled state only
val color = cws.control?.getCustomColor()?.let {
val state = intArrayOf(android.R.attr.state_enabled)
it.getColorForState(state, it.getDefaultColor())
- } ?: context.resources.getColor(ri.enabledBackground, context.theme)
+ } ?: context.resources.getColor(bgColor, context.theme)
listOf(color, ALPHA_ENABLED)
} else {
listOf(
@@ -251,21 +295,6 @@
)
}
- status.setTextColor(fg)
-
- cws.control?.getCustomIcon()?.let {
- // do not tint custom icons, assume the intended icon color is correct
- icon.imageTintList = null
- icon.setImageIcon(it)
- } ?: run {
- icon.setImageDrawable(ri.icon)
-
- // do not color app icons
- if (deviceType != DeviceTypes.TYPE_ROUTINE) {
- icon.imageTintList = fg
- }
- }
-
(clipLayer.getDrawable() as GradientDrawable).apply {
val newBaseColor = if (behavior is ToggleRangeBehavior) {
ColorUtils.blendARGB(bg, newClipColor, toggleBackgroundIntensity)
@@ -303,6 +332,77 @@
}
}
+ private fun animateStatusChange(animated: Boolean, statusRowUpdater: () -> Unit) {
+ statusAnimator?.cancel()
+
+ if (!animated) {
+ statusRowUpdater.invoke()
+ return
+ }
+
+ if (isLoading) {
+ statusRowUpdater.invoke()
+ statusAnimator = ObjectAnimator.ofFloat(status, "alpha", STATUS_ALPHA_DIMMED).apply {
+ repeatMode = ValueAnimator.REVERSE
+ repeatCount = ValueAnimator.INFINITE
+ duration = 500L
+ interpolator = Interpolators.LINEAR
+ startDelay = 900L
+ start()
+ }
+ } else {
+ val fadeOut = ObjectAnimator.ofFloat(status, "alpha", 0f).apply {
+ duration = 200L
+ interpolator = Interpolators.LINEAR
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ statusRowUpdater.invoke()
+ }
+ })
+ }
+ val fadeIn = ObjectAnimator.ofFloat(status, "alpha", STATUS_ALPHA_ENABLED).apply {
+ duration = 200L
+ interpolator = Interpolators.LINEAR
+ }
+ statusAnimator = AnimatorSet().apply {
+ playSequentially(fadeOut, fadeIn)
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ status.alpha = STATUS_ALPHA_ENABLED
+ statusAnimator = null
+ }
+ })
+ start()
+ }
+ }
+ }
+
+ private fun updateStatusRow(
+ enabled: Boolean,
+ text: CharSequence,
+ drawable: Drawable,
+ color: ColorStateList,
+ control: Control?
+ ) {
+ setEnabled(enabled)
+
+ status.text = text
+ status.setTextColor(color)
+
+ control?.getCustomIcon()?.let {
+ // do not tint custom icons, assume the intended icon color is correct
+ icon.imageTintList = null
+ icon.setImageIcon(it)
+ } ?: run {
+ icon.setImageDrawable(drawable)
+
+ // do not color app icons
+ if (deviceType != DeviceTypes.TYPE_ROUTINE) {
+ icon.imageTintList = color
+ }
+ }
+ }
+
private fun setEnabled(enabled: Boolean) {
status.setEnabled(enabled)
icon.setEnabled(enabled)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
index 722ade9..0c8e3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
@@ -24,7 +24,7 @@
}
override fun bind(cws: ControlWithState, colorOffset: Int) {
- cvh.status.setText(cws.control?.getStatusText() ?: "")
+ cvh.setStatusText(cws.control?.getStatusText() ?: "")
cvh.applyRenderInfo(false, colorOffset)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
index 124df32..ba331f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
@@ -97,6 +97,8 @@
private const val THERMOSTAT_RANGE = DeviceTypes.TYPE_THERMOSTAT * BUCKET_SIZE
private val deviceColorMap = mapOf<Int, Pair<Int, Int>>(
+ (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_OFF) to
+ Pair(R.color.control_default_foreground, R.color.control_default_background),
(THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to
Pair(R.color.thermo_heat_foreground, R.color.control_enabled_thermo_heat_background),
(THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_COOL) to
@@ -108,13 +110,9 @@
}
private val deviceIconMap = mapOf<Int, IconState>(
- THERMOSTAT_RANGE to IconState(
- R.drawable.ic_device_thermostat_off,
- R.drawable.ic_device_thermostat_on
- ),
(THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_OFF) to IconState(
R.drawable.ic_device_thermostat_off,
- R.drawable.ic_device_thermostat_on
+ R.drawable.ic_device_thermostat_off
),
(THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to IconState(
R.drawable.ic_device_thermostat_off,
@@ -130,7 +128,7 @@
),
(THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_ECO) to IconState(
R.drawable.ic_device_thermostat_off,
- R.drawable.ic_device_thermostat_on
+ R.drawable.ic_device_thermostat_off
),
DeviceTypes.TYPE_THERMOSTAT to IconState(
R.drawable.ic_device_thermostat_off,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
index d8dceba..bf3835d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
@@ -32,9 +32,12 @@
val msg = when (status) {
Control.STATUS_ERROR -> R.string.controls_error_generic
Control.STATUS_NOT_FOUND -> R.string.controls_error_removed
- else -> com.android.internal.R.string.loading
+ else -> {
+ cvh.isLoading = true
+ com.android.internal.R.string.loading
+ }
}
- cvh.status.setText(cvh.context.getString(msg))
+ cvh.setStatusText(cvh.context.getString(msg))
cvh.applyRenderInfo(false, colorOffset)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
index 2795c7a..a7dc09b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
@@ -39,7 +39,7 @@
override fun bind(cws: ControlWithState, colorOffset: Int) {
this.control = cws.control!!
- cvh.status.setText(control.getStatusText())
+ cvh.setStatusText(control.getStatusText())
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
index c432c09..dc7247c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
@@ -34,7 +34,6 @@
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
- cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
cvh.layout.setOnClickListener(View.OnClickListener() {
cvh.controlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked())
@@ -44,7 +43,7 @@
override fun bind(cws: ControlWithState, colorOffset: Int) {
this.control = cws.control!!
- cvh.status.setText(control.getStatusText())
+ cvh.setStatusText(control.getStatusText())
val controlTemplate = control.getControlTemplate()
template = when (controlTemplate) {
is ToggleTemplate -> controlTemplate
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index a09ed09..1f0ca9b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -31,7 +31,6 @@
import android.service.controls.templates.ToggleRangeTemplate
import android.util.Log
import android.util.MathUtils
-import android.util.TypedValue
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
@@ -39,7 +38,6 @@
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
-import android.widget.TextView
import com.android.systemui.Interpolators
import com.android.systemui.R
import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
@@ -57,7 +55,6 @@
lateinit var control: Control
lateinit var cvh: ControlViewHolder
lateinit var rangeTemplate: RangeTemplate
- lateinit var status: TextView
lateinit var context: Context
var currentStatusText: CharSequence = ""
var currentRangeValue: String = ""
@@ -71,10 +68,7 @@
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
- status = cvh.status
- context = status.getContext()
-
- cvh.applyRenderInfo(false /* enabled */, colorOffset, false /* animated */)
+ context = cvh.context
val gestureListener = ToggleRangeGestureListener(cvh.layout)
val gestureDetector = GestureDetector(context, gestureListener)
@@ -131,7 +125,6 @@
this.colorOffset = colorOffset
currentStatusText = control.getStatusText()
- status.setText(currentStatusText)
// ControlViewHolder sets a long click listener, but we want to handle touch in
// here instead, otherwise we'll have state conflicts.
@@ -222,7 +215,7 @@
}
fun beginUpdateRange() {
- status.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources()
+ cvh.setStatusTextSize(context.getResources()
.getDimensionPixelSize(R.dimen.control_status_expanded).toFloat())
}
@@ -261,14 +254,13 @@
val newValue = levelToRangeValue(newLevel)
currentRangeValue = format(rangeTemplate.getFormatString().toString(),
DEFAULT_FORMAT, newValue)
- val text = if (isDragging) {
- currentRangeValue
+ if (isDragging) {
+ cvh.setStatusText(currentRangeValue, /* immediately */ true)
} else {
- "$currentStatusText $currentRangeValue"
+ cvh.setStatusText("$currentStatusText $currentRangeValue")
}
- status.setText(text)
} else {
- status.setText(currentStatusText)
+ cvh.setStatusText(currentStatusText)
}
}
@@ -296,9 +288,9 @@
}
fun endUpdateRange() {
- status.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources()
+ cvh.setStatusTextSize(context.getResources()
.getDimensionPixelSize(R.dimen.control_status_normal).toFloat())
- status.setText("$currentStatusText $currentRangeValue")
+ cvh.setStatusText("$currentStatusText $currentRangeValue", /* immediately */ true)
cvh.action(FloatAction(rangeTemplate.getTemplateId(),
findNearestStep(levelToRangeValue(clipLayer.getLevel()))))
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
index 8ce2e61..48f9458 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
@@ -23,6 +23,7 @@
import android.service.controls.templates.ControlTemplate
import com.android.systemui.R
+import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
/**
@@ -37,7 +38,6 @@
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
- cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
cvh.layout.setOnClickListener(View.OnClickListener() {
cvh.controlActionCoordinator.touch(cvh, template.getTemplateId(), control)
@@ -46,13 +46,14 @@
override fun bind(cws: ControlWithState, colorOffset: Int) {
this.control = cws.control!!
- cvh.status.setText(control.getStatusText())
+ cvh.setStatusText(control.getStatusText())
template = control.getControlTemplate()
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
- clipLayer.setLevel(MIN_LEVEL)
- cvh.applyRenderInfo(false, colorOffset)
+ val enabled = if (colorOffset > 0) true else false
+ clipLayer.setLevel(if (enabled) MAX_LEVEL else MIN_LEVEL)
+ cvh.applyRenderInfo(enabled, colorOffset)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 82ccb17..e2a6d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -21,7 +21,6 @@
import com.android.systemui.appops.AppOpsControllerImpl;
import com.android.systemui.classifier.FalsingManagerProxy;
import com.android.systemui.controls.dagger.ControlsModule;
-import com.android.systemui.doze.DozeHost;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.globalactions.GlobalActionsImpl;
import com.android.systemui.plugins.ActivityStarter;
@@ -38,7 +37,6 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
-import com.android.systemui.statusbar.phone.DozeServiceHost;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -259,11 +257,6 @@
/**
*/
@Binds
- public abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
-
- /**
- */
- @Binds
public abstract VolumeComponent provideVolumeComponent(
VolumeDialogComponent volumeDialogComponent);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 6d1bf72..3bb953a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -31,6 +31,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
+import com.android.systemui.doze.DozeHost;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
@@ -43,6 +44,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
@@ -148,4 +150,7 @@
@Binds
abstract KeyguardViewController bindKeyguardViewController(
StatusBarKeyguardViewManager statusBarKeyguardViewManager);
+
+ @Binds
+ abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 8117bbb..5e36704 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -33,7 +33,6 @@
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.phone.DozeServiceHost;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
@@ -59,7 +58,7 @@
private final Handler mHandler;
private final BiometricUnlockController mBiometricUnlockController;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final DozeServiceHost mDozeServiceHost;
+ private final DozeHost mDozeHost;
@Inject
public DozeFactory(FalsingManager falsingManager, DozeLog dozeLog,
@@ -70,7 +69,7 @@
ProximitySensor proximitySensor,
DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
BiometricUnlockController biometricUnlockController,
- BroadcastDispatcher broadcastDispatcher, DozeServiceHost dozeServiceHost) {
+ BroadcastDispatcher broadcastDispatcher, DozeHost dozeHost) {
mFalsingManager = falsingManager;
mDozeLog = dozeLog;
mDozeParameters = dozeParameters;
@@ -86,7 +85,7 @@
mHandler = handler;
mBiometricUnlockController = biometricUnlockController;
mBroadcastDispatcher = broadcastDispatcher;
- mDozeServiceHost = dozeServiceHost;
+ mDozeHost = dozeHost;
}
/** Creates a DozeMachine with its parts for {@code dozeService}. */
@@ -95,7 +94,7 @@
WakeLock wakeLock = mDelayedWakeLockBuilder.setHandler(mHandler).setTag("Doze").build();
DozeMachine.Service wrappedService = dozeService;
- wrappedService = new DozeBrightnessHostForwarder(wrappedService, mDozeServiceHost);
+ wrappedService = new DozeBrightnessHostForwarder(wrappedService, mDozeHost);
wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(
wrappedService, mDozeParameters);
wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(
@@ -103,19 +102,19 @@
DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock,
mWakefulnessLifecycle, mBatteryController, mDozeLog, mDockManager,
- mDozeServiceHost);
+ mDozeHost);
machine.setParts(new DozeMachine.Part[]{
new DozePauser(mHandler, machine, mAlarmManager, mDozeParameters.getPolicy()),
new DozeFalsingManagerAdapter(mFalsingManager),
- createDozeTriggers(dozeService, mAsyncSensorManager, mDozeServiceHost,
+ createDozeTriggers(dozeService, mAsyncSensorManager, mDozeHost,
mAlarmManager, config, mDozeParameters, mHandler, wakeLock, machine,
mDockManager, mDozeLog),
- createDozeUi(dozeService, mDozeServiceHost, wakeLock, machine, mHandler,
+ createDozeUi(dozeService, mDozeHost, wakeLock, machine, mHandler,
mAlarmManager, mDozeParameters, mDozeLog),
- new DozeScreenState(wrappedService, mHandler, mDozeServiceHost, mDozeParameters,
+ new DozeScreenState(wrappedService, mHandler, mDozeHost, mDozeParameters,
wakeLock),
createDozeScreenBrightness(dozeService, wrappedService, mAsyncSensorManager,
- mDozeServiceHost, mDozeParameters, mHandler),
+ mDozeHost, mDozeParameters, mHandler),
new DozeWallpaperState(mWallpaperManager, mBiometricUnlockController,
mDozeParameters),
new DozeDockHandler(config, machine, mDockManager),
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index f93c169..d69c3b0 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 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.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 @@
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 @@
@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 @@
mBlurUtils = blurUtils;
mRingerModeTracker = ringerModeTracker;
mControlsController = controlsController;
+ mSysUiState = sysUiState;
mMainHandler = handler;
// receive broadcasts
@@ -638,7 +643,7 @@
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 @@
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 @@
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 @@
mNotificationShadeWindowController = notificationShadeWindowController;
mControlsUiController = controlsUiController;
mBlurUtils = blurUtils;
+ mSysUiState = sysuiState;
mUseControlsLayout = useControlsLayout;
mOnRotateCallback = onRotateCallback;
mKeyguardShowing = keyguardShowing;
@@ -2203,6 +2211,8 @@
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) -> {
@@ -2221,7 +2231,7 @@
mBackgroundDrawable.setAlpha(0);
float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
ObjectAnimator alphaAnimator =
- ObjectAnimator.ofFloat(mContainer, "transitionAlpha", 0f, 1f);
+ ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f);
alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
alphaAnimator.setDuration(183);
alphaAnimator.addUpdateListener((animation) -> {
@@ -2234,8 +2244,8 @@
ObjectAnimator xAnimator =
ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f);
- alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- alphaAnimator.setDuration(350);
+ xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ xAnimator.setDuration(350);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(alphaAnimator, xAnimator);
@@ -2247,7 +2257,7 @@
dismissWithAnimation(() -> {
mContainer.setTranslationX(0);
ObjectAnimator alphaAnimator =
- ObjectAnimator.ofFloat(mContainer, "transitionAlpha", 1f, 0f);
+ ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f);
alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
alphaAnimator.setDuration(233);
alphaAnimator.addUpdateListener((animation) -> {
@@ -2261,8 +2271,8 @@
float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
ObjectAnimator xAnimator =
ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset);
- alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- alphaAnimator.setDuration(350);
+ xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ xAnimator.setDuration(350);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(alphaAnimator, xAnimator);
@@ -2303,6 +2313,8 @@
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();
}
@@ -2362,6 +2374,14 @@
}
public void refreshDialog() {
+ // ensure dropdown menus are dismissed before re-initializing the dialog
+ dismissPanel();
+ dismissOverflow(true);
+ if (mControlsUiController != null) {
+ mControlsUiController.hide();
+ }
+
+ // re-create dialog
initializeLayout();
mGlobalActionsLayout.updateList();
if (mControlsUiController != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
index c7612d4..83046ef 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 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 @@
}
}
+ @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 0000000..07fa592
--- /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/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index e08d7a5..1691c53 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -323,10 +323,6 @@
setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
- if (mViewHolder.getBackground().getBackground() instanceof IlluminationDrawable) {
- ((IlluminationDrawable) mViewHolder.getBackground().getBackground())
- .setupTouch(mViewHolder.getSeamless(), mViewHolder.getPlayer());
- }
mViewHolder.getSeamless().setOnClickListener(v -> {
final Intent intent = new Intent()
.setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
@@ -358,11 +354,6 @@
button.setContentDescription(mediaAction.getContentDescription());
PendingIntent actionIntent = mediaAction.getIntent();
- if (mViewHolder.getBackground().getBackground() instanceof IlluminationDrawable) {
- ((IlluminationDrawable) mViewHolder.getBackground().getBackground())
- .setupTouch(button, mViewHolder.getPlayer());
- }
-
button.setOnClickListener(v -> {
if (actionIntent != null) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 07ce9e4..90c558a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -54,7 +54,7 @@
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,
+private val LOADING = MediaData(false, 0, null, null, null, null, null,
emptyList(), emptyList(), null, null, null)
/**
@@ -191,12 +191,14 @@
// TODO: b/153736623 look into creating actions when this isn't a media style notification
val packageContext: Context = sbn.getPackageContext(context)
- for (action in actions) {
- val mediaAction = MediaAction(
- action.getIcon().loadDrawable(packageContext),
- action.actionIntent,
- action.title)
- actionIcons.add(mediaAction)
+ if (actions != null) {
+ for (action in actions) {
+ val mediaAction = MediaAction(
+ action.getIcon().loadDrawable(packageContext),
+ action.actionIntent,
+ action.title)
+ actionIcons.add(mediaAction)
+ }
}
foregroundExcecutor.execute {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
index d72c369..8db9dcc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -299,7 +299,7 @@
firstPlayer.measure(input)
// Relayouting is necessary in motionlayout to obtain its size properly ....
it.layout(0, 0, it.measuredWidth, it.measuredHeight)
- val result = MeasurementOutput(it.measuredWidth, it.measuredHeight)
+ result = MeasurementOutput(it.measuredWidth, it.measuredHeight)
it.progress = previousProgress
if (desiredState != null) {
// remeasure it to the old size again!
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 571e18d..764dbe6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -60,6 +60,17 @@
val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
+ init {
+ (background.background as IlluminationDrawable).let {
+ it.setupTouch(seamless, player)
+ it.setupTouch(action0, player)
+ it.setupTouch(action1, player)
+ it.setupTouch(action2, player)
+ it.setupTouch(action3, player)
+ it.setupTouch(action4, player)
+ }
+ }
+
fun getAction(id: Int): ImageButton {
return when (id) {
R.id.action0 -> action0
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index f900f1e..ccf58ba 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -39,7 +39,7 @@
public class SysUiState implements Dumpable {
private static final String TAG = SysUiState.class.getSimpleName();
- public static final boolean DEBUG = true;
+ public static final boolean DEBUG = false;
private @QuickStepContract.SystemUiStateFlags int mFlags;
private final List<SysUiStateCallback> mCallbacks = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 394f997..9360517 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 @@
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 @@
mReentrySnapFraction = INVALID_SNAP_FRACTION;
mReentrySize = null;
mLastPipComponentName = null;
- mLastDestinationBounds.setEmpty();
- }
-
- public Rect getLastDestinationBounds() {
- return mLastDestinationBounds;
}
public Rect getDisplayBounds() {
@@ -262,7 +251,6 @@
false /* useCurrentMinEdgeSize */);
}
mAspectRatio = aspectRatio;
- mLastDestinationBounds.set(destinationBounds);
return destinationBounds;
}
@@ -276,8 +264,8 @@
*
* @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 @@
}
// 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 @@
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 bb627bc..ae61006 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 @@
Log.w(TAG, "Abort animation, invalid leash");
return;
}
+ mLastReportedBounds.set(destinationBounds);
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper
.crop(tx, mLeash, destinationBounds)
@@ -709,7 +710,7 @@
* @return {@code true} if destinationBounds is altered for split screen
*/
private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut) {
- if (mSplitDivider == null || !mSplitDivider.inSplitMode()) {
+ if (mSplitDivider == null || !mSplitDivider.isDividerVisible()) {
// bail early if system is not in split screen mode
return false;
}
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 30d6cd9..64df2ff 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 @@
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 @@
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 59c962d..d0ff2ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -424,7 +424,7 @@
// 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
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index dfd385d..2f06c4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -32,7 +32,6 @@
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
@@ -228,6 +227,11 @@
}
@Override
+ protected boolean shouldShowDetail() {
+ return !mExpanded;
+ }
+
+ @Override
protected void drawTile(TileRecord r, State state) {
if (state instanceof SignalState) {
SignalState copy = new SignalState();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index f821b19..9e2bb98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -36,7 +36,6 @@
import android.util.Log;
import android.util.PathParser;
import android.view.Gravity;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
@@ -44,7 +43,6 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Switch;
-import android.widget.TextView;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -70,7 +68,6 @@
private float mStrokeWidthInactive;
private final ImageView mBg;
- private final TextView mDetailText;
private final int mColorActive;
private final int mColorInactive;
private final int mColorDisabled;
@@ -112,12 +109,6 @@
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.CENTER);
mIconFrame.addView(mIcon, params);
-
- // "..." afforadance below icon
- mDetailText = (TextView) LayoutInflater.from(context).inflate(R.layout.qs_tile_detail_text,
- mIconFrame, false);
- mIconFrame.addView(mDetailText);
-
mIconFrame.setClipChildren(false);
mIconFrame.setClipToPadding(false);
@@ -173,10 +164,6 @@
tile.longClick();
return true;
});
-
- if (tile.supportsDetailView()) {
- mDetailText.setVisibility(View.VISIBLE);
- }
}
public void init(OnClickListener click, OnClickListener secondaryClick,
@@ -254,8 +241,6 @@
mCircleColor = circleColor;
}
- mDetailText.setTextColor(QSTileImpl.getColorForState(getContext(), state.state));
-
mShowRippleEffect = state.showRippleEffect;
setClickable(state.state != Tile.STATE_UNAVAILABLE);
setLongClickable(state.handlesLongClick);
@@ -400,4 +385,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 7e5f2e19..87faacc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -68,8 +68,6 @@
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QuickStatusBarHeader;
import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tiles.QSSettingsControllerKt;
-import com.android.systemui.qs.tiles.QSSettingsPanel;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -152,19 +150,12 @@
*/
abstract public int getMetricsCategory();
- /**
- * Experimental option on whether to use settings panels. Only loaded on creation, so the tile
- * needs to be removed and added for this to take effect.
- */
- protected final QSSettingsPanel mQSSettingsPanelOption;
-
protected QSTileImpl(QSHost host) {
mHost = host;
mContext = host.getContext();
mInstanceId = host.getNewInstanceId();
mState = newTileState();
mTmpState = newTileState();
- mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption();
mQSLogger = host.getQSLogger();
mUiEventLogger = host.getUiEventLogger();
}
@@ -366,10 +357,6 @@
* {@link QSTileImpl#getLongClickIntent}
*/
protected void handleLongClick() {
- if (mQSSettingsPanelOption == QSSettingsPanel.USE_DETAIL) {
- showDetail(true);
- return;
- }
Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
getLongClickIntent(), 0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt
deleted file mode 100644
index c7ef0be..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.tiles
-
-import android.provider.DeviceConfig
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
-
-enum class QSSettingsPanel {
- DEFAULT,
- OPEN_LONG_PRESS,
- OPEN_CLICK,
- USE_DETAIL
-}
-
-fun getQSSettingsPanelOption(): QSSettingsPanel =
- when (DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.QS_USE_SETTINGS_PANELS, 0)) {
- 1 -> QSSettingsPanel.OPEN_LONG_PRESS
- 2 -> QSSettingsPanel.OPEN_CLICK
- 3 -> QSSettingsPanel.USE_DETAIL
- else -> QSSettingsPanel.DEFAULT
- }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index f169501..1279d42 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -56,7 +56,6 @@
/** Quick settings tile: Wifi **/
public class WifiTile extends QSTileImpl<SignalState> {
private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
- private static final Intent WIFI_PANEL = new Intent(Settings.Panel.ACTION_WIFI);
protected final NetworkController mController;
private final AccessPointController mWifiController;
@@ -109,21 +108,11 @@
@Override
public Intent getLongClickIntent() {
- if (mQSSettingsPanelOption == QSSettingsPanel.OPEN_LONG_PRESS) return WIFI_PANEL;
- else return WIFI_SETTINGS;
- }
-
- @Override
- public boolean supportsDetailView() {
- return getDetailAdapter() != null && mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK;
+ return WIFI_SETTINGS;
}
@Override
protected void handleClick() {
- if (mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK) {
- mActivityStarter.postStartActivityDismissingKeyguard(WIFI_PANEL, 0);
- return;
- }
// Secondary clicks are header clicks, just toggle.
mState.copyTo(mStateBeforeClick);
boolean wifiEnabled = mState.value;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index cf098d5..960c501 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -156,7 +156,7 @@
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
// Remove notification
- notificationManager.cancel(NOTIFICATION_RECORDING_ID);
+ notificationManager.cancel(NOTIFICATION_VIEW_ID);
startActivity(Intent.createChooser(shareIntent, shareLabel)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
@@ -175,7 +175,7 @@
Toast.LENGTH_LONG).show();
// Remove notification
- notificationManager.cancel(NOTIFICATION_RECORDING_ID);
+ notificationManager.cancel(NOTIFICATION_VIEW_ID);
Log.d(TAG, "Deleted recording " + uri);
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 4148289..6f68ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -16,6 +16,8 @@
package com.android.systemui.screenshot;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -54,6 +56,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
@@ -61,16 +64,19 @@
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
@@ -155,6 +161,9 @@
static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
+ // From WizardManagerHelper.java
+ private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
+
private static final String TAG = "GlobalScreenshot";
private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
@@ -169,7 +178,7 @@
private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
private static final float ROUNDED_CORNER_RADIUS = .05f;
- private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000;
+ private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
private static final int MESSAGE_CORNER_TIMEOUT = 2;
private final Interpolator mAccelerateInterpolator = new AccelerateInterpolator();
@@ -183,24 +192,25 @@
private final Display mDisplay;
private final DisplayMetrics mDisplayMetrics;
- private final View mScreenshotLayout;
- private final ScreenshotSelectorView mScreenshotSelectorView;
- private final ImageView mScreenshotAnimatedView;
- private final ImageView mScreenshotPreview;
- private final ImageView mScreenshotFlash;
- private final ImageView mActionsContainerBackground;
- private final FrameLayout mActionsContainer;
- private final LinearLayout mActionsView;
- private final ImageView mBackgroundProtection;
- private final FrameLayout mDismissButton;
- private final ImageView mDismissImage;
+ private View mScreenshotLayout;
+ private ScreenshotSelectorView mScreenshotSelectorView;
+ private ImageView mScreenshotAnimatedView;
+ private ImageView mScreenshotPreview;
+ private ImageView mScreenshotFlash;
+ private ImageView mActionsContainerBackground;
+ private HorizontalScrollView mActionsContainer;
+ private LinearLayout mActionsView;
+ private ImageView mBackgroundProtection;
+ private FrameLayout mDismissButton;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private Animator mScreenshotAnimation;
private Runnable mOnCompleteRunnable;
- private boolean mInDarkMode = false;
private Animator mDismissAnimation;
+ private boolean mInDarkMode = false;
+ private boolean mDirectionLTR = true;
+ private boolean mOrientationPortrait = true;
private float mScreenshotOffsetXPx;
private float mScreenshotOffsetYPx;
@@ -232,57 +242,18 @@
*/
@Inject
public GlobalScreenshot(
- Context context, @Main Resources resources, LayoutInflater layoutInflater,
+ Context context, @Main Resources resources,
ScreenshotNotificationsController screenshotNotificationsController,
UiEventLogger uiEventLogger) {
mContext = context;
mNotificationsController = screenshotNotificationsController;
mUiEventLogger = uiEventLogger;
- // Inflate the screenshot layout
- mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
- mScreenshotAnimatedView =
- mScreenshotLayout.findViewById(R.id.global_screenshot_animated_view);
- mScreenshotAnimatedView.setClipToOutline(true);
- mScreenshotAnimatedView.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
- ROUNDED_CORNER_RADIUS * view.getWidth());
- }
- });
- mScreenshotPreview = mScreenshotLayout.findViewById(R.id.global_screenshot_preview);
- mScreenshotPreview.setClipToOutline(true);
- mScreenshotPreview.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
- ROUNDED_CORNER_RADIUS * view.getWidth());
- }
- });
-
- mActionsContainerBackground = mScreenshotLayout.findViewById(
- R.id.global_screenshot_actions_container_background);
- mActionsContainer = mScreenshotLayout.findViewById(
- R.id.global_screenshot_actions_container);
- mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
- mBackgroundProtection = mScreenshotLayout.findViewById(
- R.id.global_screenshot_actions_background);
- mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
- mDismissButton.setOnClickListener(view -> {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
- dismissScreenshot("dismiss_button", false);
- mOnCompleteRunnable.run();
- });
- mDismissImage = mDismissButton.findViewById(R.id.global_screenshot_dismiss_image);
-
- mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
- mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
- mScreenshotLayout.setFocusable(true);
- mScreenshotSelectorView.setFocusable(true);
- mScreenshotSelectorView.setFocusableInTouchMode(true);
- mScreenshotAnimatedView.setPivotX(0);
- mScreenshotAnimatedView.setPivotY(0);
+ reloadAssets();
+ Configuration config = mContext.getResources().getConfiguration();
+ mInDarkMode = config.isNightModeActive();
+ mDirectionLTR = config.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+ mOrientationPortrait = config.orientation == ORIENTATION_PORTRAIT;
// Setup the window that we are going to use
mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -333,6 +304,121 @@
inoutInfo.touchableRegion.set(touchRegion);
}
+ private void onConfigChanged(Configuration newConfig) {
+ boolean needsUpdate = false;
+ // dark mode
+ if (newConfig.isNightModeActive()) {
+ // Night mode is active, we're using dark theme
+ if (!mInDarkMode) {
+ mInDarkMode = true;
+ needsUpdate = true;
+ }
+ } else {
+ // Night mode is not active, we're using the light theme
+ if (mInDarkMode) {
+ mInDarkMode = false;
+ needsUpdate = true;
+ }
+ }
+
+ // RTL configuration
+ switch (newConfig.getLayoutDirection()) {
+ case View.LAYOUT_DIRECTION_LTR:
+ if (!mDirectionLTR) {
+ mDirectionLTR = true;
+ needsUpdate = true;
+ }
+ break;
+ case View.LAYOUT_DIRECTION_RTL:
+ if (mDirectionLTR) {
+ mDirectionLTR = false;
+ needsUpdate = true;
+ }
+ break;
+ }
+
+ // portrait/landscape orientation
+ switch (newConfig.orientation) {
+ case ORIENTATION_PORTRAIT:
+ if (!mOrientationPortrait) {
+ mOrientationPortrait = true;
+ needsUpdate = true;
+ }
+ break;
+ case ORIENTATION_LANDSCAPE:
+ if (mOrientationPortrait) {
+ mOrientationPortrait = false;
+ needsUpdate = true;
+ }
+ break;
+ }
+
+ if (needsUpdate) {
+ reloadAssets();
+ }
+ }
+
+ /**
+ * Update assets (called when the dark theme status changes). We only need to update the dismiss
+ * button and the actions container background, since the buttons are re-inflated on demand.
+ */
+ private void reloadAssets() {
+ boolean wasAttached = mScreenshotLayout != null && mScreenshotLayout.isAttachedToWindow();
+ if (wasAttached) {
+ mWindowManager.removeView(mScreenshotLayout);
+ }
+
+ // Inflate the screenshot layout
+ mScreenshotLayout = LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null);
+ mScreenshotAnimatedView =
+ mScreenshotLayout.findViewById(R.id.global_screenshot_animated_view);
+ mScreenshotAnimatedView.setClipToOutline(true);
+ mScreenshotAnimatedView.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
+ ROUNDED_CORNER_RADIUS * view.getWidth());
+ }
+ });
+ mScreenshotPreview = mScreenshotLayout.findViewById(R.id.global_screenshot_preview);
+ mScreenshotPreview.setClipToOutline(true);
+ mScreenshotPreview.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
+ ROUNDED_CORNER_RADIUS * view.getWidth());
+ }
+ });
+
+ mActionsContainerBackground = mScreenshotLayout.findViewById(
+ R.id.global_screenshot_actions_container_background);
+ mActionsContainer = mScreenshotLayout.findViewById(
+ R.id.global_screenshot_actions_container);
+ mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
+ mBackgroundProtection = mScreenshotLayout.findViewById(
+ R.id.global_screenshot_actions_background);
+ mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
+ mDismissButton.setOnClickListener(view -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
+ dismissScreenshot("dismiss_button", false);
+ mOnCompleteRunnable.run();
+ });
+
+ mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
+ mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
+ mScreenshotLayout.setFocusable(true);
+ mScreenshotSelectorView.setFocusable(true);
+ mScreenshotSelectorView.setFocusableInTouchMode(true);
+ mScreenshotAnimatedView.setPivotX(0);
+ mScreenshotAnimatedView.setPivotY(0);
+ mActionsContainer.setScrollX(0);
+
+ if (wasAttached) {
+ mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+ }
+ }
+
+
/**
* Creates a new worker thread and saves the screenshot to the media store.
*/
@@ -378,14 +464,19 @@
return;
}
+ if (!isUserSetupComplete()) {
+ // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
+ // and sharing shouldn't be exposed to the user.
+ saveScreenshotAndToast(finisher);
+ return;
+ }
+
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
- updateDarkTheme();
+ onConfigChanged(mContext.getResources().getConfiguration());
- mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
- mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
mDismissAnimation.cancel();
@@ -395,7 +486,6 @@
}
void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) {
- dismissScreenshot("new screenshot requested", true);
mOnCompleteRunnable = onComplete;
mDisplay.getRealMetrics(mDisplayMetrics);
@@ -407,7 +497,6 @@
void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
Insets visibleInsets, int taskId, Consumer<Uri> finisher, Runnable onComplete) {
// TODO use taskId and visibleInsets
- dismissScreenshot("new screenshot requested", true);
mOnCompleteRunnable = onComplete;
takeScreenshot(screenshot, finisher, screenshotScreenBounds);
}
@@ -468,6 +557,41 @@
}
/**
+ * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
+ * failure).
+ */
+ private void saveScreenshotAndToast(Consumer<Uri> finisher) {
+ // Play the shutter sound to notify that we've taken a screenshot
+ mScreenshotHandler.post(() -> {
+ mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+ });
+
+ saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+ @Override
+ void onActionsReady(SavedImageData imageData) {
+ finisher.accept(imageData.uri);
+ if (imageData.uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ } else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
+
+ mScreenshotHandler.post(() -> {
+ Toast.makeText(mContext, R.string.screenshot_saved_title,
+ Toast.LENGTH_SHORT).show();
+ });
+ }
+ }
+ });
+ }
+
+ private boolean isUserSetupComplete() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ }
+
+ /**
* Clears current screenshot
*/
private void dismissScreenshot(String reason, boolean immediate) {
@@ -513,53 +637,15 @@
}
/**
- * Update assets (called when the dark theme status changes). We only need to update the
- * dismiss
- * button and the actions container background, since the buttons are re-inflated on demand.
- */
- private void reloadAssets() {
- mDismissImage.setImageDrawable(mContext.getDrawable(R.drawable.screenshot_cancel));
- mActionsContainerBackground.setBackground(
- mContext.getDrawable(R.drawable.action_chip_container_background));
- }
-
- /**
- * Checks the current dark theme status and updates if it has changed.
- */
- private void updateDarkTheme() {
- int currentNightMode = mContext.getResources().getConfiguration().uiMode
- & Configuration.UI_MODE_NIGHT_MASK;
- switch (currentNightMode) {
- case Configuration.UI_MODE_NIGHT_NO:
- // Night mode is not active, we're using the light theme
- if (mInDarkMode) {
- mInDarkMode = false;
- reloadAssets();
- }
- break;
- case Configuration.UI_MODE_NIGHT_YES:
- // Night mode is active, we're using dark theme
- if (!mInDarkMode) {
- mInDarkMode = true;
- reloadAssets();
- }
- break;
- }
- }
-
- /**
* Starts the animation after taking the screenshot
*/
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);
@@ -588,31 +674,58 @@
} else {
createScreenshotActionsShadeAnimation(imageData).start();
}
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
+ SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS);
+
mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
mScreenshotHandler.sendMessageDelayed(
mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
- SCREENSHOT_CORNER_TIMEOUT_MILLIS);
+ timeoutMs);
});
}
}
});
mScreenshotHandler.post(() -> {
- // Play the shutter sound to notify that we've taken a screenshot
- mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+ if (!mScreenshotLayout.isAttachedToWindow()) {
+ mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+ }
+ mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- mScreenshotPreview.buildLayer();
- mScreenshotAnimation.start();
+ mScreenshotHandler.post(() -> {
+
+ // Play the shutter sound to notify that we've taken a screenshot
+ mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+
+ mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mScreenshotPreview.buildLayer();
+ mScreenshotAnimation.start();
+ });
+
});
+
}
private AnimatorSet createScreenshotDropInAnimation(int width, int height, Rect bounds) {
- float cornerScale = mCornerSizeX / (float) width;
+ float screenWidth = mDisplayMetrics.widthPixels;
+ float screenHeight = mDisplayMetrics.heightPixels;
- mScreenshotAnimatedView.setScaleX(1);
- mScreenshotAnimatedView.setScaleY(1);
- mScreenshotAnimatedView.setX(0);
- mScreenshotAnimatedView.setY(0);
+ int rotation = mContext.getDisplay().getRotation();
+ float cornerScale;
+ if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+ cornerScale = (mCornerSizeX / screenHeight);
+ } else {
+ cornerScale = (mCornerSizeX / screenWidth);
+ }
+ float currentScale = width / screenWidth;
+
+ mScreenshotAnimatedView.setScaleX(currentScale);
+ mScreenshotAnimatedView.setScaleY(currentScale);
+
+ mScreenshotAnimatedView.setPivotX(0);
+ mScreenshotAnimatedView.setPivotY(0);
mScreenshotAnimatedView.setImageBitmap(mScreenBitmap);
mScreenshotPreview.setImageBitmap(mScreenBitmap);
@@ -632,14 +745,12 @@
final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
float finalX;
- if (mContext.getResources().getConfiguration().getLayoutDirection()
- == View.LAYOUT_DIRECTION_LTR) {
- finalX = mScreenshotOffsetXPx + width * cornerScale / 2f;
+ if (mDirectionLTR) {
+ 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);
@@ -647,13 +758,12 @@
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 {
@@ -667,13 +777,13 @@
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() {
@@ -713,7 +823,6 @@
private ValueAnimator createScreenshotActionsShadeAnimation(SavedImageData imageData) {
LayoutInflater inflater = LayoutInflater.from(mContext);
mActionsView.removeAllViews();
- mActionsContainer.setScrollX(0);
mScreenshotLayout.invalidate();
mScreenshotLayout.requestLayout();
mScreenshotLayout.getViewTreeObserver().dispatchOnGlobalLayout();
@@ -803,14 +912,11 @@
animator.setDuration(SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS);
float alphaFraction = (float) SCREENSHOT_ACTIONS_ALPHA_DURATION_MS
/ SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS;
- mActionsContainer.setVisibility(View.VISIBLE);
mActionsContainer.setAlpha(0f);
mActionsContainerBackground.setAlpha(0f);
+ mActionsContainer.setVisibility(View.VISIBLE);
mActionsContainerBackground.setVisibility(View.VISIBLE);
- mActionsContainer.setPivotX(0);
- mActionsContainerBackground.setPivotX(0);
-
animator.addUpdateListener(animation -> {
float t = animation.getAnimatedFraction();
mBackgroundProtection.setAlpha(t);
@@ -825,6 +931,10 @@
chip.setAlpha(t);
chip.setScaleX(1 / containerScale); // invert to keep size of children constant
}
+ mActionsContainer.setScrollX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
+ mActionsContainer.setPivotX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
+ mActionsContainerBackground.setPivotX(
+ mDirectionLTR ? 0 : mActionsContainerBackground.getWidth());
});
return animator;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 4f20492..f7f1223 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -92,7 +92,7 @@
}
private void handleDockKey(long shortcutCode) {
- if (mDivider == null || !mDivider.inSplitMode()) {
+ if (mDivider == null || !mDivider.isDividerVisible()) {
// Split the screen
mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index db33c79..cdd1280 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -16,6 +16,7 @@
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;
@@ -123,14 +124,17 @@
SplitDisplayLayout sdl = new SplitDisplayLayout(mContext, displayLayout, mSplits);
sdl.rotateTo(toRotation);
mRotateSplitLayout = sdl;
- int position = mMinimized ? mView.mSnapTargetBeforeMinimized.position
- : mView.getCurrentPosition();
+ final int position = isDividerVisible()
+ ? (mMinimized ? mView.mSnapTargetBeforeMinimized.position
+ : mView.getCurrentPosition())
+ // snap resets to middle target when not in split-mode
+ : sdl.getSnapAlgorithm().getMiddleTarget().position;
DividerSnapAlgorithm snap = sdl.getSnapAlgorithm();
final DividerSnapAlgorithm.SnapTarget target =
snap.calculateNonDismissingSnapTarget(position);
sdl.resizeSplits(target.position, t);
- if (inSplitMode()) {
+ if (isSplitActive()) {
WindowManagerProxy.applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t);
}
};
@@ -199,7 +203,7 @@
@Override
public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
boolean imeShouldShow, SurfaceControl.Transaction t) {
- if (!inSplitMode()) {
+ if (!isDividerVisible()) {
return;
}
final boolean splitIsVisible = !mView.isHidden();
@@ -298,7 +302,7 @@
@Override
public void onImePositionChanged(int displayId, int imeTop,
SurfaceControl.Transaction t) {
- if (mAnimation != null || !inSplitMode() || mPaused) {
+ if (mAnimation != null || !isDividerVisible() || mPaused) {
// Not synchronized with IME anymore, so return.
return;
}
@@ -310,7 +314,7 @@
@Override
public void onImeEndPositioning(int displayId, boolean cancelled,
SurfaceControl.Transaction t) {
- if (mAnimation != null || !inSplitMode() || mPaused) {
+ if (mAnimation != null || !isDividerVisible() || mPaused) {
// Not synchronized with IME anymore, so return.
return;
}
@@ -479,7 +483,7 @@
@Override
public void onKeyguardShowingChanged() {
- if (!inSplitMode() || mView == null) {
+ if (!isDividerVisible() || mView == null) {
return;
}
mView.setHidden(mKeyguardStateController.isShowing());
@@ -559,10 +563,20 @@
}
/** {@code true} if this is visible */
- public boolean inSplitMode() {
+ public boolean isDividerVisible() {
return mView != null && mView.getVisibility() == View.VISIBLE;
}
+ /**
+ * This indicates that at-least one of the splits has content. This differs from
+ * isDividerVisible because the divider is only visible once *everything* is in split mode
+ * while this only cares if some things are (eg. while entering/exiting as well).
+ */
+ private boolean isSplitActive() {
+ return mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
+ || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED;
+ }
+
private void addDivider(Configuration configuration) {
Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
mView = (DividerView)
@@ -635,8 +649,8 @@
}
void onSplitDismissed() {
- mMinimized = false;
updateVisibility(false /* visible */);
+ mMinimized = false;
removeDivider();
}
@@ -655,7 +669,8 @@
private void setHomeMinimized(final boolean minimized, boolean homeStackResizable) {
if (DEBUG) {
Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:"
- + mHomeStackResizable + "->" + homeStackResizable + " split:" + inSplitMode());
+ + mHomeStackResizable + "->" + homeStackResizable
+ + " split:" + isDividerVisible());
}
WindowContainerTransaction wct = new WindowContainerTransaction();
final boolean minimizedChanged = mMinimized != minimized;
@@ -670,7 +685,7 @@
final boolean homeResizableChanged = mHomeStackResizable != homeStackResizable;
if (homeResizableChanged) {
mHomeStackResizable = homeStackResizable;
- if (inSplitMode()) {
+ if (isDividerVisible()) {
WindowManagerProxy.applyHomeTasksMinimized(
mSplitLayout, mSplits.mSecondary.token, wct);
}
@@ -780,7 +795,7 @@
/** Register a listener that gets called whenever the existence of the divider changes */
public void registerInSplitScreenListener(Consumer<Boolean> listener) {
- listener.accept(inSplitMode());
+ listener.accept(isDividerVisible());
synchronized (mDockedStackExistsListeners) {
mDockedStackExistsListeners.add(new WeakReference<>(listener));
}
@@ -795,7 +810,7 @@
void ensureMinimizedSplit() {
setHomeMinimized(true /* minimized */, mSplits.mSecondary.isResizable());
- if (!inSplitMode()) {
+ if (!isDividerVisible()) {
// Wasn't in split-mode yet, so enter now.
if (DEBUG) {
Slog.d(TAG, " entering split mode with minimized=true");
@@ -806,7 +821,7 @@
void ensureNormalSplit() {
setHomeMinimized(false /* minimized */, mHomeStackResizable);
- if (!inSplitMode()) {
+ if (!isDividerVisible()) {
// Wasn't in split-mode, so enter now.
if (DEBUG) {
Slog.d(TAG, " enter split mode unminimized ");
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 060760a..db89cea 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -28,12 +28,14 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Region.Op;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.Handler;
+import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Slog;
import android.view.Display;
@@ -164,6 +166,9 @@
int mDividerPositionX;
int mDividerPositionY;
+ private final Matrix mTmpMatrix = new Matrix();
+ private final float[] mTmpValues = new float[9];
+
// The view is removed or in the process of been removed from the system.
private boolean mRemoved;
@@ -249,6 +254,22 @@
}
};
+ private Runnable mUpdateEmbeddedMatrix = () -> {
+ if (getViewRootImpl() == null) {
+ return;
+ }
+ if (isHorizontalDivision()) {
+ mTmpMatrix.setTranslate(0, mDividerPositionY - mDividerInsets);
+ } else {
+ mTmpMatrix.setTranslate(mDividerPositionX - mDividerInsets, 0);
+ }
+ mTmpMatrix.getValues(mTmpValues);
+ try {
+ getViewRootImpl().getAccessibilityEmbeddedConnection().setScreenMatrix(mTmpValues);
+ } catch (RemoteException e) {
+ }
+ };
+
public DividerView(Context context) {
this(context, null);
}
@@ -1084,6 +1105,10 @@
t.setPosition(dividerCtrl, mDividerPositionX - mDividerInsets, 0);
}
}
+ if (getViewRootImpl() != null) {
+ mHandler.removeCallbacks(mUpdateEmbeddedMatrix);
+ mHandler.post(mUpdateEmbeddedMatrix);
+ }
}
void setResizeDimLayer(Transaction t, boolean primary, float alpha) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
index 2862c83..c496d22 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
@@ -193,7 +193,7 @@
Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType
+ " " + mSecondary.topActivityType);
}
- if (mDivider.inSplitMode()) {
+ if (mDivider.isDividerVisible()) {
// Was in split-mode, which means we are leaving split, so continue that.
// This happens when the stack in the primary-split is dismissed.
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
new file mode 100644
index 0000000..7f7ff9cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -0,0 +1,88 @@
+/*
+ * 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
+
+import android.app.PendingIntent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/**
+ * Logger class for events related to the user clicking on notification actions
+ */
+class ActionClickLogger @Inject constructor(
+ @NotifInteractionLog private val buffer: LogBuffer
+) {
+ fun logInitialClick(
+ entry: NotificationEntry?,
+ pendingIntent: PendingIntent
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry?.key
+ str2 = entry?.ranking?.channel?.id
+ str3 = pendingIntent.intent.toString()
+ }, {
+ "ACTION CLICK $str1 (channel=$str2) for pending intent $str3"
+ })
+ }
+
+ fun logRemoteInputWasHandled(
+ entry: NotificationEntry?
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry?.key
+ }, {
+ " [Action click] Triggered remote input (for $str1))"
+ })
+ }
+
+ fun logStartingIntentWithDefaultHandler(
+ entry: NotificationEntry?,
+ pendingIntent: PendingIntent
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry?.key
+ str2 = pendingIntent.intent.toString()
+ }, {
+ " [Action click] Launching intent $str2 via default handler (for $str1)"
+ })
+ }
+
+ fun logWaitingToCloseKeyguard(
+ pendingIntent: PendingIntent
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = pendingIntent.intent.toString()
+ }, {
+ " [Action click] Intent $str1 launches an activity, dismissing keyguard first..."
+ })
+ }
+
+ fun logKeyguardGone(
+ pendingIntent: PendingIntent
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = pendingIntent.intent.toString()
+ }, {
+ " [Action click] Keyguard dismissed, calling default handler for intent $str1"
+ })
+ }
+}
+
+private const val TAG = "ActionClickLogger"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2647c04..2baab61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,8 +19,8 @@
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
import android.app.ActivityManager;
import android.app.KeyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index bf28040..9181c69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -54,7 +54,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.dagger.StatusBarModule;
+import com.android.systemui.statusbar.dagger.StatusBarDependenciesModule;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -114,6 +114,7 @@
private final SmartReplyController mSmartReplyController;
private final NotificationEntryManager mEntryManager;
private final Handler mMainHandler;
+ private final ActionClickLogger mLogger;
private final Lazy<StatusBar> mStatusBarLazy;
@@ -138,14 +139,18 @@
mStatusBarLazy.get().wakeUpIfDozing(SystemClock.uptimeMillis(), view,
"NOTIFICATION_CLICK");
+ final NotificationEntry entry = getNotificationForParent(view.getParent());
+ mLogger.logInitialClick(entry, pendingIntent);
+
if (handleRemoteInput(view, pendingIntent)) {
+ mLogger.logRemoteInputWasHandled(entry);
return true;
}
if (DEBUG) {
Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
}
- logActionClick(view, pendingIntent);
+ logActionClick(view, entry, pendingIntent);
// The intent we are sending is for the application, which
// won't have permission to immediately start an activity after
// the user switches to home. We know it is safe to do at this
@@ -158,11 +163,15 @@
Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view);
options.second.setLaunchWindowingMode(
WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ mLogger.logStartingIntentWithDefaultHandler(entry, pendingIntent);
return RemoteViews.startPendingIntent(view, pendingIntent, options);
});
}
- private void logActionClick(View view, PendingIntent actionIntent) {
+ private void logActionClick(
+ View view,
+ NotificationEntry entry,
+ PendingIntent actionIntent) {
Integer actionIndex = (Integer)
view.getTag(com.android.internal.R.id.notification_action_index_tag);
if (actionIndex == null) {
@@ -170,7 +179,7 @@
return;
}
ViewParent parent = view.getParent();
- StatusBarNotification statusBarNotification = getNotificationForParent(parent);
+ StatusBarNotification statusBarNotification = entry.getSbn();
if (statusBarNotification == null) {
Log.w(TAG, "Couldn't determine notification for click.");
return;
@@ -212,10 +221,10 @@
}
}
- private StatusBarNotification getNotificationForParent(ViewParent parent) {
+ private NotificationEntry getNotificationForParent(ViewParent parent) {
while (parent != null) {
if (parent instanceof ExpandableNotificationRow) {
- return ((ExpandableNotificationRow) parent).getEntry().getSbn();
+ return ((ExpandableNotificationRow) parent).getEntry();
}
parent = parent.getParent();
}
@@ -255,7 +264,7 @@
};
/**
- * Injected constructor. See {@link StatusBarModule}.
+ * Injected constructor. See {@link StatusBarDependenciesModule}.
*/
public NotificationRemoteInputManager(
Context context,
@@ -265,13 +274,15 @@
Lazy<StatusBar> statusBarLazy,
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
- RemoteInputUriController remoteInputUriController) {
+ RemoteInputUriController remoteInputUriController,
+ ActionClickLogger logger) {
mContext = context;
mLockscreenUserManager = lockscreenUserManager;
mSmartReplyController = smartReplyController;
mEntryManager = notificationEntryManager;
mStatusBarLazy = statusBarLazy;
mMainHandler = mainHandler;
+ mLogger = logger;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 765f85a..3dda15b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -186,8 +186,7 @@
boolean groupChangesAllowed =
mVisualStabilityManager.areGroupChangesAllowed() // user isn't looking at notifs
- || !ent.hasFinishedInitialization() // notif recently added
- || !mListContainer.containsView(ent.getRow()); // notif recently unfiltered
+ || !ent.hasFinishedInitialization(); // notif recently added
NotificationEntry parent = mGroupManager.getGroupSummary(ent.getSbn());
if (!groupChangesAllowed) {
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 f0fed13..b08eb9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.MediaArtworkProcessor;
import com.android.systemui.statusbar.NotificationListener;
@@ -73,7 +74,8 @@
Lazy<StatusBar> statusBarLazy,
StatusBarStateController statusBarStateController,
Handler mainHandler,
- RemoteInputUriController remoteInputUriController) {
+ RemoteInputUriController remoteInputUriController,
+ ActionClickLogger actionClickLogger) {
return new NotificationRemoteInputManager(
context,
lockscreenUserManager,
@@ -82,7 +84,8 @@
statusBarLazy,
statusBarStateController,
mainHandler,
- remoteInputUriController);
+ remoteInputUriController,
+ actionClickLogger);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index d7b391f..ce6013f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -21,12 +21,12 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
+import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
+import com.android.systemui.statusbar.notification.stack.BUCKET_MEDIA_CONTROLS
+import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.Utils
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 b1b6a1c..3517e24 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 @@
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/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 634872d..1eadd9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -31,7 +31,7 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
import static java.util.Objects.requireNonNull;
@@ -68,7 +68,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
+import com.android.systemui.statusbar.notification.stack.PriorityBucket;
import java.util.ArrayList;
import java.util.List;
@@ -151,6 +151,8 @@
public CharSequence headsUpStatusBarText;
public CharSequence headsUpStatusBarTextPublic;
+ // indicates when this entry's view was first attached to a window
+ // this value will reset when the view is completely removed from the shade (ie: filtered out)
private long initializationTime = -1;
/**
@@ -409,12 +411,12 @@
return wasBubble != isBubble();
}
- @NotificationSectionsManager.PriorityBucket
+ @PriorityBucket
public int getBucket() {
return mBucket;
}
- public void setBucket(@NotificationSectionsManager.PriorityBucket int bucket) {
+ public void setBucket(@PriorityBucket int bucket) {
mBucket = bucket;
}
@@ -473,8 +475,8 @@
}
public boolean hasFinishedInitialization() {
- return initializationTime == -1
- || SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
+ return initializationTime != -1
+ && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
}
public int getContrastedColor(Context context, boolean isLowPriority,
@@ -565,6 +567,10 @@
return false;
}
+ public void resetInitializationTime() {
+ initializationTime = -1;
+ }
+
public void setInitializationTime(long time) {
if (initializationTime == -1) {
initializationTime = time;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 9ac4229..dc0b802 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -28,12 +28,11 @@
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.PriorityBucket
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
+import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
import com.android.systemui.statusbar.phone.NotificationGroupManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import dagger.Lazy
@@ -135,26 +134,20 @@
): List<NotificationEntry> {
logger.logFilterAndSort(reason)
val filtered = entries.asSequence()
- .filterNot(notifFilter::shouldFilterOut)
+ .filterNot(this::filter)
.sortedWith(rankingComparator)
.toList()
- assignBuckets(filtered)
+ entries.forEach { it.bucket = getBucketForEntry(it) }
return filtered
}
- private fun assignBuckets(entries: List<NotificationEntry>) {
- entries.forEach { it.bucket = getBucketForEntry(it) }
- if (!usePeopleFiltering) {
- // If we don't have a Conversation section, just assign buckets normally based on the
- // content.
- return
+ private fun filter(entry: NotificationEntry): Boolean {
+ val filtered = notifFilter.shouldFilterOut(entry)
+ if (filtered) {
+ // notification is removed from the list, so we reset its initialization time
+ entry.resetInitializationTime()
}
- // If HUNs are not continuous with the top section, break out into a new Incoming section.
- entries.asReversed().asSequence().zipWithNext().forEach { (next, entry) ->
- if (entry.isRowHeadsUp && entry.bucket > next.bucket) {
- entry.bucket = BUCKET_HEADS_UP
- }
- }
+ return filtered
}
@PriorityBucket
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 0a3b02c..4cec383 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -713,6 +713,10 @@
private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
final NotifFilter filter = findRejectingFilter(entry, now, filters);
entry.getAttachState().setExcludingFilter(filter);
+ if (filter != null) {
+ // notification is removed from the list, so we reset its initialization time
+ entry.resetInitializationTime();
+ }
return filter != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 57be1a5..92426e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -19,8 +19,6 @@
import static android.service.notification.NotificationStats.DISMISSAL_OTHER;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-import android.annotation.NonNull;
-
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
@@ -123,7 +121,7 @@
private final BubbleController.NotifCallback mNotifCallback =
new BubbleController.NotifCallback() {
@Override
- public void removeNotification(@NonNull final NotificationEntry entry, int reason) {
+ public void removeNotification(NotificationEntry entry, int reason) {
if (isInterceptingDismissal(entry)) {
mInterceptedDismissalEntries.remove(entry.getKey());
mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry,
@@ -143,7 +141,7 @@
}
@Override
- public void maybeCancelSummary(@NonNull final NotificationEntry entry) {
+ public void maybeCancelSummary(NotificationEntry entry) {
// no-op
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index 9d456ef..bad36bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -32,8 +32,8 @@
* Represents the bounds of a section of the notification shade and handles animation when the
* bounds change.
*/
-class NotificationSection {
- private @NotificationSectionsManager.PriorityBucket int mBucket;
+public class NotificationSection {
+ private @PriorityBucket int mBucket;
private View mOwningView;
private Rect mBounds = new Rect();
private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
@@ -44,7 +44,7 @@
private ActivatableNotificationView mFirstVisibleChild;
private ActivatableNotificationView mLastVisibleChild;
- NotificationSection(View owningView, @NotificationSectionsManager.PriorityBucket int bucket) {
+ NotificationSection(View owningView, @PriorityBucket int bucket) {
mOwningView = owningView;
mBucket = bucket;
}
@@ -74,7 +74,7 @@
return mBottomAnimator != null || mTopAnimator != null;
}
- @NotificationSectionsManager.PriorityBucket
+ @PriorityBucket
public int getBucket() {
return mBucket;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index 92fdd64..17b4143 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -52,14 +52,35 @@
{ "$int1: other ($str1)" }
)
- fun logHeadsUp(position: Int) = logPosition(position, "Heads Up")
- fun logConversation(position: Int) = logPosition(position, "Conversation")
- fun logAlerting(position: Int) = logPosition(position, "Alerting")
- fun logSilent(position: Int) = logPosition(position, "Silent")
- fun logForegroundService(position: Int) = logPosition(position, "Foreground Service")
+ fun logHeadsUp(position: Int, isHeadsUp: Boolean) =
+ logPosition(position, "Heads Up", isHeadsUp)
+ fun logConversation(position: Int, isHeadsUp: Boolean) =
+ logPosition(position, "Conversation", isHeadsUp)
+ fun logAlerting(position: Int, isHeadsUp: Boolean) =
+ logPosition(position, "Alerting", isHeadsUp)
+ fun logSilent(position: Int, isHeadsUp: Boolean) =
+ logPosition(position, "Silent", isHeadsUp)
+ fun logForegroundService(position: Int, isHeadsUp: Boolean) =
+ logPosition(position, "Foreground Service", isHeadsUp)
fun logStr(str: String) = logBuffer.log(TAG, LogLevel.DEBUG, { str1 = str }, { "$str1" })
+ private fun logPosition(position: Int, label: String, isHeadsUp: Boolean) {
+ val headsUpTag = if (isHeadsUp) " (HUN)" else ""
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = position
+ str1 = label
+ str2 = headsUpTag
+ },
+ {
+ "$int1: $str1$str2"
+ }
+ )
+ }
+
private fun logPosition(position: Int, label: String) = logBuffer.log(
TAG,
LogLevel.DEBUG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
deleted file mode 100644
index f3ee2ce..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ /dev/null
@@ -1,689 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.stack;
-
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.ColorInt;
-import android.annotation.IntDef;
-import android.annotation.LayoutRes;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Intent;
-import android.provider.Settings;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-import com.android.systemui.media.KeyguardMediaController;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
-import com.android.systemui.statusbar.notification.people.DataListener;
-import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter;
-import com.android.systemui.statusbar.notification.people.PeopleHubViewBoundary;
-import com.android.systemui.statusbar.notification.people.PersonViewModel;
-import com.android.systemui.statusbar.notification.people.Subscription;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-
-import java.lang.annotation.Retention;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import javax.inject.Inject;
-
-import kotlin.sequences.Sequence;
-
-/**
- * Manages the boundaries of the two notification sections (high priority and low priority). Also
- * shows/hides the headers for those sections where appropriate.
- *
- * TODO: Move remaining sections logic from NSSL into this class.
- */
-public class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvider {
-
- private static final String TAG = "NotifSectionsManager";
- private static final boolean DEBUG = false;
- private static final boolean ENABLE_SNOOZED_CONVERSATION_HUB = false;
-
- private final ActivityStarter mActivityStarter;
- private final StatusBarStateController mStatusBarStateController;
- private final ConfigurationController mConfigurationController;
- private final PeopleHubViewAdapter mPeopleHubViewAdapter;
- private final NotificationSectionsFeatureManager mSectionsFeatureManager;
- private final KeyguardMediaController mKeyguardMediaController;
- private final int mNumberOfSections;
- private final NotificationSectionsLogger mLogger;
- private final PeopleHubViewBoundary mPeopleHubViewBoundary = new PeopleHubViewBoundary() {
- @Override
- public void setVisible(boolean isVisible) {
- if (mPeopleHubVisible != isVisible) {
- mPeopleHubVisible = isVisible;
- if (mInitialized) {
- updateSectionBoundaries("PeopleHub visibility changed");
- }
- }
- }
-
- @NonNull
- @Override
- public View getAssociatedViewForClickAnimation() {
- return mPeopleHubView;
- }
-
- @NonNull
- @Override
- public Sequence<DataListener<PersonViewModel>> getPersonViewAdapters() {
- return mPeopleHubView.getPersonViewAdapters();
- }
- };
-
- private NotificationStackScrollLayout mParent;
- private boolean mInitialized = false;
-
- private SectionHeaderView mGentleHeader;
- @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener;
-
- private SectionHeaderView mAlertingHeader;
- private SectionHeaderView mIncomingHeader;
-
- private PeopleHubView mPeopleHubView;
- private boolean mPeopleHubVisible = false;
- @Nullable private Subscription mPeopleHubSubscription;
-
- private MediaHeaderView mMediaControlsView;
-
- @Inject
- NotificationSectionsManager(
- ActivityStarter activityStarter,
- StatusBarStateController statusBarStateController,
- ConfigurationController configurationController,
- PeopleHubViewAdapter peopleHubViewAdapter,
- KeyguardMediaController keyguardMediaController,
- NotificationSectionsFeatureManager sectionsFeatureManager,
- NotificationSectionsLogger logger) {
-
- mActivityStarter = activityStarter;
- mStatusBarStateController = statusBarStateController;
- mConfigurationController = configurationController;
- mPeopleHubViewAdapter = peopleHubViewAdapter;
- mSectionsFeatureManager = sectionsFeatureManager;
- mNumberOfSections = mSectionsFeatureManager.getNumberOfBuckets();
- mKeyguardMediaController = keyguardMediaController;
- mLogger = logger;
- }
-
- NotificationSection[] createSectionsForBuckets() {
- int[] buckets = mSectionsFeatureManager.getNotificationBuckets();
- NotificationSection[] sections = new NotificationSection[buckets.length];
- for (int i = 0; i < buckets.length; i++) {
- sections[i] = new NotificationSection(mParent, buckets[i] /* bucket */);
- }
-
- return sections;
- }
-
- /** Must be called before use. */
- void initialize(
- NotificationStackScrollLayout parent, LayoutInflater layoutInflater) {
- if (mInitialized) {
- throw new IllegalStateException("NotificationSectionsManager already initialized");
- }
- mInitialized = true;
- mParent = parent;
- reinflateViews(layoutInflater);
- mConfigurationController.addCallback(mConfigurationListener);
- }
-
- private <T extends ExpandableView> T reinflateView(
- T view, LayoutInflater layoutInflater, @LayoutRes int layoutResId) {
- int oldPos = -1;
- if (view != null) {
- if (view.getTransientContainer() != null) {
- view.getTransientContainer().removeView(mGentleHeader);
- } else if (view.getParent() != null) {
- oldPos = mParent.indexOfChild(view);
- mParent.removeView(view);
- }
- }
-
- view = (T) layoutInflater.inflate(layoutResId, mParent, false);
-
- if (oldPos != -1) {
- mParent.addView(view, oldPos);
- }
-
- return view;
- }
-
- /**
- * Reinflates the entire notification header, including all decoration views.
- */
- void reinflateViews(LayoutInflater layoutInflater) {
- mGentleHeader = reinflateView(
- mGentleHeader, layoutInflater, R.layout.status_bar_notification_section_header);
- mGentleHeader.setHeaderText(R.string.notification_section_header_gentle);
- mGentleHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
- mGentleHeader.setOnClearAllClickListener(this::onClearGentleNotifsClick);
-
- mAlertingHeader = reinflateView(
- mAlertingHeader, layoutInflater, R.layout.status_bar_notification_section_header);
- mAlertingHeader.setHeaderText(R.string.notification_section_header_alerting);
- mAlertingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
-
- if (mPeopleHubSubscription != null) {
- mPeopleHubSubscription.unsubscribe();
- }
- mPeopleHubView = reinflateView(mPeopleHubView, layoutInflater, R.layout.people_strip);
- if (ENABLE_SNOOZED_CONVERSATION_HUB) {
- mPeopleHubSubscription = mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
- }
-
- mIncomingHeader = reinflateView(
- mIncomingHeader, layoutInflater, R.layout.status_bar_notification_section_header);
- mIncomingHeader.setHeaderText(R.string.notification_section_header_incoming);
- mIncomingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
-
- mMediaControlsView = reinflateView(mMediaControlsView, layoutInflater,
- R.layout.keyguard_media_header);
- mKeyguardMediaController.attach(mMediaControlsView);
- }
-
- /** Listener for when the "clear all" button is clicked on the gentle notification header. */
- void setOnClearGentleNotifsClickListener(View.OnClickListener listener) {
- mOnClearGentleNotifsClickListener = listener;
- }
-
- @Override
- public boolean beginsSection(@NonNull View view, @Nullable View previous) {
- return view == mGentleHeader
- || view == mMediaControlsView
- || view == mPeopleHubView
- || view == mAlertingHeader
- || view == mIncomingHeader
- || !Objects.equals(getBucket(view), getBucket(previous));
- }
-
- private boolean isUsingMultipleSections() {
- return mNumberOfSections > 1;
- }
-
- @Nullable
- private Integer getBucket(View view) {
- if (view == mGentleHeader) {
- return BUCKET_SILENT;
- } else if (view == mIncomingHeader) {
- return BUCKET_HEADS_UP;
- } else if (view == mMediaControlsView) {
- return BUCKET_MEDIA_CONTROLS;
- } else if (view == mPeopleHubView) {
- return BUCKET_PEOPLE;
- } else if (view == mAlertingHeader) {
- return BUCKET_ALERTING;
- } else if (view instanceof ExpandableNotificationRow) {
- return ((ExpandableNotificationRow) view).getEntry().getBucket();
- }
- return null;
- }
-
- private void logShadeContents() {
- final int childCount = mParent.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mParent.getChildAt(i);
- if (child == mIncomingHeader) {
- mLogger.logIncomingHeader(i);
- continue;
- }
- if (child == mMediaControlsView) {
- mLogger.logMediaControls(i);
- continue;
- }
- if (child == mPeopleHubView) {
- mLogger.logConversationsHeader(i);
- continue;
- }
- if (child == mAlertingHeader) {
- mLogger.logAlertingHeader(i);
- continue;
- }
- if (child == mGentleHeader) {
- mLogger.logSilentHeader(i);
- continue;
- }
-
- if (!(child instanceof ExpandableNotificationRow)) {
- mLogger.logOther(i, child.getClass());
- continue;
- }
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- // Once we enter a new section, calculate the target position for the header.
- switch (row.getEntry().getBucket()) {
- case BUCKET_HEADS_UP:
- mLogger.logHeadsUp(i);
- break;
- case BUCKET_PEOPLE:
- mLogger.logConversation(i);
- break;
- case BUCKET_ALERTING:
- mLogger.logAlerting(i);
- break;
- case BUCKET_SILENT:
- mLogger.logSilent(i);
- break;
- }
- }
- }
-
- @VisibleForTesting
- void updateSectionBoundaries() {
- updateSectionBoundaries("test");
- }
-
- /**
- * Should be called whenever notifs are added, removed, or updated. Updates section boundary
- * bookkeeping and adds/moves/removes section headers if appropriate.
- */
- void updateSectionBoundaries(String reason) {
- if (!isUsingMultipleSections()) {
- return;
- }
-
- mLogger.logStartSectionUpdate(reason);
-
- // The overall strategy here is to iterate over the current children of mParent, looking
- // for where the sections headers are currently positioned, and where each section begins.
- // Then, once we find the start of a new section, we track that position as the "target" for
- // the section header, adjusted for the case where existing headers are in front of that
- // target, but won't be once they are moved / removed after the pass has completed.
-
- final boolean showHeaders = mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
- final boolean usingPeopleFiltering = mSectionsFeatureManager.isFilteringEnabled();
- final boolean usingMediaControls = mSectionsFeatureManager.isMediaControlsEnabled();
-
- boolean peopleNotifsPresent = false;
-
- int currentMediaControlsIdx = -1;
- int mediaControlsTarget = usingMediaControls ? 0 : -1;
- int currentIncomingHeaderIdx = -1;
- int incomingHeaderTarget = -1;
- int currentPeopleHeaderIdx = -1;
- int peopleHeaderTarget = -1;
- int currentAlertingHeaderIdx = -1;
- int alertingHeaderTarget = -1;
- int currentGentleHeaderIdx = -1;
- int gentleHeaderTarget = -1;
-
- int lastNotifIndex = 0;
-
- final int childCount = mParent.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mParent.getChildAt(i);
-
- // Track the existing positions of the headers
- if (child == mIncomingHeader) {
- mLogger.logIncomingHeader(i);
- currentIncomingHeaderIdx = i;
- continue;
- }
- if (child == mMediaControlsView) {
- mLogger.logMediaControls(i);
- currentMediaControlsIdx = i;
- continue;
- }
- if (child == mPeopleHubView) {
- mLogger.logConversationsHeader(i);
- currentPeopleHeaderIdx = i;
- continue;
- }
- if (child == mAlertingHeader) {
- mLogger.logAlertingHeader(i);
- currentAlertingHeaderIdx = i;
- continue;
- }
- if (child == mGentleHeader) {
- mLogger.logSilentHeader(i);
- currentGentleHeaderIdx = i;
- continue;
- }
-
- if (!(child instanceof ExpandableNotificationRow)) {
- mLogger.logOther(i, child.getClass());
- continue;
- }
- lastNotifIndex = i;
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- // Once we enter a new section, calculate the target position for the header.
- switch (row.getEntry().getBucket()) {
- case BUCKET_HEADS_UP:
- mLogger.logHeadsUp(i);
- if (showHeaders && incomingHeaderTarget == -1) {
- incomingHeaderTarget = i;
- // Offset the target if there are other headers before this that will be
- // moved.
- if (currentIncomingHeaderIdx != -1) {
- incomingHeaderTarget--;
- }
- if (currentMediaControlsIdx != -1) {
- incomingHeaderTarget--;
- }
- if (currentPeopleHeaderIdx != -1) {
- incomingHeaderTarget--;
- }
- if (currentAlertingHeaderIdx != -1) {
- incomingHeaderTarget--;
- }
- if (currentGentleHeaderIdx != -1) {
- incomingHeaderTarget--;
- }
- }
- if (mediaControlsTarget != -1) {
- mediaControlsTarget++;
- }
- break;
- case BUCKET_FOREGROUND_SERVICE:
- mLogger.logForegroundService(i);
- if (mediaControlsTarget != -1) {
- mediaControlsTarget++;
- }
- break;
- case BUCKET_PEOPLE:
- mLogger.logConversation(i);
- peopleNotifsPresent = true;
- if (showHeaders && peopleHeaderTarget == -1) {
- peopleHeaderTarget = i;
- // Offset the target if there are other headers before this that will be
- // moved.
- if (currentPeopleHeaderIdx != -1) {
- peopleHeaderTarget--;
- }
- if (currentAlertingHeaderIdx != -1) {
- peopleHeaderTarget--;
- }
- if (currentGentleHeaderIdx != -1) {
- peopleHeaderTarget--;
- }
- }
- break;
- case BUCKET_ALERTING:
- mLogger.logAlerting(i);
- if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
- alertingHeaderTarget = i;
- // Offset the target if there are other headers before this that will be
- // moved.
- if (currentAlertingHeaderIdx != -1) {
- alertingHeaderTarget--;
- }
- if (currentGentleHeaderIdx != -1) {
- alertingHeaderTarget--;
- }
- }
- break;
- case BUCKET_SILENT:
- mLogger.logSilent(i);
- if (showHeaders && gentleHeaderTarget == -1) {
- gentleHeaderTarget = i;
- // Offset the target if there are other headers before this that will be
- // moved.
- if (currentGentleHeaderIdx != -1) {
- gentleHeaderTarget--;
- }
- }
- break;
- default:
- throw new IllegalStateException("Cannot find section bucket for view");
- }
- }
- if (showHeaders && usingPeopleFiltering && mPeopleHubVisible && peopleHeaderTarget == -1) {
- // Insert the people header even if there are no people visible, in order to show
- // the hub. Put it directly above the next header.
- if (alertingHeaderTarget != -1) {
- peopleHeaderTarget = alertingHeaderTarget;
- } else if (gentleHeaderTarget != -1) {
- peopleHeaderTarget = gentleHeaderTarget;
- } else {
- // Put it at the end of the list.
- peopleHeaderTarget = lastNotifIndex;
- }
- // Offset the target to account for the current position of the people header.
- if (currentPeopleHeaderIdx != -1 && currentPeopleHeaderIdx < peopleHeaderTarget) {
- peopleHeaderTarget--;
- }
- }
-
- mLogger.logStr("New header target positions:");
- mLogger.logIncomingHeader(incomingHeaderTarget);
- mLogger.logMediaControls(mediaControlsTarget);
- mLogger.logConversationsHeader(peopleHeaderTarget);
- mLogger.logAlertingHeader(alertingHeaderTarget);
- mLogger.logSilentHeader(gentleHeaderTarget);
-
- // Add headers in reverse order to preserve indices
- adjustHeaderVisibilityAndPosition(
- gentleHeaderTarget, mGentleHeader, currentGentleHeaderIdx);
- adjustHeaderVisibilityAndPosition(
- alertingHeaderTarget, mAlertingHeader, currentAlertingHeaderIdx);
- adjustHeaderVisibilityAndPosition(
- peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx);
- adjustViewPosition(mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx);
- adjustHeaderVisibilityAndPosition(incomingHeaderTarget, mIncomingHeader,
- currentIncomingHeaderIdx);
-
- mLogger.logStr("Final order:");
- logShadeContents();
- mLogger.logStr("Section boundary update complete");
-
- // Update headers to reflect state of section contents
- mGentleHeader.setAreThereDismissableGentleNotifs(
- mParent.hasActiveClearableNotifications(ROWS_GENTLE));
- mPeopleHubView.setCanSwipe(showHeaders && mPeopleHubVisible && !peopleNotifsPresent);
- if (peopleHeaderTarget != currentPeopleHeaderIdx) {
- mPeopleHubView.resetTranslation();
- }
- }
-
- private void adjustHeaderVisibilityAndPosition(
- int targetPosition, StackScrollerDecorView header, int currentPosition) {
- adjustViewPosition(targetPosition, header, currentPosition);
- if (targetPosition != -1 && currentPosition == -1) {
- header.setContentVisible(true);
- }
- }
-
- private void adjustViewPosition(int targetPosition, ExpandableView view, int currentPosition) {
- if (targetPosition == -1) {
- if (currentPosition != -1) {
- mParent.removeView(view);
- }
- } else {
- if (currentPosition == -1) {
- // If the header is animating away, it will still have a parent, so detach it first
- // TODO: We should really cancel the active animations here. This will happen
- // automatically when the view's intro animation starts, but it's a fragile link.
- if (view.getTransientContainer() != null) {
- view.getTransientContainer().removeTransientView(view);
- view.setTransientContainer(null);
- }
- mParent.addView(view, targetPosition);
- } else {
- mParent.changeViewPosition(view, targetPosition);
- }
- }
- }
-
- /**
- * Updates the boundaries (as tracked by their first and last views) of the priority sections.
- *
- * @return {@code true} If the last view in the top section changed (so we need to animate).
- */
- boolean updateFirstAndLastViewsForAllSections(
- NotificationSection[] sections,
- List<ActivatableNotificationView> children) {
-
- if (sections.length <= 0 || children.size() <= 0) {
- for (NotificationSection s : sections) {
- s.setFirstVisibleChild(null);
- s.setLastVisibleChild(null);
- }
- return false;
- }
-
- boolean changed = false;
- ArrayList<ActivatableNotificationView> viewsInBucket = new ArrayList<>();
- for (NotificationSection s : sections) {
- int filter = s.getBucket();
- viewsInBucket.clear();
-
- //TODO: do this in a single pass, and more better
- for (ActivatableNotificationView v : children) {
- Integer bucket = getBucket(v);
- if (bucket == null) {
- throw new IllegalArgumentException("Cannot find section bucket for view");
- }
-
- if (bucket == filter) {
- viewsInBucket.add(v);
- }
-
- if (viewsInBucket.size() >= 1) {
- changed |= s.setFirstVisibleChild(viewsInBucket.get(0));
- changed |= s.setLastVisibleChild(viewsInBucket.get(viewsInBucket.size() - 1));
- } else {
- changed |= s.setFirstVisibleChild(null);
- changed |= s.setLastVisibleChild(null);
- }
- }
- }
-
- if (DEBUG) {
- logSections(sections);
- }
-
- return changed;
- }
-
- private void logSections(NotificationSection[] sections) {
- for (int i = 0; i < sections.length; i++) {
- NotificationSection s = sections[i];
- ActivatableNotificationView first = s.getFirstVisibleChild();
- String fs = first == null ? "(null)"
- : (first instanceof ExpandableNotificationRow)
- ? ((ExpandableNotificationRow) first).getEntry().getKey()
- : Integer.toHexString(System.identityHashCode(first));
- ActivatableNotificationView last = s.getLastVisibleChild();
- String ls = last == null ? "(null)"
- : (last instanceof ExpandableNotificationRow)
- ? ((ExpandableNotificationRow) last).getEntry().getKey()
- : Integer.toHexString(System.identityHashCode(last));
- android.util.Log.d(TAG, "updateSections: f=" + fs + " s=" + i);
- android.util.Log.d(TAG, "updateSections: l=" + ls + " s=" + i);
- }
- }
-
- @VisibleForTesting
- ExpandableView getGentleHeaderView() {
- return mGentleHeader;
- }
-
- @VisibleForTesting
- ExpandableView getAlertingHeaderView() {
- return mAlertingHeader;
- }
-
- @VisibleForTesting
- ExpandableView getPeopleHeaderView() {
- return mPeopleHubView;
- }
-
- @VisibleForTesting
- ExpandableView getMediaControlsView() {
- return mMediaControlsView;
- }
-
- @VisibleForTesting
- ExpandableView getIncomingHeaderView() {
- return mIncomingHeader;
- }
-
- @VisibleForTesting
- void setPeopleHubVisible(boolean visible) {
- mPeopleHubVisible = visible;
- }
-
- private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
- @Override
- public void onLocaleListChanged() {
- reinflateViews(LayoutInflater.from(mParent.getContext()));
- }
- };
-
- private void onGentleHeaderClick(View v) {
- Intent intent = new Intent(Settings.ACTION_NOTIFICATION_SETTINGS);
- mActivityStarter.startActivity(
- intent,
- true,
- true,
- Intent.FLAG_ACTIVITY_SINGLE_TOP);
- }
-
- private void onClearGentleNotifsClick(View v) {
- if (mOnClearGentleNotifsClickListener != null) {
- mOnClearGentleNotifsClickListener.onClick(v);
- }
- }
-
- void hidePeopleRow() {
- mPeopleHubVisible = false;
- updateSectionBoundaries("PeopleHub dismissed");
- }
-
- void setHeaderForegroundColor(@ColorInt int color) {
- mPeopleHubView.setTextColor(color);
- mGentleHeader.setForegroundColor(color);
- mAlertingHeader.setForegroundColor(color);
- }
-
- /**
- * For now, declare the available notification buckets (sections) here so that other
- * presentation code can decide what to do based on an entry's buckets
- */
- @Retention(SOURCE)
- @IntDef(prefix = { "BUCKET_" }, value = {
- BUCKET_HEADS_UP,
- BUCKET_FOREGROUND_SERVICE,
- BUCKET_MEDIA_CONTROLS,
- BUCKET_PEOPLE,
- BUCKET_ALERTING,
- BUCKET_SILENT
- })
- public @interface PriorityBucket {}
- public static final int BUCKET_HEADS_UP = 0;
- public static final int BUCKET_FOREGROUND_SERVICE = 1;
- public static final int BUCKET_MEDIA_CONTROLS = 2;
- public static final int BUCKET_PEOPLE = 3;
- public static final int BUCKET_ALERTING = 4;
- public static final int BUCKET_SILENT = 5;
-}
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
new file mode 100644
index 0000000..e39a4a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.stack
+
+import android.annotation.ColorInt
+import android.annotation.IntDef
+import android.annotation.LayoutRes
+import android.content.Intent
+import android.provider.Settings
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.media.KeyguardMediaController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.people.DataListener
+import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter
+import com.android.systemui.statusbar.notification.people.PeopleHubViewBoundary
+import com.android.systemui.statusbar.notification.people.PersonViewModel
+import com.android.systemui.statusbar.notification.people.Subscription
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.row.StackScrollerDecorView
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.children
+import com.android.systemui.util.foldToSparseArray
+import javax.inject.Inject
+
+/**
+ * Manages the boundaries of the two notification sections (high priority and low priority). Also
+ * shows/hides the headers for those sections where appropriate.
+ *
+ * TODO: Move remaining sections logic from NSSL into this class.
+ */
+class NotificationSectionsManager @Inject internal constructor(
+ private val activityStarter: ActivityStarter,
+ private val statusBarStateController: StatusBarStateController,
+ private val configurationController: ConfigurationController,
+ private val peopleHubViewAdapter: PeopleHubViewAdapter,
+ private val keyguardMediaController: KeyguardMediaController,
+ private val sectionsFeatureManager: NotificationSectionsFeatureManager,
+ private val logger: NotificationSectionsLogger
+) : SectionProvider {
+
+ private val configurationListener = object : ConfigurationController.ConfigurationListener {
+ override fun onLocaleListChanged() {
+ reinflateViews(LayoutInflater.from(parent.context))
+ }
+ }
+
+ private val peopleHubViewBoundary: PeopleHubViewBoundary = object : PeopleHubViewBoundary {
+ override fun setVisible(isVisible: Boolean) {
+ if (peopleHubVisible != isVisible) {
+ peopleHubVisible = isVisible
+ if (initialized) {
+ updateSectionBoundaries("PeopleHub visibility changed")
+ }
+ }
+ }
+
+ override val associatedViewForClickAnimation: View
+ get() = peopleHeaderView!!
+
+ override val personViewAdapters: Sequence<DataListener<PersonViewModel?>>
+ get() = peopleHeaderView!!.personViewAdapters
+ }
+
+ private lateinit var parent: NotificationStackScrollLayout
+ private var initialized = false
+ private var onClearSilentNotifsClickListener: View.OnClickListener? = null
+
+ @get:VisibleForTesting
+ var silentHeaderView: SectionHeaderView? = null
+ private set
+
+ @get:VisibleForTesting
+ var alertingHeaderView: SectionHeaderView? = null
+ private set
+
+ @get:VisibleForTesting
+ var incomingHeaderView: SectionHeaderView? = null
+ private set
+
+ @get:VisibleForTesting
+ var peopleHeaderView: PeopleHubView? = null
+ private set
+
+ @set:VisibleForTesting
+ var peopleHubVisible = false
+ private var peopleHubSubscription: Subscription? = null
+
+ @get:VisibleForTesting
+ var mediaControlsView: MediaHeaderView? = null
+ private set
+
+ /** Must be called before use. */
+ fun initialize(parent: NotificationStackScrollLayout, layoutInflater: LayoutInflater) {
+ check(!initialized) { "NotificationSectionsManager already initialized" }
+ initialized = true
+ this.parent = parent
+ reinflateViews(layoutInflater)
+ configurationController.addCallback(configurationListener)
+ }
+
+ private fun <T : ExpandableView> reinflateView(
+ view: T?,
+ layoutInflater: LayoutInflater,
+ @LayoutRes layoutResId: Int
+ ): T {
+ var oldPos = -1
+ view?.let {
+ view.transientContainer?.removeView(view)
+ if (view.parent === parent) {
+ oldPos = parent.indexOfChild(view)
+ parent.removeView(view)
+ }
+ }
+ val inflated = layoutInflater.inflate(layoutResId, parent, false) as T
+ if (oldPos != -1) {
+ parent.addView(inflated, oldPos)
+ }
+ return inflated
+ }
+
+ fun createSectionsForBuckets(): Array<NotificationSection> =
+ sectionsFeatureManager.getNotificationBuckets()
+ .map { NotificationSection(parent, it) }
+ .toTypedArray()
+
+ /**
+ * Reinflates the entire notification header, including all decoration views.
+ */
+ fun reinflateViews(layoutInflater: LayoutInflater) {
+ silentHeaderView = reinflateView(
+ silentHeaderView, layoutInflater, R.layout.status_bar_notification_section_header
+ ).apply {
+ setHeaderText(R.string.notification_section_header_gentle)
+ setOnHeaderClickListener { onGentleHeaderClick() }
+ setOnClearAllClickListener { onClearGentleNotifsClick(it) }
+ }
+ alertingHeaderView = reinflateView(
+ alertingHeaderView, layoutInflater, R.layout.status_bar_notification_section_header
+ ).apply {
+ setHeaderText(R.string.notification_section_header_alerting)
+ setOnHeaderClickListener { onGentleHeaderClick() }
+ }
+ peopleHubSubscription?.unsubscribe()
+ peopleHubSubscription = null
+ peopleHeaderView = reinflateView(peopleHeaderView, layoutInflater, R.layout.people_strip)
+ if (ENABLE_SNOOZED_CONVERSATION_HUB) {
+ peopleHubSubscription = peopleHubViewAdapter.bindView(peopleHubViewBoundary)
+ }
+ incomingHeaderView = reinflateView(
+ incomingHeaderView, layoutInflater, R.layout.status_bar_notification_section_header
+ ).apply {
+ setHeaderText(R.string.notification_section_header_incoming)
+ setOnHeaderClickListener { onGentleHeaderClick() }
+ }
+ mediaControlsView =
+ reinflateView(mediaControlsView, layoutInflater, R.layout.keyguard_media_header)
+ .also(keyguardMediaController::attach)
+ }
+
+ override fun beginsSection(view: View, previous: View?): Boolean =
+ view === silentHeaderView ||
+ view === mediaControlsView ||
+ view === peopleHeaderView ||
+ view === alertingHeaderView ||
+ view === incomingHeaderView ||
+ getBucket(view) != getBucket(previous)
+
+ private fun getBucket(view: View?): Int? = when {
+ view === silentHeaderView -> BUCKET_SILENT
+ view === incomingHeaderView -> BUCKET_HEADS_UP
+ view === mediaControlsView -> BUCKET_MEDIA_CONTROLS
+ view === peopleHeaderView -> BUCKET_PEOPLE
+ view === alertingHeaderView -> BUCKET_ALERTING
+ view is ExpandableNotificationRow -> view.entry.bucket
+ else -> null
+ }
+
+ private fun logShadeContents() = parent.children.forEachIndexed { i, child ->
+ when {
+ child === incomingHeaderView -> logger.logIncomingHeader(i)
+ child === mediaControlsView -> logger.logMediaControls(i)
+ child === peopleHeaderView -> logger.logConversationsHeader(i)
+ child === alertingHeaderView -> logger.logAlertingHeader(i)
+ child === silentHeaderView -> logger.logSilentHeader(i)
+ child !is ExpandableNotificationRow -> logger.logOther(i, child.javaClass)
+ else -> {
+ val isHeadsUp = child.isHeadsUp
+ when (child.entry.bucket) {
+ BUCKET_HEADS_UP -> logger.logHeadsUp(i, isHeadsUp)
+ BUCKET_PEOPLE -> logger.logConversation(i, isHeadsUp)
+ BUCKET_ALERTING -> logger.logAlerting(i, isHeadsUp)
+ BUCKET_SILENT -> logger.logSilent(i, isHeadsUp)
+ }
+ }
+ }
+ }
+
+ private val isUsingMultipleSections: Boolean
+ get() = sectionsFeatureManager.getNumberOfBuckets() > 1
+
+ @VisibleForTesting
+ fun updateSectionBoundaries() = updateSectionBoundaries("test")
+
+ /**
+ * Should be called whenever notifs are added, removed, or updated. Updates section boundary
+ * bookkeeping and adds/moves/removes section headers if appropriate.
+ */
+ fun updateSectionBoundaries(reason: String) {
+ if (!isUsingMultipleSections) {
+ return
+ }
+ logger.logStartSectionUpdate(reason)
+
+ // The overall strategy here is to iterate over the current children of mParent, looking
+ // for where the sections headers are currently positioned, and where each section begins.
+ // Then, once we find the start of a new section, we track that position as the "target" for
+ // the section header, adjusted for the case where existing headers are in front of that
+ // target, but won't be once they are moved / removed after the pass has completed.
+ val showHeaders = statusBarStateController.state != StatusBarState.KEYGUARD
+ val usingPeopleFiltering = sectionsFeatureManager.isFilteringEnabled()
+ val usingMediaControls = sectionsFeatureManager.isMediaControlsEnabled()
+
+ var peopleNotifsPresent = false
+ var currentMediaControlsIdx = -1
+ val mediaControlsTarget = if (usingMediaControls) 0 else -1
+ var currentIncomingHeaderIdx = -1
+ var incomingHeaderTarget = -1
+ var currentPeopleHeaderIdx = -1
+ var peopleHeaderTarget = -1
+ var currentAlertingHeaderIdx = -1
+ var alertingHeaderTarget = -1
+ var currentGentleHeaderIdx = -1
+ var gentleHeaderTarget = -1
+
+ var lastNotifIndex = 0
+ var lastIncomingIndex = -1
+ var prev: ExpandableNotificationRow? = null
+
+ for ((i, child) in parent.children.withIndex()) {
+ when {
+ // Track the existing positions of the headers
+ child === incomingHeaderView -> {
+ logger.logIncomingHeader(i)
+ currentIncomingHeaderIdx = i
+ }
+ child === mediaControlsView -> {
+ logger.logMediaControls(i)
+ currentMediaControlsIdx = i
+ }
+ child === peopleHeaderView -> {
+ logger.logConversationsHeader(i)
+ currentPeopleHeaderIdx = i
+ }
+ child === alertingHeaderView -> {
+ logger.logAlertingHeader(i)
+ currentAlertingHeaderIdx = i
+ }
+ child === silentHeaderView -> {
+ logger.logSilentHeader(i)
+ currentGentleHeaderIdx = i
+ }
+ child !is ExpandableNotificationRow -> logger.logOther(i, child.javaClass)
+ else -> {
+ lastNotifIndex = i
+ // Is there a section discontinuity? This usually occurs due to HUNs
+ if (prev?.entry?.bucket?.let { it > child.entry.bucket } == true) {
+ // Remove existing headers, and move the Incoming header if necessary
+ if (alertingHeaderTarget != -1) {
+ if (showHeaders && incomingHeaderTarget != -1) {
+ incomingHeaderTarget = alertingHeaderTarget
+ }
+ alertingHeaderTarget = -1
+ }
+ if (peopleHeaderTarget != -1) {
+ if (showHeaders && incomingHeaderTarget != -1) {
+ incomingHeaderTarget = peopleHeaderTarget
+ }
+ peopleHeaderTarget = -1
+ }
+ if (showHeaders && incomingHeaderTarget == -1) {
+ incomingHeaderTarget = 0
+ }
+ // Walk backwards changing all previous notifications to the Incoming
+ // section
+ for (j in i - 1 downTo lastIncomingIndex + 1) {
+ val prevChild = parent.getChildAt(j)
+ if (prevChild is ExpandableNotificationRow) {
+ prevChild.entry.bucket = BUCKET_HEADS_UP
+ }
+ }
+ // Track the new bottom of the Incoming section
+ lastIncomingIndex = i - 1
+ }
+ val isHeadsUp = child.isHeadsUp
+ when (child.entry.bucket) {
+ BUCKET_FOREGROUND_SERVICE -> logger.logForegroundService(i, isHeadsUp)
+ BUCKET_PEOPLE -> {
+ logger.logConversation(i, isHeadsUp)
+ peopleNotifsPresent = true
+ if (showHeaders && peopleHeaderTarget == -1) {
+ peopleHeaderTarget = i
+ // Offset the target if there are other headers before this that
+ // will be moved.
+ if (currentPeopleHeaderIdx != -1) {
+ peopleHeaderTarget--
+ }
+ if (currentAlertingHeaderIdx != -1) {
+ peopleHeaderTarget--
+ }
+ if (currentGentleHeaderIdx != -1) {
+ peopleHeaderTarget--
+ }
+ }
+ }
+ BUCKET_ALERTING -> {
+ logger.logAlerting(i, isHeadsUp)
+ if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
+ alertingHeaderTarget = i
+ // Offset the target if there are other headers before this that
+ // will be moved.
+ if (currentAlertingHeaderIdx != -1) {
+ alertingHeaderTarget--
+ }
+ if (currentGentleHeaderIdx != -1) {
+ alertingHeaderTarget--
+ }
+ }
+ }
+ BUCKET_SILENT -> {
+ logger.logSilent(i, isHeadsUp)
+ if (showHeaders && gentleHeaderTarget == -1) {
+ gentleHeaderTarget = i
+ // Offset the target if there are other headers before this that
+ // will be moved.
+ if (currentGentleHeaderIdx != -1) {
+ gentleHeaderTarget--
+ }
+ }
+ }
+ }
+
+ prev = child
+ }
+ }
+ }
+
+ if (showHeaders && usingPeopleFiltering && peopleHubVisible && peopleHeaderTarget == -1) {
+ // Insert the people header even if there are no people visible, in order to show
+ // the hub. Put it directly above the next header.
+ peopleHeaderTarget = when {
+ alertingHeaderTarget != -1 -> alertingHeaderTarget
+ gentleHeaderTarget != -1 -> gentleHeaderTarget
+ else -> lastNotifIndex // Put it at the end of the list.
+ }
+ // Offset the target to account for the current position of the people header.
+ if (currentPeopleHeaderIdx != -1 && currentPeopleHeaderIdx < peopleHeaderTarget) {
+ peopleHeaderTarget--
+ }
+ }
+
+ logger.logStr("New header target positions:")
+ logger.logIncomingHeader(incomingHeaderTarget)
+ logger.logMediaControls(mediaControlsTarget)
+ logger.logConversationsHeader(peopleHeaderTarget)
+ logger.logAlertingHeader(alertingHeaderTarget)
+ logger.logSilentHeader(gentleHeaderTarget)
+
+ // Add headers in reverse order to preserve indices
+ silentHeaderView?.let {
+ adjustHeaderVisibilityAndPosition(gentleHeaderTarget, it, currentGentleHeaderIdx)
+ }
+ alertingHeaderView?.let {
+ adjustHeaderVisibilityAndPosition(alertingHeaderTarget, it, currentAlertingHeaderIdx)
+ }
+ peopleHeaderView?.let {
+ adjustHeaderVisibilityAndPosition(peopleHeaderTarget, it, currentPeopleHeaderIdx)
+ }
+ incomingHeaderView?.let {
+ adjustHeaderVisibilityAndPosition(incomingHeaderTarget, it, currentIncomingHeaderIdx)
+ }
+ mediaControlsView?.let {
+ adjustViewPosition(mediaControlsTarget, it, currentMediaControlsIdx)
+ }
+
+ logger.logStr("Final order:")
+ logShadeContents()
+ logger.logStr("Section boundary update complete")
+
+ // Update headers to reflect state of section contents
+ silentHeaderView?.setAreThereDismissableGentleNotifs(
+ parent.hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE)
+ )
+ peopleHeaderView?.canSwipe = showHeaders && peopleHubVisible && !peopleNotifsPresent
+ if (peopleHeaderTarget != currentPeopleHeaderIdx) {
+ peopleHeaderView?.resetTranslation()
+ }
+ }
+
+ private fun adjustHeaderVisibilityAndPosition(
+ targetPosition: Int,
+ header: StackScrollerDecorView,
+ currentPosition: Int
+ ) {
+ adjustViewPosition(targetPosition, header, currentPosition)
+ if (targetPosition != -1 && currentPosition == -1) {
+ header.isContentVisible = true
+ }
+ }
+
+ private fun adjustViewPosition(
+ targetPosition: Int,
+ view: ExpandableView,
+ currentPosition: Int
+ ) {
+ if (targetPosition == -1) {
+ if (currentPosition != -1) {
+ parent.removeView(view)
+ }
+ } else {
+ if (currentPosition == -1) {
+ // If the header is animating away, it will still have a parent, so detach it first
+ // TODO: We should really cancel the active animations here. This will happen
+ // automatically when the view's intro animation starts, but it's a fragile link.
+ view.transientContainer?.removeTransientView(view)
+ view.transientContainer = null
+ parent.addView(view, targetPosition)
+ } else {
+ parent.changeViewPosition(view, targetPosition)
+ }
+ }
+ }
+
+ private sealed class SectionBounds {
+
+ data class Many(
+ val first: ActivatableNotificationView,
+ val last: ActivatableNotificationView
+ ) : SectionBounds()
+
+ data class One(val lone: ActivatableNotificationView) : SectionBounds()
+ object None : SectionBounds()
+
+ fun addNotif(notif: ActivatableNotificationView): SectionBounds = when (this) {
+ is None -> One(notif)
+ is One -> Many(lone, notif)
+ is Many -> copy(last = notif)
+ }
+
+ fun updateSection(section: NotificationSection): Boolean = when (this) {
+ is None -> section.setFirstAndLastVisibleChildren(null, null)
+ is One -> section.setFirstAndLastVisibleChildren(lone, lone)
+ is Many -> section.setFirstAndLastVisibleChildren(first, last)
+ }
+
+ private fun NotificationSection.setFirstAndLastVisibleChildren(
+ first: ActivatableNotificationView?,
+ last: ActivatableNotificationView?
+ ): Boolean {
+ val firstChanged = setFirstVisibleChild(first)
+ val lastChanged = setLastVisibleChild(last)
+ return firstChanged || lastChanged
+ }
+ }
+
+ /**
+ * Updates the boundaries (as tracked by their first and last views) of the priority sections.
+ *
+ * @return `true` If the last view in the top section changed (so we need to animate).
+ */
+ fun updateFirstAndLastViewsForAllSections(
+ sections: Array<NotificationSection>,
+ children: List<ActivatableNotificationView>
+ ): Boolean {
+ // Create mapping of bucket to section
+ val sectionBounds = children.asSequence()
+ // Group children by bucket
+ .groupingBy {
+ getBucket(it)
+ ?: throw IllegalArgumentException("Cannot find section bucket for view")
+ }
+ // Combine each bucket into a SectionBoundary
+ .foldToSparseArray(
+ SectionBounds.None,
+ size = sections.size,
+ operation = SectionBounds::addNotif
+ )
+ // Update each section with the associated boundary, tracking if there was a change
+ val changed = sections.fold(false) { changed, section ->
+ val bounds = sectionBounds[section.bucket] ?: SectionBounds.None
+ bounds.updateSection(section) || changed
+ }
+ if (DEBUG) {
+ logSections(sections)
+ }
+ return changed
+ }
+
+ private fun logSections(sections: Array<NotificationSection>) {
+ for (i in sections.indices) {
+ val s = sections[i]
+ val fs = when (val first = s.firstVisibleChild) {
+ null -> "(null)"
+ is ExpandableNotificationRow -> first.entry.key
+ else -> Integer.toHexString(System.identityHashCode(first))
+ }
+ val ls = when (val last = s.lastVisibleChild) {
+ null -> "(null)"
+ is ExpandableNotificationRow -> last.entry.key
+ else -> Integer.toHexString(System.identityHashCode(last))
+ }
+ Log.d(TAG, "updateSections: f=$fs s=$i")
+ Log.d(TAG, "updateSections: l=$ls s=$i")
+ }
+ }
+
+ private fun onGentleHeaderClick() {
+ val intent = Intent(Settings.ACTION_NOTIFICATION_SETTINGS)
+ activityStarter.startActivity(
+ intent,
+ true,
+ true,
+ Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ }
+
+ private fun onClearGentleNotifsClick(v: View) {
+ onClearSilentNotifsClickListener?.onClick(v)
+ }
+
+ /** Listener for when the "clear all" button is clicked on the gentle notification header. */
+ fun setOnClearSilentNotifsClickListener(listener: View.OnClickListener) {
+ onClearSilentNotifsClickListener = listener
+ }
+
+ fun hidePeopleRow() {
+ peopleHubVisible = false
+ updateSectionBoundaries("PeopleHub dismissed")
+ }
+
+ fun setHeaderForegroundColor(@ColorInt color: Int) {
+ peopleHeaderView?.setTextColor(color)
+ silentHeaderView?.setForegroundColor(color)
+ alertingHeaderView?.setForegroundColor(color)
+ }
+
+ companion object {
+ private const val TAG = "NotifSectionsManager"
+ private const val DEBUG = false
+ private const val ENABLE_SNOOZED_CONVERSATION_HUB = false
+ }
+}
+
+/**
+ * For now, declare the available notification buckets (sections) here so that other
+ * presentation code can decide what to do based on an entry's buckets
+ */
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+ prefix = ["BUCKET_"],
+ value = [
+ BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE,
+ BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT
+ ]
+)
+annotation class PriorityBucket
+
+const val BUCKET_UNKNOWN = 0
+const val BUCKET_MEDIA_CONTROLS = 1
+const val BUCKET_HEADS_UP = 2
+const val BUCKET_FOREGROUND_SERVICE = 3
+const val BUCKET_PEOPLE = 4
+const val BUCKET_ALERTING = 5
+const val BUCKET_SILENT = 6
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 1ccc2bd..3db4b6f 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
@@ -21,7 +21,7 @@
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
@@ -577,7 +577,7 @@
mSectionsManager = notificationSectionsManager;
mSectionsManager.initialize(this, LayoutInflater.from(context));
- mSectionsManager.setOnClearGentleNotifsClickListener(v -> {
+ mSectionsManager.setOnClearSilentNotifsClickListener(v -> {
// Leave the shade open if there will be other notifs left over to clear
final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
clearNotifications(ROWS_GENTLE, closeShade);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index d0a872e..80785db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
@@ -455,7 +454,7 @@
* If there is a {@link NotificationGroup} associated with the provided entry, this method
* will update the suppression of that group.
*/
- public void updateSuppression(@NonNull final NotificationEntry entry) {
+ public void updateSuppression(NotificationEntry entry) {
NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
if (group != null) {
updateSuppression(group);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index 926c1bd..2e4a929d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -321,7 +321,8 @@
|| state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
|| state.mHeadsUpShowing
|| state.mScrimsVisibility != ScrimController.TRANSPARENT)
- || state.mBackgroundBlurRadius > 0;
+ || state.mBackgroundBlurRadius > 0
+ || state.mLaunchingActivity;
}
private void applyFitsSystemWindows(State state) {
@@ -485,6 +486,11 @@
apply(mCurrentState);
}
+ void setLaunchingActivity(boolean launching) {
+ mCurrentState.mLaunchingActivity = launching;
+ apply(mCurrentState);
+ }
+
public void setScrimsVisibility(int scrimsVisibility) {
mCurrentState.mScrimsVisibility = scrimsVisibility;
apply(mCurrentState);
@@ -645,6 +651,7 @@
boolean mForceCollapsed;
boolean mForceDozeBrightness;
boolean mForceUserActivity;
+ boolean mLaunchingActivity;
boolean mBackdropShowing;
boolean mWallpaperSupportsAmbientMode;
boolean mNotTouchable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 596a607..42222d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -93,6 +93,7 @@
private PhoneStatusBarView mStatusBarView;
private PhoneStatusBarTransitions mBarTransitions;
private StatusBar mService;
+ private NotificationShadeWindowController mNotificationShadeWindowController;
private DragDownHelper mDragDownHelper;
private boolean mDoubleTapEnabled;
private boolean mSingleTapEnabled;
@@ -429,11 +430,19 @@
}
public void setExpandAnimationPending(boolean pending) {
- mExpandAnimationPending = pending;
+ if (mExpandAnimationPending != pending) {
+ mExpandAnimationPending = pending;
+ mNotificationShadeWindowController
+ .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
+ }
}
public void setExpandAnimationRunning(boolean running) {
- mExpandAnimationRunning = running;
+ if (mExpandAnimationRunning != running) {
+ mExpandAnimationRunning = running;
+ mNotificationShadeWindowController
+ .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
+ }
}
public void cancelExpandHelper() {
@@ -456,8 +465,9 @@
}
}
- public void setService(StatusBar statusBar) {
+ public void setService(StatusBar statusBar, NotificationShadeWindowController controller) {
mService = statusBar;
+ mNotificationShadeWindowController = controller;
}
@VisibleForTesting
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 ba32327..de9c745 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.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 @@
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 @@
}
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 @@
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 @@
}
};
}
+
+ 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/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index bbf83bc..19de191 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1001,7 +1001,7 @@
updateTheme();
inflateStatusBarWindow();
- mNotificationShadeWindowViewController.setService(this);
+ mNotificationShadeWindowViewController.setService(this, mNotificationShadeWindowController);
mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
@@ -1488,7 +1488,7 @@
if (mDividerOptional.isPresent()) {
divider = mDividerOptional.get();
}
- if (divider == null || !divider.inSplitMode()) {
+ if (divider == null || !divider.isDividerVisible()) {
final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId);
if (navbarPos == NAV_BAR_POS_INVALID) {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 44ece35..7924348 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -429,7 +429,9 @@
}
int launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
null, null, getActivityOptions(adapter));
- mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent);
+ mMainThreadHandler.post(() -> {
+ mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent);
+ });
} catch (RemoteException | PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
@@ -449,10 +451,11 @@
mActivityLaunchAnimator.getLaunchAnimation(
row, mStatusBar.isOccluded())),
new UserHandle(UserHandle.getUserId(appUid)));
- mActivityLaunchAnimator.setLaunchResult(launchResult, true /* isActivityIntent */);
// Putting it back on the main thread, since we're touching views
mMainThreadHandler.post(() -> {
+ mActivityLaunchAnimator.setLaunchResult(launchResult,
+ true /* isActivityIntent */);
removeHUN(row);
});
if (shouldCollapse()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 428de9e..669e6a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -37,6 +37,7 @@
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -73,6 +74,7 @@
private View mPendingRemoteInputView;
private KeyguardManager mKeyguardManager;
private final CommandQueue mCommandQueue;
+ private final ActionClickLogger mActionClickLogger;
private int mDisabled2;
protected BroadcastReceiver mChallengeReceiver = new ChallengeReceiver();
private Handler mMainHandler = new Handler();
@@ -87,7 +89,8 @@
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
ActivityStarter activityStarter, ShadeController shadeController,
- CommandQueue commandQueue) {
+ CommandQueue commandQueue,
+ ActionClickLogger clickLogger) {
mContext = context;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mShadeController = shadeController;
@@ -101,6 +104,7 @@
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mCommandQueue = commandQueue;
mCommandQueue.addCallback(this);
+ mActionClickLogger = clickLogger;
mActivityIntentHelper = new ActivityIntentHelper(mContext);
mGroupManager = groupManager;
// Listen to onKeyguardShowingChanged in case a managed profile needs to be unlocked
@@ -304,9 +308,12 @@
NotificationRemoteInputManager.ClickHandler defaultHandler) {
final boolean isActivity = pendingIntent.isActivity();
if (isActivity) {
+ mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent);
final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId());
mActivityStarter.dismissKeyguardThenExecute(() -> {
+ mActionClickLogger.logKeyguardGone(pendingIntent);
+
try {
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
new file mode 100644
index 0000000..c91033e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -0,0 +1,25 @@
+/*
+ * 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
+
+import android.view.ViewGroup
+
+/** [Sequence] that yields all of the direct children of this [ViewGroup] */
+val ViewGroup.children
+ get() = sequence {
+ for (i in 0 until childCount) yield(getChildAt(i))
+ }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt b/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt
new file mode 100644
index 0000000..accb81e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt
@@ -0,0 +1,136 @@
+/*
+ * 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
+
+import android.util.SparseArray
+
+/**
+ * Transforms an [Array] into a [SparseArray], by applying each element to [keySelector] in order to
+ * generate the index at which it will be placed. If two elements produce the same index, the latter
+ * replaces the former in the final result.
+ *
+ * See [Array.associateBy].
+ */
+inline fun <T> Array<T>.associateByToSparseArray(
+ crossinline keySelector: (T) -> Int
+): SparseArray<T> {
+ val sparseArray = SparseArray<T>(size)
+ for (value in this) {
+ sparseArray.put(keySelector(value), value)
+ }
+ return sparseArray
+}
+
+/**
+ * Folds a [Grouping] into a [SparseArray]. See [Grouping.fold].
+ */
+inline fun <T, R> Grouping<T, Int>.foldToSparseArray(
+ initial: R,
+ size: Int = -1,
+ crossinline operation: (R, T) -> R
+): SparseArray<R> {
+ val sparseArray = when {
+ size < 0 -> SparseArray<R>()
+ else -> SparseArray<R>(size)
+ }
+ sourceIterator().forEach { elem ->
+ val key = keyOf(elem)
+ val acc = sparseArray.get(key) ?: initial
+ sparseArray.put(key, operation(acc, elem))
+ }
+ return sparseArray
+}
+
+/**
+ * Wraps this [SparseArray] into an immutable [Map], the methods of which forward to this
+ * [SparseArray].
+ */
+fun <T> SparseArray<T>.asMap(): Map<Int, T> = SparseArrayMapWrapper(this)
+
+private class SparseArrayMapWrapper<T>(
+ private val sparseArray: SparseArray<T>
+) : Map<Int, T> {
+
+ private data class Entry<T>(override val key: Int, override val value: T) : Map.Entry<Int, T>
+
+ private val entrySequence = sequence {
+ val size = sparseArray.size()
+ for (i in 0 until size) {
+ val key = sparseArray.keyAt(i)
+ val value = sparseArray.get(key)
+ yield(Entry(key, value))
+ }
+ }
+
+ override val entries: Set<Map.Entry<Int, T>>
+ get() = object : Set<Map.Entry<Int, T>> {
+ override val size: Int
+ get() = this@SparseArrayMapWrapper.size
+
+ override fun contains(element: Map.Entry<Int, T>): Boolean =
+ sparseArray[element.key]?.let { it == element.value } == true
+
+ override fun containsAll(elements: Collection<Map.Entry<Int, T>>): Boolean =
+ elements.all { contains(it) }
+
+ override fun isEmpty(): Boolean = size == 0
+
+ override fun iterator(): Iterator<Map.Entry<Int, T>> = entrySequence.iterator()
+ }
+
+ override val keys: Set<Int> = object : Set<Int> {
+ private val keySequence = entrySequence.map { it.key }
+
+ override val size: Int
+ get() = this@SparseArrayMapWrapper.size
+
+ override fun contains(element: Int): Boolean = containsKey(element)
+
+ override fun containsAll(elements: Collection<Int>): Boolean =
+ elements.all { contains(it) }
+
+ override fun isEmpty(): Boolean = size == 0
+
+ override fun iterator(): Iterator<Int> = keySequence.iterator()
+ }
+ override val size: Int
+ get() = sparseArray.size()
+ override val values: Collection<T>
+ get() = object : Collection<T> {
+ private val valueSequence = entrySequence.map { it.value }
+
+ override val size: Int
+ get() = this@SparseArrayMapWrapper.size
+
+ override fun contains(element: T): Boolean = containsValue(element)
+
+ override fun containsAll(elements: Collection<T>): Boolean =
+ elements.all { contains(it) }
+
+ override fun isEmpty(): Boolean = this@SparseArrayMapWrapper.isEmpty()
+
+ override fun iterator(): Iterator<T> = valueSequence.iterator()
+ }
+
+ override fun containsKey(key: Int): Boolean = sparseArray.contains(key)
+
+ override fun containsValue(value: T): Boolean = sparseArray.indexOfValue(value) >= 0
+
+ override fun get(key: Int): T? = sparseArray.get(key)
+
+ override fun isEmpty(): Boolean = sparseArray.size() == 0
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 52923ab..96e868d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -317,7 +317,7 @@
verify(mNotificationEntryManager).updateNotifications(any());
mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
@@ -329,7 +329,7 @@
mBubbleController.updateBubble(mRow2.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
Bubble b = mBubbleData.getOverflowBubbleWithKey(mRow.getEntry().getKey());
assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(b));
@@ -350,10 +350,9 @@
mBubbleController.updateBubble(mRow.getEntry(), /* suppressFlyout */
false, /* showInShade */ true);
mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
- mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+ mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL);
verify(mNotificationEntryManager, times(1)).performRemoveNotification(
eq(mRow.getEntry().getSbn()), anyInt());
assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
@@ -366,7 +365,7 @@
assertTrue(mBubbleController.hasBubbles());
mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_USER_CHANGED);
+ mRow.getEntry(), BubbleController.DISMISS_USER_CHANGED);
verify(mNotificationEntryManager, never()).performRemoveNotification(
eq(mRow.getEntry().getSbn()), anyInt());
assertFalse(mBubbleController.hasBubbles());
@@ -564,8 +563,7 @@
// Dismiss currently expanded
mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey())
- .getEntry().getKey(),
+ mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
BubbleController.DISMISS_USER_GESTURE);
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
@@ -576,8 +574,7 @@
// Dismiss that one
mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey())
- .getEntry().getKey(),
+ mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
BubbleController.DISMISS_USER_GESTURE);
// Make sure state changes and collapse happens
@@ -703,7 +700,7 @@
@Test
public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
- mBubbleController.removeBubble(mRow.getEntry().getKey(), BubbleController.DISMISS_AGED);
+ mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED);
verify(mDeleteIntent, never()).send();
}
@@ -711,7 +708,7 @@
public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(1)).send();
}
@@ -811,7 +808,7 @@
// Dismiss the bubble into overflow.
mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
assertFalse(mBubbleController.hasBubbles());
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
@@ -832,7 +829,7 @@
mRow.getEntry()));
mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_NO_LONGER_BUBBLE);
+ mRow.getEntry(), BubbleController.DISMISS_NO_LONGER_BUBBLE);
assertFalse(mBubbleController.hasBubbles());
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
@@ -854,12 +851,12 @@
mBubbleData.setMaxOverflowBubbles(1);
mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
assertEquals(mBubbleData.getBubbles().size(), 2);
assertEquals(mBubbleData.getOverflowBubbles().size(), 1);
mBubbleController.removeBubble(
- mRow2.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mRow2.getEntry(), BubbleController.DISMISS_USER_GESTURE);
// Overflow max of 1 is reached; mRow is oldest, so it gets removed
verify(mNotificationEntryManager, times(1)).performRemoveNotification(
mRow.getEntry().getSbn(), REASON_CANCEL);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 882504b..66f119a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -20,8 +20,11 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -177,8 +180,7 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(
- mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
// Verify
verifyUpdateReceived();
@@ -295,14 +297,12 @@
mBubbleData.setListener(mListener);
mBubbleData.setMaxOverflowBubbles(1);
- mBubbleData.notificationEntryRemoved(
- mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
// Overflow max of 1 is reached; A1 is oldest, so it gets removed
- mBubbleData.notificationEntryRemoved(
- mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
}
@@ -449,8 +449,7 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(
- mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1);
}
@@ -470,8 +469,7 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(
- mEntryB1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOrderNotChanged();
}
@@ -491,8 +489,7 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(
- mEntryA1.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA2);
}
@@ -513,14 +510,12 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(
- mEntryA1.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
// Test
- mBubbleData.notificationEntryRemoved(
- mEntryA2.getKey(), BubbleController.DISMISS_GROUP_CANCELLED);
+ mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_GROUP_CANCELLED);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of());
}
@@ -539,8 +534,7 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(
- mEntryA2.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+ mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_NOTIF_CANCEL);
verifyUpdateReceived();
assertSelectionChangedTo(mBubbleB2);
}
@@ -631,8 +625,7 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(
- mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
// Verify the selection was cleared.
verifyUpdateReceived();
@@ -786,8 +779,7 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(
- mEntryB2.getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleB1, mBubbleA2, mBubbleA1);
}
@@ -812,13 +804,11 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(
- mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertSelectionChangedTo(mBubbleA1);
- mBubbleData.notificationEntryRemoved(
- mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertSelectionChangedTo(mBubbleB1);
}
@@ -933,8 +923,7 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(
- mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertExpandedChangedTo(false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index c16801c..73b8760 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -285,8 +285,7 @@
assertTrue(mBubbleController.hasBubbles());
verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
- mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
}
@@ -303,8 +302,7 @@
mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true);
// Now remove the bubble
- mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey()));
// We don't remove the notification since the bubble is still in overflow.
@@ -324,8 +322,7 @@
mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true);
// Now remove the bubble
- mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+ mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL);
assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey()));
// Since the notif is dismissed and not in overflow, once the bubble is removed,
@@ -505,8 +502,7 @@
// Dismiss currently expanded
mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getEntry().getKey(),
+ mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
BubbleController.DISMISS_USER_GESTURE);
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
@@ -517,8 +513,7 @@
// Dismiss that one
mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getEntry().getKey(),
+ mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
BubbleController.DISMISS_USER_GESTURE);
// Make sure state changes and collapse happens
@@ -618,7 +613,7 @@
@Test
public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
- mBubbleController.removeBubble(mRow.getEntry().getKey(), BubbleController.DISMISS_AGED);
+ mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED);
verify(mDeleteIntent, never()).send();
}
@@ -626,7 +621,7 @@
public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(1)).send();
}
@@ -691,7 +686,7 @@
// Dismiss the bubble
mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
assertFalse(mBubbleController.hasBubbles());
// Dismiss the notification
@@ -712,7 +707,7 @@
// Dismiss the bubble
mBubbleController.removeBubble(
- mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+ mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL);
assertFalse(mBubbleController.hasBubbles());
// Dismiss the notification
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
new file mode 100644
index 0000000..d49d021
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
@@ -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.
+ */
+
+package com.android.systemui.bubbles.storage
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BubblePersistentRepositoryTest : SysuiTestCase() {
+
+ private val bubbles = listOf(
+ BubbleEntity(0, "com.example.messenger", "shortcut-1"),
+ BubbleEntity(10, "com.example.chat", "alice and bob"),
+ BubbleEntity(0, "com.example.messenger", "shortcut-2")
+ )
+ private lateinit var repository: BubblePersistentRepository
+
+ @Before
+ fun setup() {
+ repository = BubblePersistentRepository(mContext)
+ }
+
+ @Test
+ fun testReadWriteOperation() {
+ repository.persistsToDisk(bubbles)
+ assertEquals(bubbles, repository.readFromDisk())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
new file mode 100644
index 0000000..7acc937
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.bubbles.storage
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BubbleVolatileRepositoryTest : SysuiTestCase() {
+
+ private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1")
+ private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob")
+ private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2")
+ private val bubbles = listOf(bubble1, bubble2, bubble3)
+
+ private lateinit var repository: BubbleVolatileRepository
+
+ @Before
+ fun setup() {
+ repository = BubbleVolatileRepository()
+ }
+
+ @Test
+ fun testAddAndRemoveBubbles() {
+ repository.addBubbles(bubbles)
+ assertEquals(bubbles, repository.bubbles)
+ repository.addBubbles(listOf(bubble1))
+ assertEquals(listOf(bubble2, bubble3, bubble1), repository.bubbles)
+ repository.removeBubbles(listOf(bubble3))
+ assertEquals(listOf(bubble2, bubble1), repository.bubbles)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
new file mode 100644
index 0000000..ef4580c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.bubbles.storage
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BubbleXmlHelperTest : SysuiTestCase() {
+
+ private val bubbles = listOf(
+ BubbleEntity(0, "com.example.messenger", "shortcut-1"),
+ BubbleEntity(10, "com.example.chat", "alice and bob"),
+ BubbleEntity(0, "com.example.messenger", "shortcut-2")
+ )
+
+ @Test
+ fun testWriteXml() {
+ val expectedEntries = """
+ <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" />
+ <bb uid="10" pkg="com.example.chat" sid="alice and bob" />
+ <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" />
+ """.trimIndent()
+ ByteArrayOutputStream().use {
+ writeXml(it, bubbles)
+ val actual = it.toString()
+ assertTrue("cannot find expected entry in \n$actual",
+ actual.contains(expectedEntries))
+ }
+ }
+
+ @Test
+ fun testReadXml() {
+ val src = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <bs>
+ <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" />
+ <bb uid="10" pkg="com.example.chat" sid="alice and bob" />
+ <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" />
+ </bs>
+ """.trimIndent()
+ val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
+ assertEquals("failed parsing bubbles from xml\n$src", bubbles, actual)
+ }
+}
\ No newline at end of file
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 ee7733a..4a85980 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.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 @@
@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 @@
mControlsController,
mUiEventLogger,
mRingerModeTracker,
+ mSysUiState,
mHandler
);
mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
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 0000000..7678525
--- /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/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index e5ced0d..1bbf24f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.media
-import android.graphics.Color
import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.PlaybackState
@@ -355,25 +354,6 @@
}
@Test
- fun taskUpdatesProgress() {
- // GIVEN that the PlaybackState contins the initial position
- val initialPosition = 0L
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, initialPosition, 1f)
- build()
- }
- whenever(mockController.getPlaybackState()).thenReturn(state)
- viewModel.updateController(mockController)
- // WHEN the task runs
- with(fakeExecutor) {
- advanceClockToNext()
- runAllReady()
- }
- // THEN elapsed time has increased
- assertThat(viewModel.progress.value!!.elapsedTime).isGreaterThan(initialPosition.toInt())
- }
-
- @Test
fun startListeningQueuesPollTask() {
// GIVEN not listening
viewModel.listening = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index d124bad..a24fa84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -20,9 +20,9 @@
import static android.content.Intent.ACTION_USER_SWITCHED;
import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 1117646..5a7dea4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -82,7 +82,8 @@
() -> mock(StatusBar.class),
mStateController,
Handler.createAsync(Looper.myLooper()),
- mRemoteInputUriController);
+ mRemoteInputUriController,
+ mock(ActionClickLogger.class));
mEntry = new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_NAME)
.setOpPkg(TEST_PACKAGE_NAME)
@@ -256,17 +257,26 @@
private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
- TestableNotificationRemoteInputManager(Context context,
+ TestableNotificationRemoteInputManager(
+ Context context,
NotificationLockscreenUserManager lockscreenUserManager,
SmartReplyController smartReplyController,
NotificationEntryManager notificationEntryManager,
Lazy<StatusBar> statusBarLazy,
StatusBarStateController statusBarStateController,
Handler mainHandler,
- RemoteInputUriController remoteInputUriController) {
- super(context, lockscreenUserManager, smartReplyController, notificationEntryManager,
- statusBarLazy, statusBarStateController, mainHandler,
- remoteInputUriController);
+ RemoteInputUriController remoteInputUriController,
+ ActionClickLogger actionClickLogger) {
+ super(
+ context,
+ lockscreenUserManager,
+ smartReplyController,
+ notificationEntryManager,
+ statusBarLazy,
+ statusBarStateController,
+ mainHandler,
+ remoteInputUriController,
+ actionClickLogger);
}
public void setUpWithPresenterForTest(Callback callback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 22dc080..79507e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -92,7 +92,8 @@
mNotificationEntryManager, () -> mock(StatusBar.class),
mStatusBarStateController,
Handler.createAsync(Looper.myLooper()),
- mRemoteInputUriController);
+ mRemoteInputUriController,
+ mock(ActionClickLogger.class));
mRemoteInputManager.setUpWithCallback(mCallback, mDelegate);
mNotification = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index b4cabfd..9f47f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -21,6 +21,7 @@
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_LOW
+import android.os.SystemClock
import android.service.notification.NotificationListenerService.RankingMap
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
@@ -36,8 +37,8 @@
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationGroupManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import dagger.Lazy
@@ -58,17 +59,19 @@
private lateinit var personNotificationIdentifier: PeopleNotificationIdentifier
private lateinit var rankingManager: TestableNotificationRankingManager
private lateinit var sectionsManager: NotificationSectionsFeatureManager
+ private lateinit var notificationFilter: NotificationFilter
@Before
fun setup() {
personNotificationIdentifier =
mock(PeopleNotificationIdentifier::class.java)
sectionsManager = mock(NotificationSectionsFeatureManager::class.java)
+ notificationFilter = mock(NotificationFilter::class.java)
rankingManager = TestableNotificationRankingManager(
lazyMedia,
mock(NotificationGroupManager::class.java),
mock(HeadsUpManager::class.java),
- mock(NotificationFilter::class.java),
+ notificationFilter,
mock(NotificationEntryManagerLogger::class.java),
sectionsManager,
personNotificationIdentifier,
@@ -324,6 +327,32 @@
assertEquals(e.bucket, BUCKET_SILENT)
}
+ @Test
+ fun testFilter_resetsInitalizationTime() {
+ // GIVEN an entry that was initialized 1 second ago
+ val notif = Notification.Builder(mContext, "test") .build()
+
+ val e = NotificationEntryBuilder()
+ .setPkg("pkg")
+ .setOpPkg("pkg")
+ .setTag("tag")
+ .setNotification(notif)
+ .setUser(mContext.user)
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
+ .setOverrideGroupKey("")
+ .build()
+
+ e.setInitializationTime(SystemClock.elapsedRealtime() - 1000)
+ assertEquals(true, e.hasFinishedInitialization())
+
+ // WHEN we update ranking and filter out the notification entry
+ whenever(notificationFilter.shouldFilterOut(e)).thenReturn(true)
+ rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
+
+ // THEN the initialization time for the entry is reset
+ assertEquals(false, e.hasFinishedInitialization())
+ }
+
internal class TestableNotificationRankingManager(
mediaManager: Lazy<NotificationMediaManager>,
groupManager: NotificationGroupManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 3adc3d0..a93b54a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -19,8 +19,10 @@
import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -33,6 +35,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
@@ -475,6 +478,28 @@
}
@Test
+ public void testFilter_resetsInitalizationTime() {
+ // GIVEN a NotifFilter that filters out a specific package
+ NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1));
+ mListBuilder.addFinalizeFilter(filter1);
+
+ // GIVEN a notification that was initialized 1 second ago that will be filtered out
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(PACKAGE_1)
+ .setId(nextId(PACKAGE_1))
+ .setRank(nextRank())
+ .build();
+ entry.setInitializationTime(SystemClock.elapsedRealtime() - 1000);
+ assertTrue(entry.hasFinishedInitialization());
+
+ // WHEN the pipeline is kicked off
+ mReadyForBuildListener.onBuildList(Arrays.asList(entry));
+
+ // THEN the entry's initialization time is reset
+ assertFalse(entry.hasFinishedInitialization());
+ }
+
+ @Test
public void testNotifFiltersCanBePreempted() {
// GIVEN two notif filters
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index d39b2c2..a3a46f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.logging;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 546bce8..3dc941a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -18,11 +18,11 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
import static com.google.common.truth.Truth.assertThat;
@@ -52,6 +52,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -135,140 +136,152 @@
@Test
public void testInsertHeader() {
// GIVEN a stack with HI and LO rows but no section headers
- setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+ setStackState(
+ ALERTING,
+ ALERTING,
+ ALERTING,
+ GENTLE);
// WHEN we update the section headers
mSectionsManager.updateSectionBoundaries();
// THEN a LO section header is added
- verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3);
+ verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
}
@Test
public void testRemoveHeader() {
// GIVEN a stack that originally had a header between the HI and LO sections
- setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+ setStackState(
+ ALERTING,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN the last LO row is replaced with a HI row
setStackState(
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.GENTLE_HEADER,
- ChildType.ALERTING);
+ ALERTING,
+ ALERTING,
+ GENTLE_HEADER,
+ ALERTING);
clearInvocations(mNssl);
mSectionsManager.updateSectionBoundaries();
// THEN the LO section header is removed
- verify(mNssl).removeView(mSectionsManager.getGentleHeaderView());
+ verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
}
@Test
public void testDoNothingIfHeaderAlreadyRemoved() {
// GIVEN a stack with only HI rows
- setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING);
+ setStackState(
+ ALERTING,
+ ALERTING,
+ ALERTING);
// WHEN we update the sections headers
mSectionsManager.updateSectionBoundaries();
// THEN we don't add any section headers
- verify(mNssl, never()).addView(eq(mSectionsManager.getGentleHeaderView()), anyInt());
+ verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
}
@Test
public void testMoveHeaderForward() {
// GIVEN a stack that originally had a header between the HI and LO sections
setStackState(
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.GENTLE);
+ ALERTING,
+ ALERTING,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN the LO section moves forward
setStackState(
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.GENTLE,
- ChildType.GENTLE_HEADER,
- ChildType.GENTLE);
+ ALERTING,
+ ALERTING,
+ GENTLE,
+ GENTLE_HEADER,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// THEN the LO section header is also moved forward
- verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2);
+ verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2);
}
@Test
public void testMoveHeaderBackward() {
// GIVEN a stack that originally had a header between the HI and LO sections
setStackState(
- ChildType.ALERTING,
- ChildType.GENTLE,
- ChildType.GENTLE,
- ChildType.GENTLE);
+ ALERTING,
+ GENTLE,
+ GENTLE,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN the LO section moves backward
setStackState(
- ChildType.ALERTING,
- ChildType.GENTLE_HEADER,
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.GENTLE);
+ ALERTING,
+ GENTLE_HEADER,
+ ALERTING,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// THEN the LO section header is also moved backward (with appropriate index shifting)
- verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 3);
+ verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 3);
}
@Test
public void testHeaderRemovedFromTransientParent() {
// GIVEN a stack where the header is animating away
setStackState(
- ChildType.ALERTING,
- ChildType.GENTLE,
- ChildType.GENTLE,
- ChildType.GENTLE);
- mSectionsManager.updateSectionBoundaries();
- setStackState(
- ChildType.ALERTING,
- ChildType.GENTLE_HEADER);
+ ALERTING,
+ GENTLE_HEADER);
mSectionsManager.updateSectionBoundaries();
clearInvocations(mNssl);
ViewGroup transientParent = mock(ViewGroup.class);
- mSectionsManager.getGentleHeaderView().setTransientContainer(transientParent);
+ mSectionsManager.getSilentHeaderView().setTransientContainer(transientParent);
// WHEN the LO section reappears
setStackState(
- ChildType.ALERTING,
- ChildType.GENTLE);
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// THEN the header is first removed from the transient parent before being added to the
// NSSL.
- verify(transientParent).removeTransientView(mSectionsManager.getGentleHeaderView());
- verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 1);
+ verify(transientParent).removeTransientView(mSectionsManager.getSilentHeaderView());
+ verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 1);
}
@Test
public void testHeaderNotShownOnLockscreen() {
// GIVEN a stack of HI and LO notifs on the lockscreen
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+ setStackState(
+ ALERTING,
+ ALERTING,
+ ALERTING,
+ GENTLE);
// WHEN we update the section headers
mSectionsManager.updateSectionBoundaries();
// Then the section header is not added
- verify(mNssl, never()).addView(eq(mSectionsManager.getGentleHeaderView()), anyInt());
+ verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
}
@Test
public void testHeaderShownWhenEnterLockscreen() {
// GIVEN a stack of HI and LO notifs on the lockscreen
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+ setStackState(
+ ALERTING,
+ ALERTING,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN we unlock
@@ -276,20 +289,23 @@
mSectionsManager.updateSectionBoundaries();
// Then the section header is added
- verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3);
+ verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
}
@Test
public void testHeaderHiddenWhenEnterLockscreen() {
// GIVEN a stack of HI and LO notifs on the shade
- setStackState(ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE);
+ setStackState(
+ ALERTING,
+ GENTLE_HEADER,
+ GENTLE);
// WHEN we go back to the keyguard
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mSectionsManager.updateSectionBoundaries();
// Then the section header is removed
- verify(mNssl).removeView(mSectionsManager.getGentleHeaderView());
+ verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
}
@Test
@@ -297,13 +313,13 @@
enablePeopleFiltering();
setStackState(
- ChildType.GENTLE_HEADER,
- ChildType.PERSON,
- ChildType.ALERTING,
- ChildType.GENTLE);
+ GENTLE_HEADER,
+ PERSON,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
- verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2);
+ verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2);
verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
}
@@ -313,12 +329,12 @@
enablePeopleFiltering();
setStackState(
- ChildType.PERSON,
- ChildType.ALERTING,
- ChildType.GENTLE);
+ PERSON,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
- verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 2);
+ verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 2);
verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
}
@@ -328,15 +344,15 @@
enablePeopleFiltering();
setStackState(
- ChildType.PEOPLE_HEADER,
- ChildType.ALERTING_HEADER,
- ChildType.GENTLE_HEADER,
- ChildType.PERSON,
- ChildType.ALERTING,
- ChildType.GENTLE);
+ PEOPLE_HEADER,
+ ALERTING_HEADER,
+ GENTLE_HEADER,
+ PERSON,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
- verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 4);
+ verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 4);
verify(mNssl).changeViewPosition(mSectionsManager.getAlertingHeaderView(), 2);
verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0);
}
@@ -347,12 +363,11 @@
enablePeopleFiltering();
setStackState(
- ChildType.PEOPLE_HEADER,
- ChildType.ALERTING_HEADER,
- ChildType.ALERTING,
- ChildType.GENTLE_HEADER,
- ChildType.GENTLE
- );
+ PEOPLE_HEADER,
+ ALERTING_HEADER,
+ ALERTING,
+ GENTLE_HEADER,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
verify(mNssl, never()).removeView(mSectionsManager.getPeopleHeaderView());
@@ -360,16 +375,16 @@
}
@Test
- public void testPeopleFiltering_HunWhilePeopleVisible() {
+ public void testPeopleFiltering_AlertingHunWhilePeopleVisible() {
enablePeopleFiltering();
setupMockStack(
- ChildType.PEOPLE_HEADER,
- ChildType.HEADS_UP,
- ChildType.PERSON,
- ChildType.ALERTING_HEADER,
- ChildType.GENTLE_HEADER,
- ChildType.GENTLE
+ PEOPLE_HEADER,
+ ALERTING.headsUp(),
+ PERSON,
+ ALERTING_HEADER,
+ GENTLE_HEADER,
+ GENTLE
);
mSectionsManager.updateSectionBoundaries();
@@ -384,17 +399,74 @@
}
@Test
+ public void testPeopleFiltering_PersonHunWhileAlertingHunVisible() {
+ enablePeopleFiltering();
+
+ setupMockStack(
+ PERSON.headsUp(),
+ INCOMING_HEADER,
+ ALERTING.headsUp(),
+ PEOPLE_HEADER,
+ PERSON
+ );
+ mSectionsManager.updateSectionBoundaries();
+
+ verifyMockStack(
+ ChildType.INCOMING_HEADER,
+ ChildType.HEADS_UP,
+ ChildType.HEADS_UP,
+ ChildType.PEOPLE_HEADER,
+ ChildType.PERSON
+ );
+ }
+
+ @Test
+ public void testPeopleFiltering_PersonHun() {
+ enablePeopleFiltering();
+
+ setupMockStack(
+ PERSON.headsUp(),
+ PEOPLE_HEADER,
+ PERSON
+ );
+ mSectionsManager.updateSectionBoundaries();
+
+ verifyMockStack(
+ ChildType.PEOPLE_HEADER,
+ ChildType.PERSON,
+ ChildType.PERSON
+ );
+ }
+
+ @Test
+ public void testPeopleFiltering_AlertingHunWhilePersonHunning() {
+ enablePeopleFiltering();
+
+ setupMockStack(
+ ALERTING.headsUp(),
+ PERSON.headsUp()
+ );
+ mSectionsManager.updateSectionBoundaries();
+ verifyMockStack(
+ ChildType.INCOMING_HEADER,
+ ChildType.HEADS_UP,
+ ChildType.PEOPLE_HEADER,
+ ChildType.PERSON
+ );
+ }
+
+ @Test
public void testPeopleFiltering_Fsn() {
enablePeopleFiltering();
setupMockStack(
- ChildType.INCOMING_HEADER,
- ChildType.HEADS_UP,
- ChildType.PEOPLE_HEADER,
- ChildType.FSN,
- ChildType.PERSON,
- ChildType.ALERTING,
- ChildType.GENTLE
+ INCOMING_HEADER,
+ ALERTING.headsUp(),
+ PEOPLE_HEADER,
+ FSN,
+ PERSON,
+ ALERTING,
+ GENTLE
);
mSectionsManager.updateSectionBoundaries();
@@ -416,7 +488,7 @@
enableMediaControls();
// GIVEN a stack that doesn't include media controls
- setStackState(ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE);
+ setStackState(ALERTING, GENTLE_HEADER, GENTLE);
// WHEN we go back to the keyguard
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
@@ -431,14 +503,20 @@
enableMediaControls();
// GIVEN a stack that doesn't include media controls but includes HEADS_UP
- setupMockStack(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER,
- ChildType.GENTLE);
+ setupMockStack(
+ ALERTING.headsUp(),
+ ALERTING,
+ GENTLE_HEADER,
+ GENTLE);
// WHEN we go back to the keyguard
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mSectionsManager.updateSectionBoundaries();
- verifyMockStack(ChildType.HEADS_UP, ChildType.MEDIA_CONTROLS, ChildType.ALERTING,
+ verifyMockStack(
+ ChildType.MEDIA_CONTROLS,
+ ChildType.ALERTING,
+ ChildType.ALERTING,
ChildType.GENTLE);
}
@@ -455,11 +533,12 @@
FSN, PERSON, ALERTING, GENTLE, OTHER
}
- private void setStackState(ChildType... children) {
+ private void setStackState(StackEntry... children) {
when(mNssl.getChildCount()).thenReturn(children.length);
for (int i = 0; i < children.length; i++) {
View child;
- switch (children[i]) {
+ StackEntry entry = children[i];
+ switch (entry.mChildType) {
case INCOMING_HEADER:
child = mSectionsManager.getIncomingHeaderView();
break;
@@ -473,22 +552,19 @@
child = mSectionsManager.getAlertingHeaderView();
break;
case GENTLE_HEADER:
- child = mSectionsManager.getGentleHeaderView();
- break;
- case HEADS_UP:
- child = mockNotification(BUCKET_HEADS_UP);
+ child = mSectionsManager.getSilentHeaderView();
break;
case FSN:
- child = mockNotification(BUCKET_FOREGROUND_SERVICE);
+ child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsHeadsUp);
break;
case PERSON:
- child = mockNotification(BUCKET_PEOPLE);
+ child = mockNotification(BUCKET_PEOPLE, entry.mIsHeadsUp);
break;
case ALERTING:
- child = mockNotification(BUCKET_ALERTING);
+ child = mockNotification(BUCKET_ALERTING, entry.mIsHeadsUp);
break;
case GENTLE:
- child = mockNotification(BUCKET_SILENT);
+ child = mockNotification(BUCKET_SILENT, entry.mIsHeadsUp);
break;
case OTHER:
child = mock(View.class);
@@ -503,12 +579,24 @@
}
}
- private View mockNotification(int bucket) {
- ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
- RETURNS_DEEP_STUBS);
+ private View mockNotification(int bucket, boolean headsUp) {
+ ExpandableNotificationRow notifRow =
+ mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
- when(notifRow.getEntry().getBucket()).thenReturn(bucket);
when(notifRow.getParent()).thenReturn(mNssl);
+
+ NotificationEntry mockEntry = mock(NotificationEntry.class);
+ when(notifRow.getEntry()).thenReturn(mockEntry);
+
+ int[] bucketRef = new int[] { bucket };
+ when(mockEntry.getBucket()).thenAnswer(invocation -> bucketRef[0]);
+ doAnswer(invocation -> {
+ bucketRef[0] = invocation.getArgument(0);
+ return null;
+ }).when(mockEntry).setBucket(anyInt());
+
+ when(notifRow.isHeadsUp()).thenReturn(headsUp);
+ when(mockEntry.isRowHeadsUp()).thenReturn(headsUp);
return notifRow;
}
@@ -533,7 +621,7 @@
actual.add(ChildType.ALERTING_HEADER);
continue;
}
- if (child == mSectionsManager.getGentleHeaderView()) {
+ if (child == mSectionsManager.getSilentHeaderView()) {
actual.add(ChildType.GENTLE_HEADER);
continue;
}
@@ -565,7 +653,7 @@
assertThat(actual).containsExactly((Object[]) expected).inOrder();
}
- private void setupMockStack(ChildType... childTypes) {
+ private void setupMockStack(StackEntry... entries) {
final List<View> children = new ArrayList<>();
when(mNssl.getChildCount()).thenAnswer(invocation -> children.size());
when(mNssl.getChildAt(anyInt()))
@@ -590,9 +678,9 @@
children.add(newIndex, child);
return null;
}).when(mNssl).changeViewPosition(any(), anyInt());
- for (ChildType childType : childTypes) {
+ for (StackEntry entry : entries) {
View child;
- switch (childType) {
+ switch (entry.mChildType) {
case INCOMING_HEADER:
child = mSectionsManager.getIncomingHeaderView();
break;
@@ -606,22 +694,19 @@
child = mSectionsManager.getAlertingHeaderView();
break;
case GENTLE_HEADER:
- child = mSectionsManager.getGentleHeaderView();
- break;
- case HEADS_UP:
- child = mockNotification(BUCKET_HEADS_UP);
+ child = mSectionsManager.getSilentHeaderView();
break;
case FSN:
- child = mockNotification(BUCKET_FOREGROUND_SERVICE);
+ child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsHeadsUp);
break;
case PERSON:
- child = mockNotification(BUCKET_PEOPLE);
+ child = mockNotification(BUCKET_PEOPLE, entry.mIsHeadsUp);
break;
case ALERTING:
- child = mockNotification(BUCKET_ALERTING);
+ child = mockNotification(BUCKET_ALERTING, entry.mIsHeadsUp);
break;
case GENTLE:
- child = mockNotification(BUCKET_SILENT);
+ child = mockNotification(BUCKET_SILENT, entry.mIsHeadsUp);
break;
case OTHER:
child = mock(View.class);
@@ -629,9 +714,48 @@
when(child.getParent()).thenReturn(mNssl);
break;
default:
- throw new RuntimeException("Unknown ChildType: " + childType);
+ throw new RuntimeException("Unknown ChildType: " + entry.mChildType);
}
children.add(child);
}
}
+
+ private static final StackEntry INCOMING_HEADER = new StackEntry(ChildType.INCOMING_HEADER);
+ private static final StackEntry MEDIA_CONTROLS = new StackEntry(ChildType.MEDIA_CONTROLS);
+ private static final StackEntry PEOPLE_HEADER = new StackEntry(ChildType.PEOPLE_HEADER);
+ private static final StackEntry ALERTING_HEADER = new StackEntry(ChildType.ALERTING_HEADER);
+ private static final StackEntry GENTLE_HEADER = new StackEntry(ChildType.GENTLE_HEADER);
+ private static final StackEntry FSN = new StackEntry(ChildType.FSN);
+ private static final StackEntry.Hunnable PERSON = new StackEntry.Hunnable(ChildType.PERSON);
+ private static final StackEntry.Hunnable ALERTING = new StackEntry.Hunnable(ChildType.ALERTING);
+ private static final StackEntry GENTLE = new StackEntry(ChildType.GENTLE);
+
+ private static class StackEntry {
+ final ChildType mChildType;
+ final boolean mIsHeadsUp;
+
+ StackEntry(ChildType childType) {
+ this(childType, false);
+ }
+
+ StackEntry(ChildType childType, boolean isHeadsUp) {
+ mChildType = childType;
+ mIsHeadsUp = isHeadsUp;
+ }
+
+ static class Hunnable extends StackEntry {
+
+ Hunnable(ChildType childType) {
+ super(childType, false);
+ }
+
+ Hunnable(ChildType childType, boolean isHeadsUp) {
+ super(childType, isHeadsUp);
+ }
+
+ public Hunnable headsUp() {
+ return new Hunnable(mChildType, true);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index cc2d1c2..e04d25b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -83,6 +83,7 @@
@Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
@Mock private NotificationShadeDepthController mNotificationShadeDepthController;
@Mock private SuperStatusBarViewFactory mStatusBarViewFactory;
+ @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Before
public void setUp() {
@@ -121,7 +122,7 @@
mNotificationPanelViewController,
mStatusBarViewFactory);
mController.setupExpandedStatusBar();
- mController.setService(mStatusBar);
+ mController.setService(mStatusBar, mNotificationShadeWindowController);
mController.setDragDownHelper(mDragDownHelper);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index cd2c349..bf2bd38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -31,6 +31,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -73,7 +74,8 @@
mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
mock(NotificationGroupManager.class), mNotificationLockscreenUserManager,
mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
- mActivityStarter, mShadeController, new CommandQueue(mContext)));
+ mActivityStarter, mShadeController, new CommandQueue(mContext),
+ mock(ActionClickLogger.class)));
mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver();
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e9cf727..b3867a3 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1212,7 +1212,7 @@
final Intent intent = new Intent();
final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
intent.setComponent(name);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
} catch (ActivityNotFoundException ignore) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 930f124..37f1ad1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2098,6 +2098,7 @@
}
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 +2192,18 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
- Process.enableFreezer(false);
- }
+ try {
+ if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+ Process.enableFreezer(false);
+ }
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
- "meminfo", pw)) return;
- PriorityDump.dump(mPriorityDumper, fd, pw, args);
-
- 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 +2216,18 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
- Process.enableFreezer(false);
- }
+ try {
+ if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+ Process.enableFreezer(false);
+ }
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
- "gfxinfo", pw)) return;
- mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args);
-
- 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 +2240,18 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
- Process.enableFreezer(false);
- }
+ try {
+ if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+ Process.enableFreezer(false);
+ }
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
- "dbinfo", pw)) return;
- mActivityManagerService.dumpDbInfo(fd, pw, args);
-
- 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 +2287,34 @@
}
}
+ 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;
@@ -11083,18 +11118,22 @@
void dumpLruEntryLocked(PrintWriter pw, int index, ProcessRecord proc, String prefix) {
pw.print(prefix);
- pw.print("#");
+ pw.print('#');
+ if (index < 10) {
+ pw.print(' ');
+ }
pw.print(index);
pw.print(": ");
pw.print(ProcessList.makeOomAdjString(proc.setAdj, false));
- pw.print(" ");
+ pw.print(' ');
pw.print(ProcessList.makeProcStateString(proc.getCurProcState()));
- pw.print(" ");
+ pw.print(' ');
+ ActivityManager.printCapabilitiesSummary(pw, proc.curCapability);
+ pw.print(' ');
pw.print(proc.toShortString());
- pw.print(" ");
if (proc.hasActivitiesOrRecentTasks() || proc.hasClientActivities()
|| proc.treatLikeActivity) {
- pw.print(" activity=");
+ pw.print(" act:");
boolean printed = false;
if (proc.hasActivities()) {
pw.print("activities");
@@ -12559,7 +12598,7 @@
char schedGroup;
switch (r.setSchedGroup) {
case ProcessList.SCHED_GROUP_BACKGROUND:
- schedGroup = 'B';
+ schedGroup = 'b';
break;
case ProcessList.SCHED_GROUP_DEFAULT:
schedGroup = 'F';
@@ -12570,6 +12609,9 @@
case ProcessList.SCHED_GROUP_RESTRICTED:
schedGroup = 'R';
break;
+ case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
+ schedGroup = 'B';
+ break;
default:
schedGroup = '?';
break;
@@ -12597,7 +12639,10 @@
pw.print(foreground);
pw.print('/');
pw.print(procState);
- pw.print(" trm:");
+ pw.print(' ');
+ ActivityManager.printCapabilitiesSummary(pw, r.curCapability);
+ pw.print(' ');
+ pw.print(" t:");
if (r.trimMemoryLevel < 10) pw.print(' ');
pw.print(r.trimMemoryLevel);
pw.print(' ');
@@ -12712,6 +12757,39 @@
}
}
+ 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) {
diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
deleted file mode 100644
index 0e34801..0000000
--- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowInsets;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-
-/**
- * Dialog to show when a user switch it about to happen for the car. The intent is to snapshot the
- * screen immediately after the dialog shows so that the user is informed that something is
- * happening in the background rather than just freeze the screen and not know if the user-switch
- * affordance was being handled.
- */
-final class CarUserSwitchingDialog extends UserSwitchingDialog {
-
- private static final String TAG = "ActivityManagerCarUserSwitchingDialog";
- private View mView;
-
- public CarUserSwitchingDialog(ActivityManagerService service, Context context, UserInfo oldUser,
- UserInfo newUser, boolean aboveSystem, String switchingFromSystemUserMessage,
- String switchingToSystemUserMessage) {
- super(service, context, oldUser, newUser, aboveSystem, switchingFromSystemUserMessage,
- switchingToSystemUserMessage);
- }
-
- @Override
- void inflateContent() {
- // Set up the dialog contents
- setCancelable(false);
- Resources res = getContext().getResources();
- // Custom view due to alignment and font size requirements
- getContext().setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert_UserSwitchingDialog);
- mView = LayoutInflater.from(getContext()).inflate(
- R.layout.car_user_switching_dialog,
- null);
-
- UserManager userManager =
- (UserManager) getContext().getSystemService(Context.USER_SERVICE);
- Bitmap bitmap = userManager.getUserIcon(mNewUser.id);
- if (bitmap != null) {
- CircleFramedDrawable drawable = CircleFramedDrawable.getInstance(bitmap,
- res.getDimension(R.dimen.car_fullscreen_user_pod_image_avatar_height));
- ((ImageView) mView.findViewById(R.id.user_loading_avatar))
- .setImageDrawable(drawable);
- }
-
- TextView msgView = mView.findViewById(R.id.user_loading);
-
- // TODO(b/145132885): use constant from CarSettings
- boolean showInfo = "true".equals(Settings.Global.getString(
- getContext().getContentResolver(),
- "android.car.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE"));
-
- if (showInfo) {
- msgView.setText(res.getString(R.string.car_loading_profile) + " user\n(from "
- + mOldUser.id + " to " + mNewUser.id + ")");
- } else {
- msgView.setText(res.getString(R.string.car_loading_profile));
- }
- setView(mView);
- }
-
- @Override
- public void show() {
- super.show();
- hideNavigationBar();
- }
-
- private void hideNavigationBar() {
- mView.getWindowInsetsController().hide(WindowInsets.Type.navigationBars());
- }
-
- /**
- * Converts the user icon to a circularly clipped one. This is used in the User Picker and
- * Settings.
- */
- static class CircleFramedDrawable extends Drawable {
-
- private final Bitmap mBitmap;
- private final int mSize;
- private final Paint mPaint;
-
- private float mScale;
- private Rect mSrcRect;
- private RectF mDstRect;
-
- public static CircleFramedDrawable getInstance(Bitmap icon, float iconSize) {
- CircleFramedDrawable instance = new CircleFramedDrawable(icon, (int) iconSize);
- return instance;
- }
-
- public CircleFramedDrawable(Bitmap icon, int size) {
- super();
- mSize = size;
-
- mBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888);
- final Canvas canvas = new Canvas(mBitmap);
-
- final int width = icon.getWidth();
- final int height = icon.getHeight();
- final int square = Math.min(width, height);
-
- final Rect cropRect = new Rect((width - square) / 2, (height - square) / 2,
- square, square);
- final RectF circleRect = new RectF(0f, 0f, mSize, mSize);
-
- final Path fillPath = new Path();
- fillPath.addArc(circleRect, 0f, 360f);
-
- canvas.drawColor(0, PorterDuff.Mode.CLEAR);
-
- // opaque circle
- mPaint = new Paint();
- mPaint.setAntiAlias(true);
- mPaint.setColor(Color.BLACK);
- mPaint.setStyle(Paint.Style.FILL);
- canvas.drawPath(fillPath, mPaint);
-
- // mask in the icon where the bitmap is opaque
- mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
- canvas.drawBitmap(icon, cropRect, circleRect, mPaint);
-
- // prepare paint for frame drawing
- mPaint.setXfermode(null);
-
- mScale = 1f;
-
- mSrcRect = new Rect(0, 0, mSize, mSize);
- mDstRect = new RectF(0, 0, mSize, mSize);
- }
-
- @Override
- public void draw(Canvas canvas) {
- final float inside = mScale * mSize;
- final float pad = (mSize - inside) / 2f;
-
- mDstRect.set(pad, pad, mSize - pad, mSize - pad);
- canvas.drawBitmap(mBitmap, mSrcRect, mDstRect, null);
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- public void setAlpha(int alpha) {
- // Needed to implement abstract method. Do nothing.
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- // Needed to implement abstract method. Do nothing.
- }
- }
-}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ad85853..c13bb5a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -151,15 +151,6 @@
@EnabledAfter(targetSdkVersion=android.os.Build.VERSION_CODES.Q)
static final long CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID = 136219221L;
- // TODO: remove this when development is done.
- // These are debug flags used between OomAdjuster and AppOpsService to detect and report absence
- // of the real flags.
- public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q = 1 << 27;
- public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q = 1 << 28;
- public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 29;
- public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 30;
- public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
-
/**
* For some direct access we need to power manager.
*/
@@ -1506,8 +1497,9 @@
//lost the capability, use temp location capability to mark this case.
//TODO: remove this block when development is done.
capabilityFromFGS |=
- (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
- != 0 ? DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
+ (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0
+ ? ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION
+ : 0;
}
if (s.mAllowWhileInUsePermissionInFgs) {
boolean enabled = false;
@@ -1520,22 +1512,22 @@
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA
- : DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
+ : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
- : DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+ : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
} else {
// Remove fgsType check and assign PROCESS_CAPABILITY_FOREGROUND_CAMERA
// and MICROPHONE when finish debugging.
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA
- : DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q;
+ : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q;
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
- : DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q;
+ : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q;
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b753de9..108fb7d 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -972,7 +972,7 @@
return buildOomTag("vis", "vis", " ", setAdj,
ProcessList.VISIBLE_APP_ADJ, compact);
} else if (setAdj >= ProcessList.FOREGROUND_APP_ADJ) {
- return buildOomTag("fore ", "fore", null, setAdj,
+ return buildOomTag("fg ", "fg ", " ", setAdj,
ProcessList.FOREGROUND_APP_ADJ, compact);
} else if (setAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) {
return buildOomTag("psvc ", "psvc", null, setAdj,
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index fc6931d..c5152c0 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -431,16 +431,46 @@
pw.print(" nextPssTime=");
TimeUtils.formatDuration(nextPssTime, nowUptime, pw);
pw.println();
- pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
- pw.print(" lruSeq="); pw.print(lruSeq);
- pw.print(" lastPss="); DebugUtils.printSizeValue(pw, lastPss*1024);
- pw.print(" lastSwapPss="); DebugUtils.printSizeValue(pw, lastSwapPss*1024);
- pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, lastCachedPss*1024);
- pw.print(" lastCachedSwapPss="); DebugUtils.printSizeValue(pw, lastCachedSwapPss*1024);
- pw.print(" lastRss="); DebugUtils.printSizeValue(pw, mLastRss * 1024);
+ pw.print(prefix); pw.print("lastPss="); DebugUtils.printSizeValue(pw, lastPss * 1024);
+ pw.print(" lastSwapPss="); DebugUtils.printSizeValue(pw, lastSwapPss * 1024);
+ pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, lastCachedPss * 1024);
+ pw.print(" lastCachedSwapPss="); DebugUtils.printSizeValue(pw,
+ lastCachedSwapPss * 1024);
+ pw.print(" lastRss="); DebugUtils.printSizeValue(pw, mLastRss * 1024);
pw.println();
pw.print(prefix); pw.print("procStateMemTracker: ");
procStateMemTracker.dumpLine(pw);
+ pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
+ pw.print(" lruSeq="); pw.println(lruSeq);
+ pw.print(prefix); pw.print("oom adj: max="); pw.print(maxAdj);
+ pw.print(" curRaw="); pw.print(mCurRawAdj);
+ pw.print(" setRaw="); pw.print(setRawAdj);
+ pw.print(" cur="); pw.print(curAdj);
+ pw.print(" set="); pw.println(setAdj);
+ pw.print(prefix); pw.print("lastCompactTime="); pw.print(lastCompactTime);
+ pw.print(" lastCompactAction="); pw.println(lastCompactAction);
+ pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
+ pw.print(" setSchedGroup="); pw.print(setSchedGroup);
+ pw.print(" systemNoUi="); pw.print(systemNoUi);
+ pw.print(" trimMemoryLevel="); pw.println(trimMemoryLevel);
+ pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState());
+ pw.print(" mRepProcState="); pw.print(mRepProcState);
+ pw.print(" pssProcState="); pw.print(pssProcState);
+ pw.print(" setProcState="); pw.print(setProcState);
+ pw.print(" lastStateTime=");
+ TimeUtils.formatDuration(lastStateTime, nowUptime, pw);
+ pw.println();
+ pw.print(prefix); pw.print("curCapability=");
+ ActivityManager.printCapabilitiesFull(pw, curCapability);
+ pw.print(" setCapability=");
+ ActivityManager.printCapabilitiesFull(pw, setCapability);
+ pw.println();
+ if (hasShownUi || mPendingUiClean || hasAboveClient || treatLikeActivity) {
+ pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi);
+ pw.print(" pendingUiClean="); pw.print(mPendingUiClean);
+ pw.print(" hasAboveClient="); pw.print(hasAboveClient);
+ pw.print(" treatLikeActivity="); pw.println(treatLikeActivity);
+ }
pw.print(prefix); pw.print("cached="); pw.print(mCached);
pw.print(" empty="); pw.println(empty);
if (serviceb) {
@@ -451,32 +481,6 @@
pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(notCachedSinceIdle);
pw.print(" initialIdlePss="); pw.println(initialIdlePss);
}
- pw.print(prefix); pw.print("oom: max="); pw.print(maxAdj);
- pw.print(" curRaw="); pw.print(mCurRawAdj);
- pw.print(" setRaw="); pw.print(setRawAdj);
- pw.print(" cur="); pw.print(curAdj);
- pw.print(" set="); pw.println(setAdj);
- pw.print(prefix); pw.print("lastCompactTime="); pw.print(lastCompactTime);
- pw.print(" lastCompactAction="); pw.print(lastCompactAction);
- pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
- pw.print(" setSchedGroup="); pw.print(setSchedGroup);
- pw.print(" systemNoUi="); pw.print(systemNoUi);
- pw.print(" trimMemoryLevel="); pw.println(trimMemoryLevel);
- pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState());
- pw.print(" mRepProcState="); pw.print(mRepProcState);
- pw.print(" pssProcState="); pw.print(pssProcState);
- pw.print(" setProcState="); pw.print(setProcState);
- pw.print(" curCapability="); pw.print(curCapability);
- pw.print(" setCapability="); pw.print(setCapability);
- pw.print(" lastStateTime=");
- TimeUtils.formatDuration(lastStateTime, nowUptime, pw);
- pw.println();
- if (hasShownUi || mPendingUiClean || hasAboveClient || treatLikeActivity) {
- pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi);
- pw.print(" pendingUiClean="); pw.print(mPendingUiClean);
- pw.print(" hasAboveClient="); pw.print(hasAboveClient);
- pw.print(" treatLikeActivity="); pw.println(treatLikeActivity);
- }
if (connectionService != null || connectionGroup != 0) {
pw.print(prefix); pw.print("connectionGroup="); pw.print(connectionGroup);
pw.print(" Importance="); pw.print(connectionImportance);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 5b12933..fac4a1e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2736,19 +2736,13 @@
void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
- Dialog d;
if (!mService.mContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
- d = new UserSwitchingDialog(mService, mService.mContext, fromUser, toUser,
- true /* above system */, switchingFromSystemUserMessage,
- switchingToSystemUserMessage);
- } else {
- d = new CarUserSwitchingDialog(mService, mService.mContext, fromUser, toUser,
- true /* above system */, switchingFromSystemUserMessage,
- switchingToSystemUserMessage);
+ final Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromUser,
+ toUser, true /* above system */, switchingFromSystemUserMessage,
+ switchingToSystemUserMessage);
+ d.show();
}
-
- d.show();
}
void reportGlobalUsageEventLocked(int event) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 5ebfb00..ae38e81 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -66,11 +66,11 @@
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
-import static com.android.server.am.OomAdjuster.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
-import static com.android.server.am.OomAdjuster.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q;
-import static com.android.server.am.OomAdjuster.DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION;
-import static com.android.server.am.OomAdjuster.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
-import static com.android.server.am.OomAdjuster.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q;
+import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
+import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q;
+import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
import static java.lang.Long.max;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 3ee3d50..df4c269 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -36,6 +36,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.PrintWriterPrinter;
@@ -43,6 +44,9 @@
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
/** @hide */
/*package*/ final class AudioDeviceBroker {
@@ -91,6 +95,9 @@
// TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
/*package*/ final Object mSetModeLock = new Object();
+ /** PID of current audio mode owner communicated by AudioService */
+ private int mModeOwnerPid = 0;
+
//-------------------------------------------------------------------
/*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
mContext = context;
@@ -136,6 +143,7 @@
/*package*/ void onSystemReady() {
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
+ mModeOwnerPid = mAudioService.getModeOwnerPid();
mBtHelper.onSystemReady();
}
}
@@ -202,30 +210,57 @@
* @param eventSource for logging purposes
* @return true if speakerphone state changed
*/
- /*package*/ boolean setSpeakerphoneOn(boolean on, String eventSource) {
+ /*package*/ boolean setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) {
synchronized (mDeviceStateLock) {
- final boolean wasOn = isSpeakerphoneOn();
- if (on) {
- if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
- setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
- }
- mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
- } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
- if (mBtHelper.isBluetoothScoOn()) {
- mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
- setForceUse_Async(
- AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource);
- } else {
- mForcedUseForComm = AudioSystem.FORCE_NONE;
- }
+ if (!addSpeakerphoneClient(cb, pid, on)) {
+ return false;
}
-
- mForcedUseForCommExt = mForcedUseForComm;
- setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+ final boolean wasOn = isSpeakerphoneOn();
+ updateSpeakerphoneOn(eventSource);
return (wasOn != isSpeakerphoneOn());
}
}
+ @GuardedBy("mDeviceStateLock")
+ private void updateSpeakerphoneOn(String eventSource) {
+ if (isSpeakerphoneOnRequested()) {
+ if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+ setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
+ }
+ mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
+ } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
+ if (mBtHelper.isBluetoothScoOn()) {
+ mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
+ setForceUse_Async(
+ AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource);
+ } else {
+ mForcedUseForComm = AudioSystem.FORCE_NONE;
+ }
+ }
+ mForcedUseForCommExt = mForcedUseForComm;
+ setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+ }
+
+ /**
+ * Returns if speakerphone is requested ON or OFF.
+ * If the current audio mode owner is in the speakerphone client list, use this preference.
+ * Otherwise use first client's preference (first client corresponds to latest request).
+ * Speakerphone is requested OFF if no client is in the list.
+ * @return true if speakerphone is requested ON, false otherwise
+ */
+ @GuardedBy("mDeviceStateLock")
+ private boolean isSpeakerphoneOnRequested() {
+ if (mSpeakerphoneClients.isEmpty()) {
+ return false;
+ }
+ for (SpeakerphoneClient cl : mSpeakerphoneClients) {
+ if (cl.getPid() == mModeOwnerPid) {
+ return cl.isOn();
+ }
+ }
+ return mSpeakerphoneClients.get(0).isOn();
+ }
+
/*package*/ boolean isSpeakerphoneOn() {
synchronized (mDeviceStateLock) {
return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
@@ -384,7 +419,8 @@
}
mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
} else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
- mForcedUseForComm = AudioSystem.FORCE_NONE;
+ mForcedUseForComm = isSpeakerphoneOnRequested()
+ ? AudioSystem.FORCE_SPEAKER : AudioSystem.FORCE_NONE;
}
mForcedUseForCommExt = mForcedUseForComm;
AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
@@ -429,8 +465,8 @@
sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
}
- /*package*/ void postDisconnectBluetoothSco(int exceptPid) {
- sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
+ /*package*/ void postSetModeOwnerPid(int pid) {
+ sendIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid);
}
/*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
@@ -483,7 +519,7 @@
}
/*package*/ int getModeOwnerPid() {
- return mAudioService.getModeOwnerPid();
+ return mModeOwnerPid;
}
/*package*/ int getDeviceForStream(int streamType) {
@@ -605,6 +641,10 @@
sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
}
+ /*package*/ void postSpeakerphoneClientDied(Object obj) {
+ sendLMsgNoDelay(MSG_L_SPEAKERPHONE_CLIENT_DIED, SENDMSG_QUEUE, obj);
+ }
+
/*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy,
AudioDeviceAttributes device)
{
@@ -674,7 +714,7 @@
new BtHelper.BluetoothA2dpDeviceInfo(btDevice);
return (mBrokerHandler.hasEqualMessages(
MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED, devInfoToCheck)
- || mBrokerHandler.hasEqualMessages(
+ || mBrokerHandler.hasEqualMessages(
MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED, devInfoToCheck));
}
@@ -707,7 +747,20 @@
} else {
pw.println("Message handler is null");
}
+
mDeviceInventory.dump(pw, prefix);
+
+ pw.println("\n" + prefix + "mForcedUseForComm: "
+ + AudioSystem.forceUseConfigToString(mForcedUseForComm));
+ pw.println(prefix + "mForcedUseForCommExt: "
+ + AudioSystem.forceUseConfigToString(mForcedUseForCommExt));
+ pw.println(prefix + "mModeOwnerPid: " + mModeOwnerPid);
+ pw.println(prefix + "Speakerphone clients:");
+ mSpeakerphoneClients.forEach((cl) -> {
+ pw.println(" " + prefix + "pid: " + cl.getPid() + " on: "
+ + cl.isOn() + " cb: " + cl.getBinder()); });
+
+ mBtHelper.dump(pw, prefix);
}
//---------------------------------------------------------------------
@@ -877,10 +930,16 @@
mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
}
break;
- case MSG_I_DISCONNECT_BT_SCO:
+ case MSG_I_SET_MODE_OWNER_PID:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- mBtHelper.disconnectBluetoothSco(msg.arg1);
+ if (mModeOwnerPid != msg.arg1) {
+ mModeOwnerPid = msg.arg1;
+ updateSpeakerphoneOn("setNewModeOwner");
+ if (mModeOwnerPid != 0) {
+ mBtHelper.disconnectBluetoothSco(mModeOwnerPid);
+ }
+ }
}
}
break;
@@ -891,6 +950,11 @@
}
}
break;
+ case MSG_L_SPEAKERPHONE_CLIENT_DIED:
+ synchronized (mDeviceStateLock) {
+ speakerphoneClientDied(msg.obj);
+ }
+ break;
case MSG_TOGGLE_HDMI:
synchronized (mDeviceStateLock) {
mDeviceInventory.onToggleHdmi();
@@ -1021,7 +1085,7 @@
private static final int MSG_REPORT_NEW_ROUTES = 13;
private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
- private static final int MSG_I_DISCONNECT_BT_SCO = 16;
+ private static final int MSG_I_SET_MODE_OWNER_PID = 16;
// process active A2DP device change, obj is BtHelper.BluetoothA2dpDeviceInfo
private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
@@ -1051,6 +1115,8 @@
private static final int MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY = 33;
private static final int MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY = 34;
+ private static final int MSG_L_SPEAKERPHONE_CLIENT_DIED = 35;
+
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
@@ -1166,4 +1232,93 @@
time);
}
}
+
+ private class SpeakerphoneClient implements IBinder.DeathRecipient {
+ private final IBinder mCb;
+ private final int mPid;
+ private final boolean mOn;
+ SpeakerphoneClient(IBinder cb, int pid, boolean on) {
+ mCb = cb;
+ mPid = pid;
+ mOn = on;
+ }
+
+ public boolean registerDeathRecipient() {
+ boolean status = false;
+ try {
+ mCb.linkToDeath(this, 0);
+ status = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "SpeakerphoneClient could not link to " + mCb + " binder death");
+ }
+ return status;
+ }
+
+ public void unregisterDeathRecipient() {
+ try {
+ mCb.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "SpeakerphoneClient could not not unregistered to binder");
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ postSpeakerphoneClientDied(this);
+ }
+
+ IBinder getBinder() {
+ return mCb;
+ }
+
+ int getPid() {
+ return mPid;
+ }
+
+ boolean isOn() {
+ return mOn;
+ }
+ }
+
+ @GuardedBy("mDeviceStateLock")
+ private void speakerphoneClientDied(Object obj) {
+ if (obj == null) {
+ return;
+ }
+ Log.w(TAG, "Speaker client died");
+ if (removeSpeakerphoneClient(((SpeakerphoneClient) obj).getBinder(), false) != null) {
+ updateSpeakerphoneOn("speakerphoneClientDied");
+ }
+ }
+
+ private SpeakerphoneClient removeSpeakerphoneClient(IBinder cb, boolean unregister) {
+ for (SpeakerphoneClient cl : mSpeakerphoneClients) {
+ if (cl.getBinder() == cb) {
+ if (unregister) {
+ cl.unregisterDeathRecipient();
+ }
+ mSpeakerphoneClients.remove(cl);
+ return cl;
+ }
+ }
+ return null;
+ }
+
+ @GuardedBy("mDeviceStateLock")
+ private boolean addSpeakerphoneClient(IBinder cb, int pid, boolean on) {
+ // always insert new request at first position
+ removeSpeakerphoneClient(cb, true);
+ SpeakerphoneClient client = new SpeakerphoneClient(cb, pid, on);
+ if (client.registerDeathRecipient()) {
+ mSpeakerphoneClients.add(0, client);
+ return true;
+ }
+ return false;
+ }
+
+ // List of clients requesting speakerPhone ON
+ @GuardedBy("mDeviceStateLock")
+ private final @NonNull ArrayList<SpeakerphoneClient> mSpeakerphoneClients =
+ new ArrayList<SpeakerphoneClient>();
+
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8068e37..98d662a 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -52,7 +52,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
@@ -570,6 +569,10 @@
private int[] mAccessibilityServiceUids;
private final Object mAccessibilityServiceUidsLock = new Object();
+ // Uid of the active input method service to check if caller is the one or not.
+ private int mInputMethodServiceUid = android.os.Process.INVALID_UID;
+ private final Object mInputMethodServiceUidLock = new Object();
+
private int mEncodedSurroundMode;
private String mEnabledSurroundFormats;
private boolean mSurroundModeChanged;
@@ -1078,12 +1081,14 @@
sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
sendEnabledSurroundFormats(mContentResolver, true);
updateAssistantUId(true);
- updateCurrentImeUid(true);
AudioSystem.setRttEnabled(mRttEnabled);
}
synchronized (mAccessibilityServiceUidsLock) {
AudioSystem.setA11yServicesUids(mAccessibilityServiceUids);
}
+ synchronized (mInputMethodServiceUidLock) {
+ mAudioSystem.setCurrentImeUid(mInputMethodServiceUid);
+ }
synchronized (mHdmiClientLock) {
if (mHdmiManager != null && mHdmiTvClient != null) {
setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
@@ -1629,37 +1634,6 @@
}
}
- @GuardedBy("mSettingsLock")
- private void updateCurrentImeUid(boolean forceUpdate) {
- String imeId = Settings.Secure.getStringForUser(
- mContentResolver,
- Settings.Secure.DEFAULT_INPUT_METHOD, UserHandle.USER_CURRENT);
- if (TextUtils.isEmpty(imeId)) {
- Log.e(TAG, "updateCurrentImeUid() could not find current IME");
- return;
- }
- ComponentName componentName = ComponentName.unflattenFromString(imeId);
- if (componentName == null) {
- Log.e(TAG, "updateCurrentImeUid() got invalid service name for "
- + Settings.Secure.DEFAULT_INPUT_METHOD + ": " + imeId);
- return;
- }
- String packageName = componentName.getPackageName();
- int currentUserId = LocalServices.getService(ActivityManagerInternal.class)
- .getCurrentUserId();
- int currentImeUid = LocalServices.getService(PackageManagerInternal.class)
- .getPackageUidInternal(packageName, 0 /* flags */, currentUserId);
- if (currentImeUid < 0) {
- Log.e(TAG, "updateCurrentImeUid() could not find UID for package: " + packageName);
- return;
- }
-
- if (currentImeUid != mCurrentImeUid || forceUpdate) {
- mAudioSystem.setCurrentImeUid(currentImeUid);
- mCurrentImeUid = currentImeUid;
- }
- }
-
private void readPersistedSettings() {
if (!mSystemServer.isPrivileged()) {
return;
@@ -1707,7 +1681,6 @@
sendEncodedSurroundMode(cr, "readPersistedSettings");
sendEnabledSurroundFormats(cr, true);
updateAssistantUId(true);
- updateCurrentImeUid(true);
AudioSystem.setRttEnabled(mRttEnabled);
}
@@ -3594,11 +3567,9 @@
}
public void binderDied() {
- int oldModeOwnerPid;
int newModeOwnerPid = 0;
synchronized (mDeviceBroker.mSetModeLock) {
Log.w(TAG, "setMode() client died");
- oldModeOwnerPid = getModeOwnerPid();
int index = mSetModeDeathHandlers.indexOf(this);
if (index < 0) {
Log.w(TAG, "unregistered setMode() client died");
@@ -3608,9 +3579,7 @@
}
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode when pid changes
- if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
- mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
- }
+ mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
}
public int getPid() {
@@ -3662,17 +3631,20 @@
return;
}
- int oldModeOwnerPid;
int newModeOwnerPid;
synchronized (mDeviceBroker.mSetModeLock) {
if (mode == AudioSystem.MODE_CURRENT) {
mode = mMode;
}
- oldModeOwnerPid = getModeOwnerPid();
+ int oldModeOwnerPid = getModeOwnerPid();
// Do not allow changing mode if a call is active and the requester
- // does not have permission to modify phone state or is not the mode owner.
- if (((mMode == AudioSystem.MODE_IN_CALL)
- || (mMode == AudioSystem.MODE_IN_COMMUNICATION))
+ // does not have permission to modify phone state or is not the mode owner,
+ // unless returning to NORMAL mode (will not change current mode owner) or
+ // not changing mode in which case the mode owner will reflect the last
+ // requester of current mode
+ if (!((mode == mMode) || (mode == AudioSystem.MODE_NORMAL))
+ && ((mMode == AudioSystem.MODE_IN_CALL)
+ || (mMode == AudioSystem.MODE_IN_COMMUNICATION))
&& !(hasModifyPhoneStatePermission || (oldModeOwnerPid == callingPid))) {
Log.w(TAG, "setMode(" + mode + ") from pid=" + callingPid
+ ", uid=" + Binder.getCallingUid()
@@ -3685,9 +3657,7 @@
}
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode when pid changes
- if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
- mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
- }
+ mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
}
// setModeInt() returns a valid PID if the audio mode was successfully set to
@@ -3964,32 +3934,18 @@
}
/** @see AudioManager#setSpeakerphoneOn(boolean) */
- public void setSpeakerphoneOn(boolean on){
+ public void setSpeakerphoneOn(IBinder cb, boolean on) {
if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
return;
}
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_PHONE_STATE)
- != PackageManager.PERMISSION_GRANTED) {
- synchronized (mSetModeDeathHandlers) {
- for (SetModeDeathHandler h : mSetModeDeathHandlers) {
- if (h.getMode() == AudioSystem.MODE_IN_CALL) {
- Log.w(TAG, "getMode is call, Permission Denial: setSpeakerphoneOn from pid="
- + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
- return;
- }
- }
- }
- }
-
// for logging only
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
.append(") from u/pid:").append(uid).append("/")
.append(pid).toString();
- final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(on, eventSource);
+ final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
+ MediaMetrics.SEPARATOR + "setSpeakerphoneOn")
.setUid(uid)
@@ -6217,8 +6173,6 @@
mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.VOICE_INTERACTION_SERVICE), false, this);
- mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
}
@Override
@@ -6242,7 +6196,6 @@
updateEncodedSurroundOutput();
sendEnabledSurroundFormats(mContentResolver, mSurroundModeChanged);
updateAssistantUId(false);
- updateCurrentImeUid(false);
}
}
@@ -7584,6 +7537,19 @@
AudioSystem.setA11yServicesUids(mAccessibilityServiceUids);
}
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setInputMethodServiceUid(int uid) {
+ synchronized (mInputMethodServiceUidLock) {
+ if (mInputMethodServiceUid != uid) {
+ mAudioSystem.setCurrentImeUid(uid);
+ mInputMethodServiceUid = uid;
+ }
+ }
+ }
}
//==========================================================================================
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index b6ffcef..b4c41b2 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -38,6 +38,7 @@
import com.android.internal.annotations.GuardedBy;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
@@ -131,6 +132,26 @@
}
}
+ /**
+ * Returns a string representation of the scoAudioState.
+ */
+ public static String scoAudioStateToString(int scoAudioState) {
+ switch (scoAudioState) {
+ case SCO_STATE_INACTIVE:
+ return "SCO_STATE_INACTIVE";
+ case SCO_STATE_ACTIVATE_REQ:
+ return "SCO_STATE_ACTIVATE_REQ";
+ case SCO_STATE_ACTIVE_EXTERNAL:
+ return "SCO_STATE_ACTIVE_EXTERNAL";
+ case SCO_STATE_ACTIVE_INTERNAL:
+ return "SCO_STATE_ACTIVE_INTERNAL";
+ case SCO_STATE_DEACTIVATING:
+ return "SCO_STATE_DEACTIVATING";
+ default:
+ return "SCO_STATE_(" + scoAudioState + ")";
+ }
+ }
+
//----------------------------------------------------------------------
/*package*/ static class BluetoothA2dpDeviceInfo {
private final @NonNull BluetoothDevice mBtDevice;
@@ -1007,4 +1028,20 @@
return "ENCODING_BT_CODEC_TYPE(" + btCodecType + ")";
}
}
+
+ //------------------------------------------------------------
+ /*package*/ void dump(PrintWriter pw, String prefix) {
+ pw.println("\n" + prefix + "mBluetoothHeadset: " + mBluetoothHeadset);
+ pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice);
+ pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState));
+ pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode));
+ pw.println(prefix + "Sco clients:");
+ mScoClients.forEach((cl) -> {
+ pw.println(" " + prefix + "pid: " + cl.getPid() + " cb: " + cl.getBinder()); });
+
+ pw.println("\n" + prefix + "mHearingAid: " + mHearingAid);
+ pw.println(prefix + "mA2dp: " + mA2dp);
+ pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported);
+ }
+
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 808f8c21..5a6ab4e5 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -978,6 +978,10 @@
}
protected void addLockoutResetCallback(IBiometricServiceLockoutResetCallback callback) {
+ if (callback == null) {
+ Slog.w(getTag(), "Null LockoutResetCallback");
+ return;
+ }
mHandler.post(() -> {
final LockoutResetMonitor monitor = new LockoutResetMonitor(callback);
if (!mLockoutMonitors.contains(monitor)) {
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 8520f5a..d90f3af 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -330,6 +330,7 @@
@Override
public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback)
throws RemoteException {
+ checkPermission(USE_BIOMETRIC_INTERNAL);
FingerprintService.super.addLockoutResetCallback(callback);
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index c54ebf8..3c05080 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -92,7 +92,7 @@
private final AppRequestObserver mAppRequestObserver;
private final SettingsObserver mSettingsObserver;
private final DisplayObserver mDisplayObserver;
- private final BrightnessObserver mBrightnessObserver;
+ private BrightnessObserver mBrightnessObserver;
private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener;
@@ -460,6 +460,21 @@
mVotesByDisplay = votesByDisplay;
}
+ @VisibleForTesting
+ void injectBrightnessObserver(BrightnessObserver brightnessObserver) {
+ mBrightnessObserver = brightnessObserver;
+ }
+
+ @VisibleForTesting
+ DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
+ float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
+ synchronized (mLock) {
+ mSettingsObserver.updateRefreshRateSettingLocked(
+ minRefreshRate, peakRefreshRate, defaultRefreshRate);
+ return getDesiredDisplayModeSpecs(Display.DEFAULT_DISPLAY);
+ }
+ }
+
/**
* Listens for changes refresh rate coordination.
*/
@@ -666,14 +681,18 @@
@VisibleForTesting
static final class Vote {
+ // DEFAULT_FRAME_RATE votes for [0, DEFAULT]. As the lowest priority vote, it's overridden
+ // by all other considerations. It acts to set a default frame rate for a device.
+ public static final int PRIORITY_DEFAULT_REFRESH_RATE = 0;
+
// LOW_BRIGHTNESS votes for a single refresh rate like [60,60], [90,90] or null.
// If the higher voters result is a range, it will fix the rate to a single choice.
// It's used to avoid rate switch in certain conditions.
- public static final int PRIORITY_LOW_BRIGHTNESS = 0;
+ public static final int PRIORITY_LOW_BRIGHTNESS = 1;
// SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
// It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
- public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 1;
+ public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 2;
// We split the app request into different priorities in case we can satisfy one desire
// without the other.
@@ -683,20 +702,20 @@
// @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
// System also forces some apps like blacklisted app to run at a lower refresh rate.
// @see android.R.array#config_highRefreshRateBlacklist
- public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 2;
- public static final int PRIORITY_APP_REQUEST_SIZE = 3;
+ public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 3;
+ public static final int PRIORITY_APP_REQUEST_SIZE = 4;
// SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest
// of low priority voters. It votes [0, max(PEAK, MIN)]
- public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 4;
+ public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 5;
// LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
- public static final int PRIORITY_LOW_POWER_MODE = 5;
+ public static final int PRIORITY_LOW_POWER_MODE = 6;
// Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
// APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
- public static final int MIN_PRIORITY = PRIORITY_LOW_BRIGHTNESS;
+ public static final int MIN_PRIORITY = PRIORITY_DEFAULT_REFRESH_RATE;
public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
// The cutoff for the app request refresh rate range. Votes with priorities lower than this
@@ -740,6 +759,8 @@
public static String priorityToString(int priority) {
switch (priority) {
+ case PRIORITY_DEFAULT_REFRESH_RATE:
+ return "PRIORITY_DEFAULT_REFRESH_RATE";
case PRIORITY_LOW_BRIGHTNESS:
return "PRIORITY_LOW_BRIGHTNESS";
case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
@@ -776,12 +797,15 @@
private final Context mContext;
private float mDefaultPeakRefreshRate;
+ private float mDefaultRefreshRate;
SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
super(handler);
mContext = context;
mDefaultPeakRefreshRate = (float) context.getResources().getInteger(
R.integer.config_defaultPeakRefreshRate);
+ mDefaultRefreshRate =
+ (float) context.getResources().getInteger(R.integer.config_defaultRefreshRate);
}
public void observe() {
@@ -849,17 +873,48 @@
Settings.System.MIN_REFRESH_RATE, 0f);
float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
+ updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
+ }
- updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
- Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate)));
+ private void updateRefreshRateSettingLocked(
+ float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
+ // TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is
+ // used to predict if we're going to be doing frequent refresh rate switching, and if
+ // so, enable the brightness observer. The logic here is more complicated and fragile
+ // than necessary, and we should improve it. See b/156304339 for more info.
+ Vote peakVote = peakRefreshRate == 0f
+ ? null
+ : Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate));
+ updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, peakVote);
updateVoteLocked(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE,
Vote.forRefreshRates(minRefreshRate, Float.POSITIVE_INFINITY));
+ Vote defaultVote =
+ defaultRefreshRate == 0f ? null : Vote.forRefreshRates(0f, defaultRefreshRate);
+ updateVoteLocked(Vote.PRIORITY_DEFAULT_REFRESH_RATE, defaultVote);
- mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, peakRefreshRate);
+ float maxRefreshRate;
+ if (peakRefreshRate == 0f && defaultRefreshRate == 0f) {
+ // We require that at least one of the peak or default refresh rate values are
+ // set. The brightness observer requires that we're able to predict whether or not
+ // we're going to do frequent refresh rate switching, and with the way the code is
+ // currently written, we need either a default or peak refresh rate value for that.
+ Slog.e(TAG, "Default and peak refresh rates are both 0. One of them should be set"
+ + " to a valid value.");
+ maxRefreshRate = minRefreshRate;
+ } else if (peakRefreshRate == 0f) {
+ maxRefreshRate = defaultRefreshRate;
+ } else if (defaultRefreshRate == 0f) {
+ maxRefreshRate = peakRefreshRate;
+ } else {
+ maxRefreshRate = Math.min(defaultRefreshRate, peakRefreshRate);
+ }
+
+ mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, maxRefreshRate);
}
public void dumpLocked(PrintWriter pw) {
pw.println(" SettingsObserver");
+ pw.println(" mDefaultRefreshRate: " + mDefaultRefreshRate);
pw.println(" mDefaultPeakRefreshRate: " + mDefaultPeakRefreshRate);
}
}
@@ -1014,7 +1069,8 @@
* {@link R.array#config_brightnessThresholdsOfPeakRefreshRate} and
* {@link R.array#config_ambientThresholdsOfPeakRefreshRate}.
*/
- private class BrightnessObserver extends ContentObserver {
+ @VisibleForTesting
+ public class BrightnessObserver extends ContentObserver {
// TODO: brightnessfloat: change this to the float setting
private final Uri mDisplayBrightnessSetting =
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 4f5a02a..2a65b33 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -201,7 +201,6 @@
private SurfaceControl.DisplayConfig[] mDisplayConfigs;
private Spline mSystemBrightnessToNits;
private Spline mNitsToHalBrightness;
- private boolean mHalBrightnessSupport;
private DisplayDeviceConfig mDisplayDeviceConfig;
@@ -225,7 +224,6 @@
}
mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken);
mGameContentTypeSupported = SurfaceControl.getGameContentTypeSupport(displayToken);
- mHalBrightnessSupport = SurfaceControl.getDisplayBrightnessSupport(displayToken);
mDisplayDeviceConfig = null;
// Defer configuration file loading
BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage(
@@ -717,11 +715,10 @@
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness("
+ "id=" + physicalDisplayId + ", brightness=" + brightness + ")");
try {
- // TODO: make it float
if (isHalBrightnessRangeSpecified()) {
brightness = displayBrightnessToHalBrightness(
- BrightnessSynchronizer.brightnessFloatToInt(getContext(),
- brightness));
+ BrightnessSynchronizer.brightnessFloatToIntRange(
+ getContext(), brightness));
}
if (mBacklight != null) {
mBacklight.setBrightness(brightness);
@@ -744,12 +741,13 @@
* Hal brightness space if the HAL brightness space has been provided via
* a display device configuration file.
*/
- private float displayBrightnessToHalBrightness(int brightness) {
+ private float displayBrightnessToHalBrightness(float brightness) {
if (!isHalBrightnessRangeSpecified()) {
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- if (brightness == 0) {
+ if (BrightnessSynchronizer.floatEquals(
+ brightness, PowerManager.BRIGHTNESS_OFF)) {
return PowerManager.BRIGHTNESS_OFF_FLOAT;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 549e336..9de95ab 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -2208,8 +2208,12 @@
public void setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled) {
enforceAccessPermission();
long token = Binder.clearCallingIdentity();
- HdmiControlService.this.setHdmiCecVolumeControlEnabled(isHdmiCecVolumeControlEnabled);
- Binder.restoreCallingIdentity(token);
+ try {
+ HdmiControlService.this.setHdmiCecVolumeControlEnabled(
+ isHdmiCecVolumeControlEnabled);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 93ef1264..52116a0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -56,6 +56,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
@@ -67,6 +68,7 @@
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManagerInternal;
import android.inputmethodservice.InputMethodService;
+import android.media.AudioManagerInternal;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -223,6 +225,8 @@
static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000;
+ static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
+
static final long TIME_TO_RECONNECT = 3 * 1000;
static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
@@ -308,6 +312,7 @@
final SettingsObserver mSettingsObserver;
final IWindowManager mIWindowManager;
final WindowManagerInternal mWindowManagerInternal;
+ final PackageManagerInternal mPackageManagerInternal;
final InputManagerInternal mInputManagerInternal;
private final DisplayManagerInternal mDisplayManagerInternal;
final HandlerCaller mCaller;
@@ -320,6 +325,16 @@
private final UserManager mUserManager;
private final UserManagerInternal mUserManagerInternal;
+ /**
+ * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
+ *
+ * <p>This field is used only within {@link #handleMessage(Message)} hence synchronization is
+ * not necessary.</p>
+ */
+ @Nullable
+ private AudioManagerInternal mAudioManagerInternal = null;
+
+
// All known input methods. mMethodMap also serves as the global
// lock for this class.
final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
@@ -643,6 +658,11 @@
IInputMethod mCurMethod;
/**
+ * If not {@link Process#INVALID_UID}, then the UID of {@link #mCurIntent}.
+ */
+ int mCurMethodUid = Process.INVALID_UID;
+
+ /**
* Time that we last initiated a bind to the input method, to determine
* if we should try to disconnect and reconnect to it.
*/
@@ -1625,6 +1645,7 @@
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mImeDisplayValidator = displayId -> mWindowManagerInternal.shouldShowIme(displayId);
@@ -2521,11 +2542,26 @@
return checker.displayCanShowIme(displayId) ? displayId : FALLBACK_DISPLAY_ID;
}
+ @AnyThread
+ private void scheduleNotifyImeUidToAudioService(int uid) {
+ mCaller.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
+ mCaller.obtainMessageI(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid).sendToTarget();
+ }
+
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
mCurMethod = IInputMethod.Stub.asInterface(service);
+ final String curMethodPackage = mCurIntent.getComponent().getPackageName();
+ final int curMethodUid = mPackageManagerInternal.getPackageUidInternal(
+ curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId());
+ if (curMethodUid < 0) {
+ Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage);
+ mCurMethodUid = Process.INVALID_UID;
+ } else {
+ mCurMethodUid = curMethodUid;
+ }
if (mCurToken == null) {
Slog.w(TAG, "Service connected without a token!");
unbindCurrentMethodLocked();
@@ -2535,6 +2571,7 @@
// Dispatch display id for InputMethodService to update context display.
executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken));
+ scheduleNotifyImeUidToAudioService(mCurMethodUid);
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
requestClientSessionLocked(mCurClient);
@@ -2656,6 +2693,8 @@
finishSessionLocked(mEnabledSession);
mEnabledSession = null;
mCurMethod = null;
+ mCurMethodUid = Process.INVALID_UID;
+ scheduleNotifyImeUidToAudioService(mCurMethodUid);
}
if (mStatusBar != null) {
mStatusBar.setIconVisibility(mSlotIme, false);
@@ -4277,6 +4316,17 @@
args.recycle();
return true;
}
+
+ // ---------------------------------------------------------------
+ case MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE: {
+ if (mAudioManagerInternal == null) {
+ mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
+ }
+ if (mAudioManagerInternal != null) {
+ mAudioManagerInternal.setInputMethodServiceUid(msg.arg1 /* uid */);
+ }
+ return true;
+ }
}
return false;
}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index fd8e03e..6acfd45 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -169,7 +169,9 @@
for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
if (device.isConnected()) {
BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
- mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+ if (newBtRoute.connectedProfiles.size() > 0) {
+ mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+ }
}
}
}
@@ -233,22 +235,32 @@
private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
newBtRoute.btDevice = device;
- // Current / Max volume will be set when connected.
+ // Current volume will be set when connected.
// TODO: Is there any BT device which has fixed volume?
String deviceName = device.getName();
if (TextUtils.isEmpty(deviceName)) {
deviceName = mContext.getResources().getText(R.string.unknownName).toString();
}
+ int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ newBtRoute.connectedProfiles = new SparseBooleanArray();
+ if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
+ newBtRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
+ }
+ if (mHearingAidProfile != null
+ && mHearingAidProfile.getConnectedDevices().contains(device)) {
+ newBtRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+ type = MediaRoute2Info.TYPE_HEARING_AID;
+ }
+
newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), deviceName)
.addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
.setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
.setDescription(mContext.getResources().getText(
R.string.bluetooth_a2dp_audio_route_name).toString())
- .setType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)
+ .setType(type)
.setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.build();
- newBtRoute.connectedProfiles = new SparseBooleanArray();
return newBtRoute;
}
@@ -316,7 +328,6 @@
btRoute = createBluetoothRoute(device);
mBluetoothRoutes.put(device.getAddress(), btRoute);
}
- btRoute.connectedProfiles.put(profile, true);
if (activeDevices.contains(device)) {
mSelectedRoute = btRoute;
setRouteConnectionState(mSelectedRoute,
@@ -378,18 +389,11 @@
BluetoothDevice.ERROR);
BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) {
- //TODO: The type of the new route is A2DP even when it's HEARING_AID.
- // We may determine the type of route when create the route.
btRoute = createBluetoothRoute(device);
- if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
- btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
+ if (btRoute.connectedProfiles.size() > 0) {
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ notifyBluetoothRoutesUpdated();
}
- if (mHearingAidProfile != null
- && mHearingAidProfile.getConnectedDevices().contains(device)) {
- btRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
- }
- mBluetoothRoutes.put(device.getAddress(), btRoute);
- notifyBluetoothRoutesUpdated();
} else if (bondState == BluetoothDevice.BOND_NONE
&& mBluetoothRoutes.remove(device.getAddress()) != null) {
notifyBluetoothRoutesUpdated();
@@ -430,9 +434,10 @@
if (state == BluetoothProfile.STATE_CONNECTED) {
if (btRoute == null) {
btRoute = createBluetoothRoute(device);
- mBluetoothRoutes.put(device.getAddress(), btRoute);
- btRoute.connectedProfiles.put(profile, true);
- notifyBluetoothRoutesUpdated();
+ if (btRoute.connectedProfiles.size() > 0) {
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ notifyBluetoothRoutesUpdated();
+ }
} else {
btRoute.connectedProfiles.put(profile, true);
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index a435f1e..53205ad 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 @@
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 @@
@Override
public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
+ mLastDiscoveryPreference = discoveryPreference;
if (mConnectionReady) {
mActiveConnection.updateDiscoveryPreference(discoveryPreference);
updateBinding();
- } else {
- mPendingDiscoveryPreference = discoveryPreference;
}
}
@@ -277,9 +276,8 @@
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/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index bc0e816..84ec440 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -1134,8 +1134,19 @@
if (cb == null) {
throw new IllegalArgumentException("Controller callback cannot be null");
}
- return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag,
- sessionInfo).getSessionBinder();
+ MediaSessionRecord session = createSessionInternal(
+ pid, uid, resolvedUserId, packageName, cb, tag, sessionInfo);
+ if (session == null) {
+ throw new IllegalStateException("Failed to create a new session record");
+ }
+ ISession sessionBinder = session.getSessionBinder();
+ if (sessionBinder == null) {
+ throw new IllegalStateException("Invalid session record");
+ }
+ return sessionBinder;
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception in creating a new session", e);
+ throw e;
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bc7bd23..86e8734 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2725,9 +2725,18 @@
Context appContext = r.getSbn().getPackageContext(getContext());
Notification.Builder nb =
Notification.Builder.recoverBuilder(appContext, r.getNotification());
- if (nb.getStyle() instanceof Notification.MessagingStyle && r.getShortcutInfo() == null) {
- mPreferencesHelper.setMessageSent(r.getSbn().getPackageName(), r.getUid());
- handleSavePolicyFile();
+ if (nb.getStyle() instanceof Notification.MessagingStyle) {
+ if (r.getShortcutInfo() != null) {
+ if (mPreferencesHelper.setValidMessageSent(
+ r.getSbn().getPackageName(), r.getUid())) {
+ handleSavePolicyFile();
+ }
+ } else {
+ if (mPreferencesHelper.setInvalidMessageSent(
+ r.getSbn().getPackageName(), r.getUid())) {
+ handleSavePolicyFile();
+ }
+ }
}
}
@@ -3158,9 +3167,22 @@
}
@Override
- public boolean hasSentMessage(String pkg, int uid) {
+ public boolean isInInvalidMsgState(String pkg, int uid) {
checkCallerIsSystem();
- return mPreferencesHelper.hasSentMessage(pkg, uid);
+ return mPreferencesHelper.isInInvalidMsgState(pkg, uid);
+ }
+
+ @Override
+ public boolean hasUserDemotedInvalidMsgApp(String pkg, int uid) {
+ checkCallerIsSystem();
+ return mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, uid);
+ }
+
+ @Override
+ public void setInvalidMsgAppDemoted(String pkg, int uid, boolean isDemoted) {
+ checkCallerIsSystem();
+ mPreferencesHelper.setInvalidMsgAppDemoted(pkg, uid, isDemoted);
+ handleSavePolicyFile();
}
@Override
@@ -5698,6 +5720,9 @@
Slog.w(TAG, "notification " + r.getKey() + " added an invalid shortcut");
}
r.setShortcutInfo(info);
+ r.setHasSentValidMsg(mPreferencesHelper.hasSentValidMsg(pkg, notificationUid));
+ r.userDemotedAppFromConvoSpace(
+ mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, notificationUid));
if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
r.getSbn().getOverrideGroupKey() != null)) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index a9fa2b1..c107822 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -188,6 +188,8 @@
private boolean mHasSeenSmartReplies;
private boolean mFlagBubbleRemoved;
private boolean mPostSilently;
+ private boolean mHasSentValidMsg;
+ private boolean mAppDemotedFromConvo;
/**
* Whether this notification (and its channels) should be considered user locked. Used in
* conjunction with user sentiment calculation.
@@ -1377,6 +1379,14 @@
return mShortcutInfo;
}
+ public void setHasSentValidMsg(boolean hasSentValidMsg) {
+ mHasSentValidMsg = hasSentValidMsg;
+ }
+
+ public void userDemotedAppFromConvoSpace(boolean userDemoted) {
+ mAppDemotedFromConvo = userDemoted;
+ }
+
/**
* Whether this notification is a conversation notification.
*/
@@ -1397,6 +1407,12 @@
&& mShortcutInfo == null) {
return false;
}
+ if (mHasSentValidMsg && mShortcutInfo == null) {
+ return false;
+ }
+ if (mAppDemotedFromConvo) {
+ return false;
+ }
return true;
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index c6ec95a..2b8ee92 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -62,7 +62,7 @@
/* android.stats.sysui.NotificationImportance importance_asst = 19 */
r.getAssistantImportance(),
/* int32 assistant_hash = 20 */ p.getAssistantHash(),
- /* float assistant_ranking_score = 21 */ 0 // TODO connect up ranking score
+ /* float assistant_ranking_score = 21 */ r.getRankingScore()
);
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index ec0fc4a..9d56d81 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -116,7 +116,9 @@
private static final String ATT_ENABLED = "enabled";
private static final String ATT_USER_ALLOWED = "allowed";
private static final String ATT_HIDE_SILENT = "hide_gentle";
- private static final String ATT_SENT_MESSAGE = "sent_invalid_msg";
+ private static final String ATT_SENT_INVALID_MESSAGE = "sent_invalid_msg";
+ private static final String ATT_SENT_VALID_MESSAGE = "sent_valid_msg";
+ private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app";
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
@@ -253,8 +255,12 @@
parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
r.lockedAppFields = XmlUtils.readIntAttribute(parser,
ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
- r.hasSentMessage = XmlUtils.readBooleanAttribute(
- parser, ATT_SENT_MESSAGE, false);
+ r.hasSentInvalidMessage = XmlUtils.readBooleanAttribute(
+ parser, ATT_SENT_INVALID_MESSAGE, false);
+ r.hasSentValidMessage = XmlUtils.readBooleanAttribute(
+ parser, ATT_SENT_VALID_MESSAGE, false);
+ r.userDemotedMsgApp = XmlUtils.readBooleanAttribute(
+ parser, ATT_USER_DEMOTED_INVALID_MSG_APP, false);
final int innerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -497,7 +503,9 @@
|| r.groups.size() > 0
|| r.delegate != null
|| r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE
- || r.hasSentMessage;
+ || r.hasSentInvalidMessage
+ || r.userDemotedMsgApp
+ || r.hasSentValidMessage;
if (hasNonDefaultSettings) {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
@@ -516,7 +524,12 @@
out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
Integer.toString(r.lockedAppFields));
- out.attribute(null, ATT_SENT_MESSAGE, Boolean.toString(r.hasSentMessage));
+ out.attribute(null, ATT_SENT_INVALID_MESSAGE,
+ Boolean.toString(r.hasSentInvalidMessage));
+ out.attribute(null, ATT_SENT_VALID_MESSAGE,
+ Boolean.toString(r.hasSentValidMessage));
+ out.attribute(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
+ Boolean.toString(r.userDemotedMsgApp));
if (!forBackup) {
out.attribute(null, ATT_UID, Integer.toString(r.uid));
@@ -635,15 +648,68 @@
updateConfig();
}
- public boolean hasSentMessage(String packageName, int uid) {
+ public boolean isInInvalidMsgState(String packageName, int uid) {
synchronized (mPackagePreferences) {
- return getOrCreatePackagePreferencesLocked(packageName, uid).hasSentMessage;
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+ return r.hasSentInvalidMessage && !r.hasSentValidMessage;
}
}
- public void setMessageSent(String packageName, int uid) {
+ public boolean hasUserDemotedInvalidMsgApp(String packageName, int uid) {
synchronized (mPackagePreferences) {
- getOrCreatePackagePreferencesLocked(packageName, uid).hasSentMessage = true;
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+ return isInInvalidMsgState(packageName, uid) ? r.userDemotedMsgApp : false;
+ }
+ }
+
+ public void setInvalidMsgAppDemoted(String packageName, int uid, boolean isDemoted) {
+ synchronized (mPackagePreferences) {
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+ r.userDemotedMsgApp = isDemoted;
+ }
+ }
+
+ public boolean setInvalidMessageSent(String packageName, int uid) {
+ synchronized (mPackagePreferences) {
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+ boolean valueChanged = r.hasSentInvalidMessage == false;
+ r.hasSentInvalidMessage = true;
+
+ return valueChanged;
+ }
+ }
+
+ public boolean setValidMessageSent(String packageName, int uid) {
+ synchronized (mPackagePreferences) {
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+ boolean valueChanged = r.hasSentValidMessage == false;
+ r.hasSentValidMessage = true;
+
+ return valueChanged;
+ }
+ }
+
+ @VisibleForTesting
+ boolean hasSentInvalidMsg(String packageName, int uid) {
+ synchronized (mPackagePreferences) {
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+ return r.hasSentInvalidMessage;
+ }
+ }
+
+ @VisibleForTesting
+ boolean hasSentValidMsg(String packageName, int uid) {
+ synchronized (mPackagePreferences) {
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+ return r.hasSentValidMessage;
+ }
+ }
+
+ @VisibleForTesting
+ boolean didUserEverDemoteInvalidMsgApp(String packageName, int uid) {
+ synchronized (mPackagePreferences) {
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+ return r.userDemotedMsgApp;
}
}
@@ -1870,6 +1936,9 @@
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());
}
}
@@ -2273,7 +2342,11 @@
boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
List<String> oemLockedChannels = new ArrayList<>();
boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE;
- boolean hasSentMessage = false;
+
+ boolean hasSentInvalidMessage = false;
+ boolean hasSentValidMessage = false;
+ // notE: only valid while hasSentMessage is false and hasSentInvalidMessage is true
+ boolean userDemotedMsgApp = false;
Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 70d1ade..f9d805e 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 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 @@
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 @@
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 7959461..d73d872 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1566,13 +1566,17 @@
// Recordkeeping of restore-after-install operations that are currently in flight
// between the Package Manager and the Backup Manager
static class PostInstallData {
+ @Nullable
public final InstallArgs args;
+ @NonNull
public final PackageInstalledInfo res;
+ @Nullable
public final Runnable mPostInstallRunnable;
- PostInstallData(InstallArgs _a, PackageInstalledInfo _r, Runnable postInstallRunnable) {
- args = _a;
- res = _r;
+ PostInstallData(@Nullable InstallArgs args, @NonNull PackageInstalledInfo res,
+ @Nullable Runnable postInstallRunnable) {
+ this.args = args;
+ this.res = res;
mPostInstallRunnable = postInstallRunnable;
}
}
@@ -1714,7 +1718,7 @@
if (data != null && data.mPostInstallRunnable != null) {
data.mPostInstallRunnable.run();
- } else if (data != null) {
+ } else if (data != null && data.args != null) {
InstallArgs args = data.args;
PackageInstalledInfo parentRes = data.res;
@@ -1732,26 +1736,12 @@
: args.whitelistedRestrictedPermissions;
int autoRevokePermissionsMode = args.autoRevokePermissionsMode;
- // Handle the parent package
handlePackagePostInstall(parentRes, grantPermissions,
killApp, virtualPreload, grantedPermissions,
whitelistedRestrictedPermissions, autoRevokePermissionsMode,
didRestore, args.installSource.installerPackageName, args.observer,
args.mDataLoaderType);
- // Handle the child packages
- final int childCount = (parentRes.addedChildPackages != null)
- ? parentRes.addedChildPackages.size() : 0;
- for (int i = 0; i < childCount; i++) {
- PackageInstalledInfo childRes = parentRes.addedChildPackages.valueAt(i);
- handlePackagePostInstall(childRes, grantPermissions,
- killApp, virtualPreload, grantedPermissions,
- whitelistedRestrictedPermissions, autoRevokePermissionsMode,
- false /*didRestore*/,
- args.installSource.installerPackageName, args.observer,
- args.mDataLoaderType);
- }
-
// Log tracing if needed
if (args.traceMethod != null) {
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, args.traceMethod,
@@ -2306,27 +2296,8 @@
// Work that needs to happen on first install within each user
if (firstUserIds != null && firstUserIds.length > 0) {
for (int userId : firstUserIds) {
- // If this app is a browser and it's newly-installed for some
- // users, clear any default-browser state in those users. The
- // app's nature doesn't depend on the user, so we can just check
- // its browser nature in any user and generalize.
- if (packageIsBrowser(packageName, userId)) {
- // If this browser is restored from user's backup, do not clear
- // default-browser state for this user
- if (pkgSetting.getInstallReason(userId)
- != PackageManager.INSTALL_REASON_DEVICE_RESTORE) {
- mPermissionManager.setDefaultBrowser(null, true, true, userId);
- }
- }
-
- // We may also need to apply pending (restored) runtime permission grants
- // within these users.
- mPermissionManager.restoreDelayedRuntimePermissions(packageName,
- UserHandle.of(userId));
-
- // Persistent preferred activity might have came into effect due to this
- // install.
- updateDefaultHomeNotLocked(userId);
+ clearRolesAndRestorePermissionsForNewUserInstall(packageName,
+ pkgSetting.getInstallReason(userId), userId);
}
}
@@ -7210,11 +7181,14 @@
sortResult = true;
}
} else {
- final AndroidPackage pkg = mPackages.get(pkgName);
+ final PackageSetting setting =
+ getPackageSettingInternal(pkgName, Process.SYSTEM_UID);
result = null;
- if (pkg != null) {
+ if (setting != null && setting.pkg != null
+ && !shouldFilterApplicationLocked(setting, filterCallingUid, userId)) {
result = filterIfNotSystemUser(mComponentResolver.queryActivities(
- intent, resolvedType, flags, pkg.getActivities(), userId), userId);
+ intent, resolvedType, flags, setting.pkg.getActivities(), userId),
+ userId);
}
if (result == null || result.size() == 0) {
// the caller wants to resolve for a particular package; however, there
@@ -13122,9 +13096,15 @@
createPackageInstalledInfo(PackageManager.INSTALL_SUCCEEDED);
res.pkg = pkgSetting.pkg;
res.newUsers = new int[]{ userId };
- PostInstallData postInstallData = intentSender == null ? null :
- new PostInstallData(null, res, () -> onRestoreComplete(res.returnCode,
- mContext, intentSender));
+
+ PostInstallData postInstallData =
+ new PostInstallData(null, res, () -> {
+ clearRolesAndRestorePermissionsForNewUserInstall(packageName,
+ pkgSetting.getInstallReason(userId), userId);
+ if (intentSender != null) {
+ onRestoreComplete(res.returnCode, mContext, intentSender);
+ }
+ });
restoreAndPostInstall(userId, res, postInstallData);
}
} finally {
@@ -13241,7 +13221,9 @@
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;
}
@@ -15801,7 +15783,6 @@
String returnMsg;
String installerPackageName;
PackageRemovedInfo removedInfo;
- ArrayMap<String, PackageInstalledInfo> addedChildPackages;
// The set of packages consuming this shared library or null if no consumers exist.
ArrayList<AndroidPackage> libraryConsumers;
PackageFreezer freezer;
@@ -15815,37 +15796,21 @@
public void setError(String msg, PackageParserException e) {
setReturnCode(e.error);
setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
- final int childCount = (addedChildPackages != null) ? addedChildPackages.size() : 0;
- for (int i = 0; i < childCount; i++) {
- addedChildPackages.valueAt(i).setError(msg, e);
- }
Slog.w(TAG, msg, e);
}
public void setError(String msg, PackageManagerException e) {
returnCode = e.error;
setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
- final int childCount = (addedChildPackages != null) ? addedChildPackages.size() : 0;
- for (int i = 0; i < childCount; i++) {
- addedChildPackages.valueAt(i).setError(msg, e);
- }
Slog.w(TAG, msg, e);
}
public void setReturnCode(int returnCode) {
this.returnCode = returnCode;
- final int childCount = (addedChildPackages != null) ? addedChildPackages.size() : 0;
- for (int i = 0; i < childCount; i++) {
- addedChildPackages.valueAt(i).returnCode = returnCode;
- }
}
private void setReturnMessage(String returnMsg) {
this.returnMsg = returnMsg;
- final int childCount = (addedChildPackages != null) ? addedChildPackages.size() : 0;
- for (int i = 0; i < childCount; i++) {
- addedChildPackages.valueAt(i).returnMsg = returnMsg;
- }
}
// In some error cases we want to convey more info back to the observer
@@ -17395,7 +17360,6 @@
int targetParseFlags = parseFlags;
final PackageSetting ps;
final PackageSetting disabledPs;
- final PackageSetting[] childPackages;
if (replace) {
if (parsedPackage.isStaticSharedLibrary()) {
// Static libs have a synthetic package name containing the version
@@ -18397,7 +18361,6 @@
final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
info.sendPackageRemovedBroadcasts(killApp);
info.sendSystemPackageUpdatedBroadcasts();
- info.sendSystemPackageAppearedBroadcasts();
}
// Force a gc here.
Runtime.getRuntime().gc();
@@ -18455,7 +18418,6 @@
SparseArray<int[]> broadcastWhitelist;
// Clean up resources deleted packages.
InstallArgs args = null;
- ArrayMap<String, PackageInstalledInfo> appearedChildPackages;
PackageRemovedInfo(PackageSender packageSender) {
this.packageSender = packageSender;
@@ -18471,18 +18433,6 @@
}
}
- void sendSystemPackageAppearedBroadcasts() {
- final int packageCount = (appearedChildPackages != null)
- ? appearedChildPackages.size() : 0;
- for (int i = 0; i < packageCount; i++) {
- PackageInstalledInfo installedInfo = appearedChildPackages.valueAt(i);
- packageSender.sendPackageAddedForNewUsers(installedInfo.name,
- true /*sendBootCompleted*/, false /*startReceiver*/,
- UserHandle.getAppId(installedInfo.uid), installedInfo.newUsers, null,
- DataLoaderType.NONE);
- }
- }
-
private void sendSystemPackageUpdatedBroadcastsInternal() {
Bundle extras = new Bundle(2);
extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid);
@@ -19749,6 +19699,30 @@
}
}
+ private void clearRolesAndRestorePermissionsForNewUserInstall(String packageName,
+ int installReason, @UserIdInt int userId) {
+ // If this app is a browser and it's newly-installed for some
+ // users, clear any default-browser state in those users. The
+ // app's nature doesn't depend on the user, so we can just check
+ // its browser nature in any user and generalize.
+ if (packageIsBrowser(packageName, userId)) {
+ // If this browser is restored from user's backup, do not clear
+ // default-browser state for this user
+ if (installReason != PackageManager.INSTALL_REASON_DEVICE_RESTORE) {
+ mPermissionManager.setDefaultBrowser(null, true, true, userId);
+ }
+ }
+
+ // We may also need to apply pending (restored) runtime permission grants
+ // within these users.
+ mPermissionManager.restoreDelayedRuntimePermissions(packageName,
+ UserHandle.of(userId));
+
+ // Persistent preferred activity might have came into effect due to this
+ // install.
+ updateDefaultHomeNotLocked(userId);
+ }
+
@Override
public void resetApplicationPreferences(int userId) {
mContext.enforceCallingOrSelfPermission(
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 7d49f78..163504c 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.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;
@@ -421,6 +422,10 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+ return;
+ }
+
mContext.getSystemService(PermissionControllerManager.class).dump(fd, args);
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 3336697..f075790 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 @@
}
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 12309f4..9871623 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -289,6 +289,7 @@
private StatsPullAtomCallbackImpl mStatsCallbackImpl;
private int mAppOpsSamplingRate = 0;
+ private final ArraySet<Integer> mDangerousAppOpsList = new ArraySet<>();
// Baselines that stores list of NetworkStats right after initializing, with associated
// information. This is used to calculate difference when pulling
@@ -320,17 +321,10 @@
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:
@@ -526,6 +520,25 @@
} catch (RemoteException e) {
Slog.e(TAG, "failed to initialize healthHalWrapper");
}
+
+ // Initialize list of AppOps related to DangerousPermissions
+ PackageManager pm = mContext.getPackageManager();
+ for (int op = 0; op < AppOpsManager._NUM_OP; op++) {
+ String perm = AppOpsManager.opToPermission(op);
+ if (perm == null) {
+ continue;
+ } else {
+ PermissionInfo permInfo;
+ try {
+ permInfo = pm.getPermissionInfo(perm, 0);
+ if (permInfo.getProtection() == PROTECTION_DANGEROUS) {
+ mDangerousAppOpsList.add(op);
+ }
+ } catch (PackageManager.NameNotFoundException exception) {
+ continue;
+ }
+ }
+ }
}
void registerEventListeners() {
@@ -619,10 +632,14 @@
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();
@@ -780,36 +797,42 @@
}
@NonNull
- private List<NetworkStatsExt> collectWifiBytesTransferSnapshot(boolean withFgbg) {
+ 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.
+ @NonNull
+ private List<NetworkStatsExt> collectUidNetworkStatsSnapshot(int transport, boolean withFgbg) {
final List<NetworkStatsExt> ret = new ArrayList<>();
- final NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard();
+ 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_WIFI, withFgbg));
+ ret.add(new NetworkStatsExt(stats, transport, withFgbg));
}
return ret;
}
- // Get a snapshot of mobile data usage. 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) {
- final List<NetworkStatsExt> ret = new ArrayList<>();
- final NetworkTemplate template =
- NetworkTemplate.buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL);
- final NetworkStats stats = getUidNetworkStatsSnapshot(template, withFgbg);
- if (stats != null) {
- ret.add(new NetworkStatsExt(stats, TRANSPORT_CELLULAR, 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.");
@@ -824,7 +847,7 @@
// 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(
@@ -3113,22 +3136,8 @@
e.writeLong(op.getBackgroundRejectCount(OP_FLAGS_PULLED));
e.writeLong(op.getForegroundAccessDuration(OP_FLAGS_PULLED));
e.writeLong(op.getBackgroundAccessDuration(OP_FLAGS_PULLED));
+ e.writeBoolean(mDangerousAppOpsList.contains(op.getOpCode()));
- String perm = AppOpsManager.opToPermission(op.getOpCode());
- if (perm == null) {
- e.writeBoolean(false);
- } else {
- PermissionInfo permInfo;
- try {
- permInfo = mContext.getPackageManager().getPermissionInfo(
- perm,
- 0);
- e.writeBoolean(
- permInfo.getProtection() == PROTECTION_DANGEROUS);
- } catch (PackageManager.NameNotFoundException exception) {
- e.writeBoolean(false);
- }
- }
if (atomTag == FrameworkStatsLog.ATTRIBUTED_APP_OPS) {
e.writeInt(mAppOpsSamplingRate);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e675afc..0598680 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1940,6 +1940,7 @@
mStartingData = null;
startingSurface = null;
startingWindow = null;
+ startingDisplayed = false;
if (surface == null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"startingWindow was set but startingSurface==null, couldn't "
@@ -5456,6 +5457,7 @@
if (mLastTransactionSequence != mWmService.mTransactionSequence) {
mLastTransactionSequence = mWmService.mTransactionSequence;
mNumDrawnWindows = 0;
+ startingDisplayed = false;
// There is the main base application window, even if it is exiting, wait for it
mNumInterestingWindows = findMainWindow(false /* includeStartingApp */) != null ? 1 : 0;
@@ -6682,6 +6684,13 @@
}
@Override
+ void getAnimationPosition(Point outPosition) {
+ // Always animate from zero because if the activity doesn't fill the task, the letterbox
+ // will fill the remaining area that should be included in the animation.
+ outPosition.set(0, 0);
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newParentConfig) {
if (mCompatDisplayInsets != null) {
Configuration overrideConfig = getRequestedOverrideConfiguration();
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index cb2d98c..2f868d9 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -382,6 +382,7 @@
return mBehindFullscreenActivity;
}
+ /** Returns {@code true} to stop the outer loop and indicate the result is computed. */
private boolean processActivity(ActivityRecord r, ActivityRecord topActivity) {
if (mAboveTop) {
if (r == topActivity) {
@@ -397,7 +398,10 @@
}
if (mHandlingOccluded) {
- mHandleBehindFullscreenActivity.accept(r);
+ // Iterating through all occluded activities.
+ if (mBehindFullscreenActivity) {
+ mHandleBehindFullscreenActivity.accept(r);
+ }
} else if (r == mToCheck) {
return true;
} else if (mBehindFullscreenActivity) {
@@ -1307,8 +1311,17 @@
/**
* Make sure that all activities that need to be visible in the stack (that is, they
* currently can be seen by the user) actually are and update their configuration.
+ * @param starting The top most activity in the task.
+ * The activity is either starting or resuming.
+ * Caller should ensure starting activity is visible.
+ * @param preserveWindows Flag indicating whether windows should be preserved when updating
+ * configuration in {@link mEnsureActivitiesVisibleHelper}.
+ * @param configChanges Parts of the configuration that changed for this activity for evaluating
+ * if the screen should be frozen as part of
+ * {@link mEnsureActivitiesVisibleHelper}.
+ *
*/
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
+ void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
boolean preserveWindows) {
ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
}
@@ -1317,9 +1330,19 @@
* Ensure visibility with an option to also update the configuration of visible activities.
* @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
* @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+ * @param starting The top most activity in the task.
+ * The activity is either starting or resuming.
+ * Caller should ensure starting activity is visible.
+ * @param notifyClients Flag indicating whether the visibility updates should be sent to the
+ * clients in {@link mEnsureActivitiesVisibleHelper}.
+ * @param preserveWindows Flag indicating whether windows should be preserved when updating
+ * configuration in {@link mEnsureActivitiesVisibleHelper}.
+ * @param configChanges Parts of the configuration that changed for this activity for evaluating
+ * if the screen should be frozen as part of
+ * {@link mEnsureActivitiesVisibleHelper}.
*/
// TODO: Should be re-worked based on the fact that each task as a stack in most cases.
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
+ void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
mTopActivityOccludesKeyguard = false;
mTopDismissingKeyguardActivity = null;
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index dfa3fe0..c28d47c 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 @@
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 @@
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/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 79e8ee3..bcdd6e3 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1539,7 +1539,10 @@
*
* Note: This method should only be called from {@link #startActivityUnchecked}.
*/
- private int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
+
+ // TODO(b/152429287): Make it easier to exercise code paths through startActivityInner
+ @VisibleForTesting
+ int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, Task inTask,
boolean restrictedBgActivity) {
@@ -1660,7 +1663,10 @@
// Also, we don't want to resume activities in a task that currently has an overlay
// as the starting activity just needs to be in the visible paused state until the
// over is removed.
- mTargetStack.ensureActivitiesVisible(mStartActivity, 0, !PRESERVE_WINDOWS);
+ // Passing {@code null} as the start parameter ensures all activities are made
+ // visible.
+ mTargetStack.ensureActivitiesVisible(null /* starting */,
+ 0 /* configChanges */, !PRESERVE_WINDOWS);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 78e4237..fdbb2b2 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 @@
}
@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 @@
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 @@
// 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 345cfb0..a45a15b 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 @@
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 @@
@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 3acb127..85c439c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3317,7 +3317,7 @@
mInputMethodWindow.getDisplayId());
}
mInsetsStateController.getSourceProvider(ITYPE_IME).setWindow(win,
- null /* frameProvider */, null /* imeFrameProvider */);
+ mDisplayPolicy.getImeSourceFrameProvider(), null /* imeFrameProvider */);
computeImeTarget(true /* updateImeTarget */);
updateImeControlTarget();
}
@@ -4700,9 +4700,11 @@
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/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 2e18fbf..2f18a0d 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1106,6 +1106,24 @@
}
}
+ TriConsumer<DisplayFrames, WindowState, Rect> getImeSourceFrameProvider() {
+ return (displayFrames, windowState, inOutFrame) -> {
+ if (mNavigationBar != null && navigationBarPosition(displayFrames.mDisplayWidth,
+ displayFrames.mDisplayHeight,
+ displayFrames.mRotation) == NAV_BAR_BOTTOM) {
+ // In gesture navigation, nav bar frame is larger than frame to calculate insets.
+ // IME should not provide frame which is smaller than the nav bar frame. Otherwise,
+ // nav bar might be overlapped with the content of the client when IME is shown.
+ sTmpRect.set(inOutFrame);
+ sTmpRect.intersectUnchecked(mNavigationBar.getFrameLw());
+ inOutFrame.inset(windowState.getGivenContentInsetsLw());
+ inOutFrame.union(sTmpRect);
+ } else {
+ inOutFrame.inset(windowState.getGivenContentInsetsLw());
+ }
+ };
+ }
+
private static void enforceSingleInsetsTypeCorrespondingToWindowType(int[] insetsTypes) {
int count = 0;
for (int insetsType : insetsTypes) {
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index c92de2b..c4e03f5 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -21,6 +21,7 @@
import static com.android.server.wm.ActivityStack.TAG_VISIBILITY;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
+import android.annotation.Nullable;
import android.util.Slog;
import com.android.internal.util.function.pooled.PooledConsumer;
@@ -42,6 +43,16 @@
mContiner = container;
}
+ /**
+ * Update all attributes except {@link mContiner} to use in subsequent calculations.
+ *
+ * @param starting The activity that is being started
+ * @param configChanges Parts of the configuration that changed for this activity for evaluating
+ * if the screen should be frozen.
+ * @param preserveWindows Flag indicating whether windows should be preserved when updating.
+ * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
+ * be sent to the clients.
+ */
void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
boolean notifyClients) {
mStarting = starting;
@@ -60,8 +71,17 @@
* Ensure visibility with an option to also update the configuration of visible activities.
* @see ActivityStack#ensureActivitiesVisible(ActivityRecord, int, boolean)
* @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+ * @param starting The top most activity in the task.
+ * The activity is either starting or resuming.
+ * Caller should ensure starting activity is visible.
+ *
+ * @param configChanges Parts of the configuration that changed for this activity for evaluating
+ * if the screen should be frozen.
+ * @param preserveWindows Flag indicating whether windows should be preserved when updating.
+ * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
+ * be sent to the clients.
*/
- void process(ActivityRecord starting, int configChanges, boolean preserveWindows,
+ void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows,
boolean notifyClients) {
reset(starting, configChanges, preserveWindows, notifyClients);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 1b1898b..efcd61d 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -469,8 +469,12 @@
public void accept(WindowState w) {
final InputChannel inputChannel = w.mInputChannel;
final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
+ final RecentsAnimationController recentsAnimationController =
+ mService.getRecentsAnimationController();
+ final boolean shouldApplyRecentsInputConsumer = recentsAnimationController != null
+ && recentsAnimationController.shouldApplyInputConsumer(w.mActivityRecord);
if (inputChannel == null || inputWindowHandle == null || w.mRemoved
- || w.cantReceiveTouchInput()) {
+ || (w.cantReceiveTouchInput() && !shouldApplyRecentsInputConsumer)) {
if (w.mWinAnimator.hasSurface()) {
mInputTransaction.setInputWindowInfo(
w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
@@ -486,22 +490,16 @@
final boolean hasFocus = w.isFocused();
final boolean isVisible = w.isVisibleLw();
- if (mAddRecentsAnimationInputConsumerHandle) {
- final RecentsAnimationController recentsAnimationController =
- mService.getRecentsAnimationController();
- if (recentsAnimationController != null
- && recentsAnimationController.shouldApplyInputConsumer(w.mActivityRecord)) {
- if (recentsAnimationController.updateInputConsumerForApp(
- mRecentsAnimationInputConsumer.mWindowHandle, hasFocus)) {
- mRecentsAnimationInputConsumer.show(mInputTransaction, w);
- mAddRecentsAnimationInputConsumerHandle = false;
- }
+ if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
+ if (recentsAnimationController.updateInputConsumerForApp(
+ mRecentsAnimationInputConsumer.mWindowHandle, hasFocus)) {
+ mRecentsAnimationInputConsumer.show(mInputTransaction, w);
+ mAddRecentsAnimationInputConsumerHandle = false;
}
}
if (w.inPinnedWindowingMode()) {
if (mAddPipInputConsumerHandle) {
-
final Task rootTask = w.getTask().getRootTask();
mPipInputConsumer.mWindowHandle.replaceTouchableRegionWithCrop(
rootTask.getSurfaceControl());
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index d6ddcd0..cbc1bdf 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -799,7 +799,8 @@
// Only apply the input consumer if it is enabled, it is not the target (home/recents)
// being revealed with the transition, and we are actively animating the app as a part of
// the animation
- return mInputConsumerEnabled && !isTargetApp(activity) && isAnimatingApp(activity);
+ return mInputConsumerEnabled && activity != null
+ && !isTargetApp(activity) && isAnimatingApp(activity);
}
boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle,
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a2979e6..0ecde72 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 @@
calculateDefaultMinimalSizeOfResizeableTasks();
final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
- defaultTaskDisplayArea.getOrCreateRootHomeTask();
+ defaultTaskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent,
false /* includingParents */);
}
@@ -2176,6 +2176,10 @@
// move the PIP activity into the task.
stack = taskDisplayArea.createStack(WINDOWING_MODE_UNDEFINED, r.getActivityType(),
ON_TOP, r.info, r.intent, false /* createdByOrganizer */);
+ // It's possible the task entering PIP is in freeform, so save the last
+ // non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore
+ // to its previous freeform bounds.
+ stack.setLastNonFullscreenBounds(task.mLastNonFullscreenBounds);
// There are multiple activities in the task and moving the top activity should
// reveal/leave the other activities in their original task.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 37a4c1f..6ce36f1 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 @@
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 45023ac..c6e1c95 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.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 @@
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 @@
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 @@
return;
}
}
- SystemClock.sleep(100);
+ SystemClock.sleep(DELAY_MS);
}
}
@@ -233,7 +238,7 @@
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 @@
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 @@
};
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 @@
}
@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/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index b9b6c08..d1cb210 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -238,23 +238,12 @@
}
}
- private final boolean isWallpaperVisible(WindowState wallpaperTarget) {
- final RecentsAnimationController recentsAnimationController =
- mService.getRecentsAnimationController();
- boolean isAnimatingWithRecentsComponent = recentsAnimationController != null
- && recentsAnimationController.isWallpaperVisible(wallpaperTarget);
- if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
- + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
- + " animating=" + ((wallpaperTarget != null && wallpaperTarget.mActivityRecord != null)
- ? wallpaperTarget.mActivityRecord.isAnimating(TRANSITION | PARENTS) : null)
- + " prev=" + mPrevWallpaperTarget
- + " recentsAnimationWallpaperVisible=" + isAnimatingWithRecentsComponent);
- return (wallpaperTarget != null
- && (!wallpaperTarget.mObscured
- || isAnimatingWithRecentsComponent
- || (wallpaperTarget.mActivityRecord != null
- && wallpaperTarget.mActivityRecord.isAnimating(TRANSITION | PARENTS))))
- || mPrevWallpaperTarget != null;
+ private boolean isWallpaperVisible(WindowState wallpaperTarget) {
+ if (DEBUG_WALLPAPER) {
+ Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + " prev="
+ + mPrevWallpaperTarget);
+ }
+ return wallpaperTarget != null || mPrevWallpaperTarget != null;
}
boolean isWallpaperTargetAnimating() {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 5d7ec12..7bfddd7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2120,6 +2120,11 @@
return getBounds();
}
+ /** Gets the position relative to parent for animation. */
+ void getAnimationPosition(Point outPosition) {
+ getRelativePosition(outPosition);
+ }
+
/**
* Applies the app transition animation according the given the layout properties in the
* window hierarchy.
@@ -2178,9 +2183,9 @@
// Separate position and size for use in animators.
mTmpRect.set(getAnimationBounds(appStackClipMode));
- if (sHierarchicalAnimations) {
- getRelativePosition(mTmpPoint);
- } else {
+ getAnimationPosition(mTmpPoint);
+ if (!sHierarchicalAnimations) {
+ // Non-hierarchical animation uses position in global coordinates.
mTmpPoint.set(mTmpRect.left, mTmpRect.top);
}
mTmpRect.offsetTo(0, 0);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a948ece..e925ce5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2191,9 +2191,9 @@
if (wasVisible) {
final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
- final int flags = startingWindow ? 0 /* self */ : PARENTS;
+
// Try starting an animation.
- if (mWinAnimator.applyAnimationLocked(transit, false, flags)) {
+ if (mWinAnimator.applyAnimationLocked(transit, false)) {
mAnimatingExit = true;
// mAnimatingExit affects canAffectSystemUiFlags(). Run layout such that
@@ -2205,9 +2205,7 @@
mWmService.mAccessibilityController.onWindowTransitionLocked(this, transit);
}
}
- final boolean isAnimating = startingWindow
- ? isAnimating(0)
- : isAnimating(TRANSITION | PARENTS)
+ final boolean isAnimating = isAnimating(TRANSITION | PARENTS)
&& (mActivityRecord == null || !mActivityRecord.isWaitingForTransitionStart());
final boolean lastWindowIsStartingWindow = startingWindow && mActivityRecord != null
&& mActivityRecord.isLastWindow(this);
@@ -2229,9 +2227,6 @@
}
}
- if (startingWindow && mActivityRecord != null) {
- mActivityRecord.startingDisplayed = false;
- }
removeImmediately();
// Removing a visible window will effect the computed orientation
// So just update orientation if needed.
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index e70f3e4..c570cf1 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1400,25 +1400,9 @@
* the switch statement below.
* @param isEntrance The animation type the last time this was called. Used to keep from
* loading the same animation twice.
- * @return {@code true} if an animation has been loaded, includes the parents.
- *
+ * @return true if an animation has been loaded.
*/
boolean applyAnimationLocked(int transit, boolean isEntrance) {
- return applyAnimationLocked(transit, isEntrance, PARENTS);
- }
-
- /**
- * Choose the correct animation and set it to the passed WindowState.
- * @param transit If AppTransition.TRANSIT_PREVIEW_DONE and the app window has been drawn
- * then the animation will be app_starting_exit. Any other value loads the animation from
- * the switch statement below.
- * @param isEntrance The animation type the last time this was called. Used to keep from
- * loading the same animation twice.
- * @param flags The combination of bitmask flags to specify targets and condition for
- * checking animating status. See {@link WindowContainer.AnimationFlags}.
- * @return {@code true} if an animation has been loaded.
- */
- boolean applyAnimationLocked(int transit, boolean isEntrance, int flags) {
if (mWin.isAnimating() && mAnimationIsEntrance == isEntrance) {
// If we are trying to apply an animation, but already running
// an animation of the same type, then just leave that one alone.
@@ -1488,7 +1472,7 @@
mWin.getDisplayContent().adjustForImeIfNeeded();
}
- return mWin.isAnimating(flags);
+ return mWin.isAnimating(PARENTS);
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 60d59b2..18c25c1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -247,6 +247,7 @@
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -11965,10 +11966,21 @@
}
private void showLocationSettingsChangedNotification(UserHandle user) {
+ Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ // Fill the component explicitly to prevent the PendingIntent from being intercepted
+ // and fired with crafted target. b/155183624
+ ActivityInfo targetInfo = intent.resolveActivityInfo(
+ mInjector.getPackageManager(user.getIdentifier()),
+ PackageManager.MATCH_SYSTEM_ONLY);
+ if (targetInfo != null) {
+ intent.setComponent(targetInfo.getComponentName());
+ } else {
+ Slog.wtf(LOG_TAG, "Failed to resolve intent for location settings");
+ }
+
PendingIntent locationSettingsIntent = mInjector.pendingIntentGetActivityAsUser(mContext, 0,
- new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_UPDATE_CURRENT,
- null, user);
+ intent, PendingIntent.FLAG_UPDATE_CURRENT, null, user);
Notification notification = new Notification.Builder(mContext,
SystemNotificationChannels.DEVICE_ADMIN)
.setSmallIcon(R.drawable.ic_info_outline)
@@ -15994,31 +16006,27 @@
private @PersonalAppsSuspensionReason int updatePersonalAppsSuspension(
int profileUserId, boolean unlocked) {
final boolean suspendedExplicitly;
- final int deadlineState;
- final String poPackage;
+ final boolean suspendedByTimeout;
synchronized (getLockObject()) {
final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId);
if (profileOwner != null) {
- deadlineState =
+ final int deadlineState =
updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked);
suspendedExplicitly = profileOwner.mSuspendPersonalApps;
- poPackage = profileOwner.info.getPackageName();
+ suspendedByTimeout = deadlineState == PROFILE_OFF_DEADLINE_REACHED;
+ Slog.d(LOG_TAG, String.format(
+ "Personal apps suspended explicitly: %b, deadline state: %d",
+ suspendedExplicitly, deadlineState));
+ final int notificationState =
+ unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState;
+ updateProfileOffDeadlineNotificationLocked(
+ profileUserId, profileOwner, notificationState);
} else {
- poPackage = null;
suspendedExplicitly = false;
- deadlineState = PROFILE_OFF_DEADLINE_DEFAULT;
+ suspendedByTimeout = false;
}
}
- Slog.d(LOG_TAG, String.format("Personal apps suspended explicitly: %b, deadline state: %d",
- suspendedExplicitly, deadlineState));
-
- if (poPackage != null) {
- final int notificationState = unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState;
- updateProfileOffDeadlineNotification(profileUserId, poPackage, notificationState);
- }
-
- final boolean suspendedByTimeout = deadlineState == PROFILE_OFF_DEADLINE_REACHED;
final int parentUserId = getProfileParentId(profileUserId);
suspendPersonalAppsInternal(parentUserId, suspendedExplicitly || suspendedByTimeout);
@@ -16130,9 +16138,9 @@
}
}
- private void updateProfileOffDeadlineNotification(
- int profileUserId, String profileOwnerPackage, int notificationState) {
-
+ @GuardedBy("getLockObject()")
+ private void updateProfileOffDeadlineNotificationLocked(
+ int profileUserId, ActiveAdmin profileOwner, int notificationState) {
if (notificationState == PROFILE_OFF_DEADLINE_DEFAULT) {
mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED);
return;
@@ -16150,11 +16158,23 @@
final Notification.Action turnProfileOnButton =
new Notification.Action.Builder(null /* icon */, buttonText, pendingIntent).build();
- final String text = mContext.getString(
- notificationState == PROFILE_OFF_DEADLINE_WARNING
- ? R.string.personal_apps_suspension_tomorrow_text
- : R.string.personal_apps_suspension_text);
- final boolean ongoing = notificationState == PROFILE_OFF_DEADLINE_REACHED;
+ final String text;
+ final boolean ongoing;
+ if (notificationState == PROFILE_OFF_DEADLINE_WARNING) {
+ // Round to the closest integer number of days.
+ final int maxDays = (int)
+ ((profileOwner.mProfileMaximumTimeOffMillis + MS_PER_DAY / 2) / MS_PER_DAY);
+ final String date = DateUtils.formatDateTime(
+ mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_DATE);
+ final String time = DateUtils.formatDateTime(
+ mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_TIME);
+ text = mContext.getString(
+ R.string.personal_apps_suspension_soon_text, date, time, maxDays);
+ ongoing = false;
+ } else {
+ text = mContext.getString(R.string.personal_apps_suspension_text);
+ ongoing = true;
+ }
final int color = mContext.getColor(R.color.personal_apps_suspension_notification_color);
final Bundle extras = new Bundle();
// TODO: Create a separate string for this.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
index 7cfbcc8..1630f27 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
@@ -22,9 +22,12 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.DateUtils;
+import android.util.Slog;
import com.android.internal.R;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -38,6 +41,7 @@
*/
class RemoteBugreportUtils {
+ private static final String TAG = "RemoteBugreportUtils";
static final int NOTIFICATION_ID = SystemMessage.NOTE_REMOTE_BUGREPORT;
@Retention(RetentionPolicy.SOURCE)
@@ -60,6 +64,17 @@
Intent dialogIntent = new Intent(Settings.ACTION_SHOW_REMOTE_BUGREPORT_DIALOG);
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
dialogIntent.putExtra(DevicePolicyManager.EXTRA_BUGREPORT_NOTIFICATION_TYPE, type);
+
+ // Fill the component explicitly to prevent the PendingIntent from being intercepted
+ // and fired with crafted target. b/155183624
+ ActivityInfo targetInfo = dialogIntent.resolveActivityInfo(
+ context.getPackageManager(), PackageManager.MATCH_SYSTEM_ONLY);
+ if (targetInfo != null) {
+ dialogIntent.setComponent(targetInfo.getComponentName());
+ } else {
+ Slog.wtf(TAG, "Failed to resolve intent for remote bugreport dialog");
+ }
+
PendingIntent pendingDialogIntent = PendingIntent.getActivityAsUser(context, type,
dialogIntent, 0, null, UserHandle.CURRENT);
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 78439db..f0dca77 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -63,6 +63,7 @@
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 @@
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/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 17dd286..724048b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -204,7 +204,7 @@
// Notification title and text for setManagedProfileMaximumTimeOff tests:
private static final String PROFILE_OFF_SUSPENSION_TITLE = "suspension_title";
private static final String PROFILE_OFF_SUSPENSION_TEXT = "suspension_text";
- private static final String PROFILE_OFF_SUSPENSION_TOMORROW_TEXT = "suspension_tomorrow_text";
+ private static final String PROFILE_OFF_SUSPENSION_SOON_TEXT = "suspension_tomorrow_text";
@Override
protected void setUp() throws Exception {
@@ -6331,7 +6331,7 @@
// Now the user should see a warning notification.
verify(getServices().notificationManager, times(1))
.notify(anyInt(), argThat(hasExtra(EXTRA_TITLE, PROFILE_OFF_SUSPENSION_TITLE,
- EXTRA_TEXT, PROFILE_OFF_SUSPENSION_TOMORROW_TEXT)));
+ EXTRA_TEXT, PROFILE_OFF_SUSPENSION_SOON_TEXT)));
// Apps shouldn't be suspended yet.
verifyZeroInteractions(getServices().ipackageManager);
clearInvocations(getServices().alarmManager);
@@ -6482,7 +6482,7 @@
// To allow creation of Notification via Notification.Builder
mContext.applicationInfo = mRealTestContext.getApplicationInfo();
- // Setup notification titles.
+ // Setup resources to render notification titles and texts.
when(mServiceContext.resources
.getString(R.string.personal_apps_suspension_title))
.thenReturn(PROFILE_OFF_SUSPENSION_TITLE);
@@ -6490,14 +6490,19 @@
.getString(R.string.personal_apps_suspension_text))
.thenReturn(PROFILE_OFF_SUSPENSION_TEXT);
when(mServiceContext.resources
- .getString(R.string.personal_apps_suspension_tomorrow_text))
- .thenReturn(PROFILE_OFF_SUSPENSION_TOMORROW_TEXT);
+ .getString(eq(R.string.personal_apps_suspension_soon_text),
+ anyString(), anyString(), anyInt()))
+ .thenReturn(PROFILE_OFF_SUSPENSION_SOON_TEXT);
+
+ // Make locale available for date formatting:
+ when(mServiceContext.resources.getConfiguration())
+ .thenReturn(mRealTestContext.getResources().getConfiguration());
clearInvocations(getServices().ipackageManager);
}
private static Matcher<Notification> hasExtra(String... extras) {
- assertEquals("Odd numebr of extra key-values", 0, extras.length % 2);
+ assertEquals("Odd number of extra key-values", 0, extras.length % 2);
return new BaseMatcher<Notification>() {
@Override
public boolean matches(Object item) {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 8137c36..43a396d 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
import android.content.Context;
import android.os.Handler;
@@ -28,6 +29,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.display.DisplayModeDirector.BrightnessObserver;
import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs;
import com.android.server.display.DisplayModeDirector.Vote;
@@ -36,6 +38,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SmallTest
@@ -52,16 +55,15 @@
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
}
- private DisplayModeDirector createDisplayModeDirectorWithDisplayFpsRange(
- int minFps, int maxFps) {
+ private DisplayModeDirector createDirectorFromRefreshRateArray(
+ float[] refreshRates, int baseModeId) {
DisplayModeDirector director =
new DisplayModeDirector(mContext, new Handler(Looper.getMainLooper()));
int displayId = 0;
- int numModes = maxFps - minFps + 1;
- Display.Mode[] modes = new Display.Mode[numModes];
- for (int i = minFps; i <= maxFps; i++) {
- modes[i - minFps] = new Display.Mode(
- /*modeId=*/i, /*width=*/1000, /*height=*/1000, /*refreshRate=*/i);
+ Display.Mode[] modes = new Display.Mode[refreshRates.length];
+ for (int i = 0; i < refreshRates.length; i++) {
+ modes[i] = new Display.Mode(
+ /*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]);
}
SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
supportedModesByDisplay.put(displayId, modes);
@@ -72,14 +74,22 @@
return director;
}
+ private DisplayModeDirector createDirectorFromFpsRange(int minFps, int maxFps) {
+ int numRefreshRates = maxFps - minFps + 1;
+ float[] refreshRates = new float[numRefreshRates];
+ for (int i = 0; i < numRefreshRates; i++) {
+ refreshRates[i] = minFps + i;
+ }
+ return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps);
+ }
+
@Test
public void testDisplayModeVoting() {
int displayId = 0;
// With no votes present, DisplayModeDirector should allow any refresh rate.
DesiredDisplayModeSpecs modeSpecs =
- createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayModeSpecs(
- displayId);
+ createDirectorFromFpsRange(60, 90).getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(modeSpecs.baseModeId).isEqualTo(60);
Truth.assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(0f);
Truth.assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(Float.POSITIVE_INFINITY);
@@ -92,7 +102,7 @@
{
int minFps = 60;
int maxFps = 90;
- DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
+ DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
assertTrue(2 * numPriorities < maxFps - minFps + 1);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -114,7 +124,7 @@
// presence of higher priority votes.
{
assertTrue(numPriorities >= 2);
- DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
+ DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(displayId, votes);
@@ -131,7 +141,7 @@
@Test
public void testVotingWithFloatingPointErrors() {
int displayId = 0;
- DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(50, 90);
+ DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(displayId, votes);
@@ -154,7 +164,7 @@
assertTrue(Vote.PRIORITY_LOW_BRIGHTNESS < Vote.PRIORITY_APP_REQUEST_SIZE);
int displayId = 0;
- DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
+ DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(displayId, votes);
@@ -202,7 +212,7 @@
>= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
int displayId = 0;
- DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
+ DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(displayId, votes);
@@ -235,4 +245,61 @@
.isWithin(FLOAT_TOLERANCE)
.of(75);
}
+
+ void verifySpecsWithRefreshRateSettings(DisplayModeDirector director, float minFps,
+ float peakFps, float defaultFps, float primaryMin, float primaryMax,
+ float appRequestMin, float appRequestMax) {
+ DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(
+ minFps, peakFps, defaultFps);
+ Truth.assertThat(specs.primaryRefreshRateRange.min).isEqualTo(primaryMin);
+ Truth.assertThat(specs.primaryRefreshRateRange.max).isEqualTo(primaryMax);
+ Truth.assertThat(specs.appRequestRefreshRateRange.min).isEqualTo(appRequestMin);
+ Truth.assertThat(specs.appRequestRefreshRateRange.max).isEqualTo(appRequestMax);
+ }
+
+ @Test
+ public void testSpecsFromRefreshRateSettings() {
+ // Confirm that, with varying settings for min, peak, and default refresh rate,
+ // DesiredDisplayModeSpecs is calculated correctly.
+ float[] refreshRates = {30.f, 60.f, 90.f, 120.f, 150.f};
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
+
+ float inf = Float.POSITIVE_INFINITY;
+ verifySpecsWithRefreshRateSettings(director, 0, 0, 0, 0, inf, 0, inf);
+ verifySpecsWithRefreshRateSettings(director, 0, 0, 90, 0, 90, 0, inf);
+ verifySpecsWithRefreshRateSettings(director, 0, 90, 0, 0, 90, 0, 90);
+ verifySpecsWithRefreshRateSettings(director, 0, 90, 60, 0, 60, 0, 90);
+ verifySpecsWithRefreshRateSettings(director, 0, 90, 120, 0, 90, 0, 90);
+ verifySpecsWithRefreshRateSettings(director, 90, 0, 0, 90, inf, 0, inf);
+ verifySpecsWithRefreshRateSettings(director, 90, 0, 120, 90, 120, 0, inf);
+ verifySpecsWithRefreshRateSettings(director, 90, 0, 60, 90, inf, 0, inf);
+ verifySpecsWithRefreshRateSettings(director, 90, 120, 0, 90, 120, 0, 120);
+ verifySpecsWithRefreshRateSettings(director, 90, 60, 0, 90, 90, 0, 90);
+ verifySpecsWithRefreshRateSettings(director, 60, 120, 90, 60, 90, 0, 120);
+ }
+
+ void verifyBrightnessObserverCall(DisplayModeDirector director, float minFps, float peakFps,
+ float defaultFps, float brightnessObserverMin, float brightnessObserverMax) {
+ BrightnessObserver brightnessObserver = Mockito.mock(BrightnessObserver.class);
+ director.injectBrightnessObserver(brightnessObserver);
+ director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(minFps, peakFps, defaultFps);
+ verify(brightnessObserver)
+ .onRefreshRateSettingChangedLocked(brightnessObserverMin, brightnessObserverMax);
+ }
+
+ @Test
+ public void testBrightnessObserverCallWithRefreshRateSettings() {
+ // Confirm that, with varying settings for min, peak, and default refresh rate, we make the
+ // correct call to the brightness observer.
+ float[] refreshRates = {60.f, 90.f, 120.f};
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
+ verifyBrightnessObserverCall(director, 0, 0, 0, 0, 0);
+ verifyBrightnessObserverCall(director, 0, 0, 90, 0, 90);
+ verifyBrightnessObserverCall(director, 0, 90, 0, 0, 90);
+ verifyBrightnessObserverCall(director, 0, 90, 60, 0, 60);
+ verifyBrightnessObserverCall(director, 90, 90, 0, 90, 90);
+ verifyBrightnessObserverCall(director, 120, 90, 0, 120, 90);
+ }
}
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 efa25bd..320dacf 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 @@
"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 e410220..2d45f9e 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 @@
@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/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 289933e..d45ecc9 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6650,18 +6650,14 @@
nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
waitForIdle();
- assertTrue(mBinderService.hasSentMessage(PKG, mUid));
+ assertTrue(mBinderService.isInInvalidMsgState(PKG, mUid));
}
@Test
public void testRecordMessages_validMsg() throws RemoteException {
- // Messaging notification with shortcut info
- Notification.BubbleMetadata metadata =
- new Notification.BubbleMetadata.Builder("id").build();
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
null /* groupKey */, false /* isSummary */);
- nb.setShortcutId("id");
- nb.setBubbleMetadata(metadata);
+ nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
"testRecordMessages_validMsg", mUid, 0, nb.build(), new UserHandle(mUid), null, 0);
NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -6670,7 +6666,43 @@
nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
waitForIdle();
- assertFalse(mBinderService.hasSentMessage(PKG, mUid));
+ assertTrue(mBinderService.isInInvalidMsgState(PKG, mUid));
+
+ nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testRecordMessages_validMsg");
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ assertFalse(mBinderService.isInInvalidMsgState(PKG, mUid));
+ }
+
+ @Test
+ public void testRecordMessages_invalidMsg_afterValidMsg() throws RemoteException {
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testRecordMessages_invalidMsg_afterValidMsg_1");
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+ assertTrue(mService.getNotificationRecord(nr.getKey()).isConversation());
+
+ mBinderService.cancelAllNotifications(PKG, mUid);
+ waitForIdle();
+
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+ null /* groupKey */, false /* isSummary */);
+ nb.setShortcutId(null);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "testRecordMessages_invalidMsg_afterValidMsg_2", mUid, 0, nb.build(),
+ new UserHandle(mUid), null, 0);
+ nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ assertFalse(mService.getNotificationRecord(nr.getKey()).isConversation());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index b03596a..6df3c7b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -1137,6 +1137,26 @@
}
@Test
+ public void testIsConversation_noShortcut_appHasPreviousSentFullConversation() {
+ StatusBarNotification sbn = getMessagingStyleNotification();
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ record.setShortcutInfo(null);
+ record.setHasSentValidMsg(true);
+
+ assertFalse(record.isConversation());
+ }
+
+ @Test
+ public void testIsConversation_noShortcut_userDemotedApp() {
+ StatusBarNotification sbn = getMessagingStyleNotification();
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ record.setShortcutInfo(null);
+ record.userDemotedAppFromConvoSpace(true);
+
+ assertFalse(record.isConversation());
+ }
+
+ @Test
public void testIsConversation_noShortcut_targetsR() {
StatusBarNotification sbn = getMessagingStyleNotification(PKG_R);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
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 4320f1c..078c21e 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.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 com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase;
+
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Before;
@@ -454,7 +456,9 @@
mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false);
mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
- mHelper.setMessageSent(PKG_P, UID_P);
+ mHelper.setInvalidMessageSent(PKG_P, UID_P);
+ mHelper.setValidMessageSent(PKG_P, UID_P);
+ mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true);
mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE);
@@ -470,8 +474,10 @@
assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_O, UID_O));
assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
- assertTrue(mHelper.hasSentMessage(PKG_P, UID_P));
- assertFalse(mHelper.hasSentMessage(PKG_N_MR1, UID_N_MR1));
+ assertTrue(mHelper.hasSentInvalidMsg(PKG_P, UID_P));
+ assertFalse(mHelper.hasSentInvalidMsg(PKG_N_MR1, UID_N_MR1));
+ assertTrue(mHelper.hasSentValidMsg(PKG_P, UID_P));
+ assertTrue(mHelper.didUserEverDemoteInvalidMsgApp(PKG_P, UID_P));
assertEquals(channel1,
mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
compareChannels(channel2,
@@ -2992,6 +2998,31 @@
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";
@@ -3380,15 +3411,49 @@
}
@Test
- public void testMessageSent() {
+ public void testInvalidMessageSent() {
// create package preferences
mHelper.canShowBadge(PKG_P, UID_P);
// check default value
- assertFalse(mHelper.hasSentMessage(PKG_P, UID_P));
+ assertFalse(mHelper.isInInvalidMsgState(PKG_P, UID_P));
// change it
- mHelper.setMessageSent(PKG_P, UID_P);
- assertTrue(mHelper.hasSentMessage(PKG_P, UID_P));
+ mHelper.setInvalidMessageSent(PKG_P, UID_P);
+ assertTrue(mHelper.isInInvalidMsgState(PKG_P, UID_P));
+ assertTrue(mHelper.hasSentInvalidMsg(PKG_P, UID_P));
+ }
+
+ @Test
+ public void testValidMessageSent() {
+ // create package preferences
+ mHelper.canShowBadge(PKG_P, UID_P);
+
+ // get into the bad state
+ mHelper.setInvalidMessageSent(PKG_P, UID_P);
+
+ // and then fix it
+ mHelper.setValidMessageSent(PKG_P, UID_P);
+
+ assertTrue(mHelper.hasSentValidMsg(PKG_P, UID_P));
+ assertFalse(mHelper.isInInvalidMsgState(PKG_P, UID_P));
+ }
+
+ @Test
+ public void testUserDemotedInvalidMsgApp() {
+ // create package preferences
+ mHelper.canShowBadge(PKG_P, UID_P);
+
+ // demotion means nothing before msg notif sent
+ mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true);
+ assertFalse(mHelper.hasUserDemotedInvalidMsgApp(PKG_P, UID_P));
+
+ // it's valid when incomplete msgs have been sent
+ mHelper.setInvalidMessageSent(PKG_P, UID_P);
+ assertTrue(mHelper.hasUserDemotedInvalidMsgApp(PKG_P, UID_P));
+
+ // and is invalid once complete msgs are sent
+ mHelper.setValidMessageSent(PKG_P, UID_P);
+ assertFalse(mHelper.hasUserDemotedInvalidMsgApp(PKG_P, UID_P));
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 822cb5a..64e08c6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -76,6 +76,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
/**
* Tests for the {@link ActivityStack} class.
*
@@ -1327,6 +1330,8 @@
@Test
public void testCheckBehindFullscreenActivity() {
+ final ArrayList<ActivityRecord> occludedActivities = new ArrayList<>();
+ final Consumer<ActivityRecord> handleBehindFullscreenActivity = occludedActivities::add;
final ActivityRecord bottomActivity =
new ActivityBuilder(mService).setStack(mStack).setTask(mTask).build();
final ActivityRecord topActivity =
@@ -1337,12 +1342,21 @@
assertFalse(mStack.checkBehindFullscreenActivity(topActivity,
null /* handleBehindFullscreenActivity */));
+ // Top activity occludes bottom activity.
+ mStack.checkBehindFullscreenActivity(null /* toCheck */, handleBehindFullscreenActivity);
+ assertThat(occludedActivities).containsExactly(bottomActivity);
+
doReturn(false).when(topActivity).occludesParent();
assertFalse(mStack.checkBehindFullscreenActivity(bottomActivity,
null /* handleBehindFullscreenActivity */));
assertFalse(mStack.checkBehindFullscreenActivity(topActivity,
null /* handleBehindFullscreenActivity */));
+ occludedActivities.clear();
+ // Top activity doesn't occlude parent, so the bottom activity is not occluded.
+ mStack.checkBehindFullscreenActivity(null /* toCheck */, handleBehindFullscreenActivity);
+ assertThat(occludedActivities).isEmpty();
+
final ActivityRecord finishingActivity =
new ActivityBuilder(mService).setStack(mStack).setTask(mTask).build();
finishingActivity.finishing = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index edc9756..02d1f9b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -49,6 +49,7 @@
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.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -56,10 +57,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -99,7 +100,6 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class ActivityStarterTests extends ActivityTestsBase {
- private ActivityStarter mStarter;
private ActivityStartController mController;
private ActivityMetricsLogger mActivityMetricsLogger;
private PackageManagerInternal mMockPackageManager;
@@ -127,8 +127,6 @@
mController = mock(ActivityStartController.class);
mActivityMetricsLogger = mock(ActivityMetricsLogger.class);
clearInvocations(mActivityMetricsLogger);
- mStarter = new ActivityStarter(mController, mService, mService.mStackSupervisor,
- mock(ActivityStartInterceptor.class));
}
@Test
@@ -181,6 +179,7 @@
* {@link ActivityStarter#execute} based on these preconditions and ensures the result matches
* the expected. It is important to note that the method also checks side effects of the start,
* such as ensuring {@link ActivityOptions#abort()} is called in the relevant scenarios.
+ *
* @param preconditions A bitmask representing the preconditions for the launch
* @param launchFlags The launch flags to be provided by the launch {@link Intent}.
* @param expectedResult The expected result from the launch.
@@ -202,7 +201,7 @@
final WindowProcessController wpc =
containsConditions(preconditions, PRECONDITION_NO_CALLER_APP)
? null : new WindowProcessController(service, ai, null, 0, -1, null, listener);
- doReturn(wpc).when(service).getProcessController(anyObject());
+ doReturn(wpc).when(service).getProcessController(any());
final Intent intent = new Intent();
intent.setFlags(launchFlags);
@@ -1034,4 +1033,46 @@
verify(recentTasks, times(1)).add(any());
}
+
+ @Test
+ public void testStartActivityInner_allSplitScreenPrimaryActivitiesVisible() {
+ // Given
+ final ActivityStarter starter = prepareStarter(0, false);
+
+ starter.setReason("testAllSplitScreenPrimaryActivitiesAreResumed");
+
+ final ActivityRecord targetRecord = new ActivityBuilder(mService).build();
+ targetRecord.setFocusable(false);
+ targetRecord.setVisibility(false);
+ final ActivityRecord sourceRecord = new ActivityBuilder(mService).build();
+
+ final ActivityStack stack = spy(
+ mRootWindowContainer.getDefaultTaskDisplayArea()
+ .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
+ /* onTop */true));
+
+ stack.addChild(targetRecord);
+
+ doReturn(stack).when(mRootWindowContainer)
+ .getLaunchStack(any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt());
+
+ starter.mStartActivity = new ActivityBuilder(mService).build();
+
+ // When
+ starter.startActivityInner(
+ /* r */targetRecord,
+ /* sourceRecord */ sourceRecord,
+ /* voiceSession */null,
+ /* voiceInteractor */ null,
+ /* startFlags */ 0,
+ /* doResume */true,
+ /* options */null,
+ /* inTask */null,
+ /* restrictedBgActivity */false);
+
+ // Then
+ verify(stack).ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ verify(targetRecord).makeVisibleIfNeeded(null, true);
+ assertTrue(targetRecord.mVisibleRequested);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 8f18f64..07050d9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -52,6 +52,7 @@
import static org.mockito.Mockito.verify;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.IWindowManager;
@@ -432,20 +433,35 @@
removeGlobalMinSizeRestriction();
final Rect stackBounds = new Rect(0, 0, 1000, 600);
final Rect taskBounds = new Rect(100, 400, 600, 800);
- mStack.setBounds(stackBounds);
- mTask.setBounds(taskBounds);
+ // Set the bounds and windowing mode to window configuration directly, otherwise the
+ // testing setups may be discarded by configuration resolving.
+ mStack.getWindowConfiguration().setBounds(stackBounds);
+ mTask.getWindowConfiguration().setBounds(taskBounds);
+ mActivity.getWindowConfiguration().setBounds(taskBounds);
// Check that anim bounds for freeform window match task bounds
- mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_NONE));
// STACK_CLIP_AFTER_ANIM should use task bounds since they will be clipped by
// bounds animation layer.
- mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
+ // Even the activity is smaller than task and it is not aligned to the top-left corner of
+ // task, the animation bounds the same as task and position should be zero because in real
+ // case the letterbox will fill the remaining area in task.
+ final Rect halfBounds = new Rect(taskBounds);
+ halfBounds.scale(0.5f);
+ mActivity.getWindowConfiguration().setBounds(halfBounds);
+ final Point animationPosition = new Point();
+ mActivity.getAnimationPosition(animationPosition);
+
+ assertEquals(taskBounds, mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
+ assertEquals(new Point(0, 0), animationPosition);
+
// STACK_CLIP_BEFORE_ANIM should use stack bounds since it won't be clipped later.
- mTask.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
assertEquals(mStack.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_BEFORE_ANIM));
}
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 6a1f50d..f4b50dc 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.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 @@
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/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index c2db0c0..a3f9b2e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -16,7 +16,9 @@
package com.android.server.wm;
+import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.Surface.ROTATION_0;
import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -56,7 +58,10 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+import android.view.InsetsSource;
import android.view.InsetsState;
+import android.view.WindowInsets.Side;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -352,4 +357,36 @@
insetsPolicy.updateBarControlTarget(mAppWindow);
assertNull(displayPolicy.mInputConsumer);
}
+
+ @Test
+ public void testImeMinimalSourceFrame() {
+ final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ displayInfo.rotation = ROTATION_0;
+ mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(),
+ displayInfo, null /* displayCutout */);
+
+ displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
+ mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
+
+ mDisplayContent.setInputMethodWindowLocked(mImeWindow);
+ mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
+ mImeWindow.getGivenContentInsetsLw().set(0, displayInfo.logicalHeight, 0, 0);
+ mImeWindow.getControllableInsetProvider().setServerVisible(true);
+
+ displayPolicy.beginLayoutLw(mDisplayContent.mDisplayFrames, 0 /* UI mode */);
+ displayPolicy.layoutWindowLw(mImeWindow, null, mDisplayContent.mDisplayFrames);
+
+ final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
+ final InsetsSource imeSource = state.peekSource(ITYPE_IME);
+ final InsetsSource navBarSource = state.peekSource(ITYPE_NAVIGATION_BAR);
+
+ assertNotNull(imeSource);
+ assertNotNull(navBarSource);
+ assertFalse(imeSource.getFrame().isEmpty());
+ assertFalse(navBarSource.getFrame().isEmpty());
+ assertTrue(imeSource.getFrame().contains(navBarSource.getFrame()));
+ }
}
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 88de34d..bdcae48 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 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.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 @@
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 42e2bbf..6c13cd7 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 @@
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/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index ead90bb..3365ab7 100755
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -22,6 +22,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -1635,13 +1636,21 @@
/**
* Instructs Telecom to put the call into the background audio processing state.
- *
+ * <p>
* This method can be called either when the call is in {@link #STATE_RINGING} or
* {@link #STATE_ACTIVE}. After Telecom acknowledges the request by setting the call's state to
* {@link #STATE_AUDIO_PROCESSING}, your app may setup the audio paths with the audio stack in
* order to capture and play audio on the call stream.
- *
+ * <p>
* This method can only be called by the default dialer app.
+ * <p>
+ * Apps built with SDK version {@link android.os.Build.VERSION_CODES#R} or later which are using
+ * the microphone as part of audio processing should specify the foreground service type using
+ * the attribute {@link android.R.attr#foregroundServiceType} in the {@link InCallService}
+ * service element of the app's manifest file.
+ * The {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE} attribute should be specified.
+ * @see <a href="https://developer.android.com/preview/privacy/foreground-service-types">
+ * the Android Developer Site</a> for more information.
* @hide
*/
@SystemApi
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index f8722f4..8abab90 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -23,6 +23,7 @@
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
@@ -281,9 +282,20 @@
* Sets whether to request background audio processing so that the in-call service can
* screen the call further. If set to {@code true}, {@link #setDisallowCall} should be
* called with {@code false}, and all other parameters in this builder will be ignored.
- *
+ * <p>
* This request will only be honored if the {@link CallScreeningService} shares the same
* uid as the default dialer app. Otherwise, the call will go through as usual.
+ * <p>
+ * Apps built with SDK version {@link android.os.Build.VERSION_CODES#R} or later which
+ * are using the microphone as part of audio processing should specify the
+ * foreground service type using the attribute
+ * {@link android.R.attr#foregroundServiceType} in the {@link CallScreeningService}
+ * service element of the app's manifest file.
+ * The {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE} attribute should be
+ * specified.
+ * @see
+ * <a href="https://developer.android.com/preview/privacy/foreground-service-types">
+ * the Android Developer Site</a> for more information.
*
* @param shouldScreenCallViaAudioProcessing Whether to request further call screening.
* @hide
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index bb6f154..b35b323 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -536,13 +536,16 @@
// 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 @@
* @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 3546434..d62cd0a 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -837,20 +837,20 @@
+ " 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 ede67dd..94407f1 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -56,14 +56,15 @@
* 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 05ab6bd..ec11279 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -363,9 +363,10 @@
/**
* 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.
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index fe0224a..ebe9b57 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -222,6 +222,10 @@
return sLoopers.get(test);
}
+ public static void remove(Object test) {
+ sLoopers.remove(test);
+ }
+
static class LooperFrameworkMethod extends FrameworkMethod {
private HandlerThread mHandlerThread;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 7d20d0d..5f46cb3 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -817,6 +817,9 @@
/**
* Broadcast intent action indicating that the wifi network settings
* had been reset.
+ *
+ * Note: This intent is sent as a directed broadcast to each manifest registered receiver.
+ * Intent will not be received by dynamically registered receivers.
* @hide
*/
@SystemApi