Merge "Introduce static @hide PowerManager.isRebootingUserspaceSupportedImpl()" into rvc-dev
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 99e82e7..579963b 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -94,7 +94,6 @@
// TODO(b/135922046) remove this
include_dirs: ["frameworks/base/core/java"],
},
- sdk_version: "system_current",
}
droidstubs {
diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp
index 793247e..fc9052e 100644
--- a/apex/permission/framework/Android.bp
+++ b/apex/permission/framework/Android.bp
@@ -46,12 +46,10 @@
name: "framework-permission-stubs-defaults",
srcs: [ ":framework-permission-sources" ],
libs: [ "framework-annotations-lib" ],
- sdk_version: "system_current",
}
droidstubs {
name: "framework-permission-stubs-srcs-publicapi",
- sdk_version: "system_current",
defaults: [
"framework-module-stubs-defaults-publicapi",
"framework-permission-stubs-defaults",
@@ -60,7 +58,6 @@
droidstubs {
name: "framework-permission-stubs-srcs-systemapi",
- sdk_version: "system_current",
defaults: [
"framework-module-stubs-defaults-systemapi",
"framework-permission-stubs-defaults",
@@ -69,7 +66,6 @@
droidstubs {
name: "framework-permission-api-module_libs_api",
- sdk_version: "system_current",
defaults: [
"framework-module-api-defaults-module_libs_api",
"framework-permission-stubs-defaults",
@@ -78,7 +74,6 @@
droidstubs {
name: "framework-permission-stubs-srcs-module_libs_api",
- sdk_version: "system_current",
defaults: [
"framework-module-stubs-defaults-module_libs_api",
"framework-permission-stubs-defaults",
diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp
index 707113b..3eabb88 100644
--- a/apex/sdkextensions/framework/Android.bp
+++ b/apex/sdkextensions/framework/Android.bp
@@ -48,7 +48,6 @@
name: "framework-sdkextensions-stubs-defaults",
srcs: [ ":framework-sdkextensions-sources" ],
libs: [ "framework-annotations-lib" ],
- sdk_version: "system_current",
}
droidstubs {
diff --git a/api/test-current.txt b/api/test-current.txt
index 22b051b..641767c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -214,6 +214,7 @@
field public static final String OPSTR_GPS = "android:gps";
field public static final String OPSTR_INSTANT_APP_START_FOREGROUND = "android:instant_app_start_foreground";
field public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage";
+ field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage";
field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels";
field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
diff --git a/cmds/statsd/benchmark/filter_value_benchmark.cpp b/cmds/statsd/benchmark/filter_value_benchmark.cpp
index 28bf21a..743ccc4 100644
--- a/cmds/statsd/benchmark/filter_value_benchmark.cpp
+++ b/cmds/statsd/benchmark/filter_value_benchmark.cpp
@@ -14,12 +14,14 @@
* limitations under the License.
*/
#include <vector>
-#include "benchmark/benchmark.h"
+
#include "FieldValue.h"
#include "HashableDimensionKey.h"
+#include "benchmark/benchmark.h"
#include "logd/LogEvent.h"
-#include "stats_log_util.h"
+#include "metric_util.h"
#include "stats_event.h"
+#include "stats_log_util.h"
namespace android {
namespace os {
@@ -34,24 +36,13 @@
std::vector<int> attributionUids = {100, 100};
std::vector<string> attributionTags = {"LOCATION", "LOCATION"};
+ writeAttribution(statsEvent, attributionUids, attributionTags);
- vector<const char*> cTags(attributionTags.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = attributionTags[i].c_str();
- }
-
- AStatsEvent_writeAttributionChain(statsEvent,
- reinterpret_cast<const uint32_t*>(attributionUids.data()),
- cTags.data(), attributionUids.size());
AStatsEvent_writeFloat(statsEvent, 3.2f);
AStatsEvent_writeString(statsEvent, "LOCATION");
AStatsEvent_writeInt64(statsEvent, 990);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- event->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, event);
field_matcher->set_field(1);
auto child = field_matcher->add_child();
diff --git a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
index c7d01cc..7a45565 100644
--- a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
+++ b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
@@ -14,12 +14,14 @@
* limitations under the License.
*/
#include <vector>
-#include "benchmark/benchmark.h"
+
#include "FieldValue.h"
#include "HashableDimensionKey.h"
+#include "benchmark/benchmark.h"
#include "logd/LogEvent.h"
-#include "stats_log_util.h"
+#include "metric_util.h"
#include "stats_event.h"
+#include "stats_log_util.h"
namespace android {
namespace os {
@@ -34,24 +36,13 @@
std::vector<int> attributionUids = {100, 100};
std::vector<string> attributionTags = {"LOCATION", "LOCATION"};
+ writeAttribution(statsEvent, attributionUids, attributionTags);
- vector<const char*> cTags(attributionTags.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = attributionTags[i].c_str();
- }
-
- AStatsEvent_writeAttributionChain(statsEvent,
- reinterpret_cast<const uint32_t*>(attributionUids.data()),
- cTags.data(), attributionUids.size());
AStatsEvent_writeFloat(statsEvent, 3.2f);
AStatsEvent_writeString(statsEvent, "LOCATION");
AStatsEvent_writeInt64(statsEvent, 990);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- event->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, event);
link->conditionId = 1;
diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp
index 482d66f..89fd3d9 100644
--- a/cmds/statsd/benchmark/metric_util.cpp
+++ b/cmds/statsd/benchmark/metric_util.cpp
@@ -247,21 +247,37 @@
return dimensions;
}
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+ const vector<string>& attributionTags) {
+ vector<const char*> cTags(attributionTags.size());
+ for (int i = 0; i < cTags.size(); i++) {
+ cTags[i] = attributionTags[i].c_str();
+ }
+
+ AStatsEvent_writeAttributionChain(statsEvent,
+ reinterpret_cast<const uint32_t*>(attributionUids.data()),
+ cTags.data(), attributionUids.size());
+}
+
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) {
+ AStatsEvent_build(statsEvent);
+
+ size_t size;
+ uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ logEvent->parseBuffer(buf, size);
+
+ AStatsEvent_release(statsEvent);
+}
+
std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
uint64_t timestampNs, const android::view::DisplayStateEnum state) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -272,24 +288,12 @@
AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
- vector<const char*> cTags(attributionTags.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = attributionTags[i].c_str();
- }
-
- AStatsEvent_writeAttributionChain(statsEvent,
- reinterpret_cast<const uint32_t*>(attributionUids.data()),
- cTags.data(), attributionUids.size());
+ writeAttribution(statsEvent, attributionUids, attributionTags);
AStatsEvent_writeString(statsEvent, jobName.c_str());
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -319,24 +323,12 @@
AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
- vector<const char*> cTags(attributionTags.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = attributionTags[i].c_str();
- }
-
- AStatsEvent_writeAttributionChain(statsEvent,
- reinterpret_cast<const uint32_t*>(attributionUids.data()),
- cTags.data(), attributionUids.size());
+ writeAttribution(statsEvent, attributionUids, attributionTags);
AStatsEvent_writeString(statsEvent, name.c_str());
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
diff --git a/cmds/statsd/benchmark/metric_util.h b/cmds/statsd/benchmark/metric_util.h
index c5fcf7c..3efaa85 100644
--- a/cmds/statsd/benchmark/metric_util.h
+++ b/cmds/statsd/benchmark/metric_util.h
@@ -18,6 +18,7 @@
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "src/StatsLogProcessor.h"
#include "src/logd/LogEvent.h"
+#include "stats_event.h"
#include "statslog.h"
namespace android {
@@ -92,6 +93,11 @@
FieldMatcher CreateAttributionUidDimensions(const int atomId,
const std::vector<Position>& positions);
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+ const vector<string>& attributionTags);
+
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent);
+
// Create log event for screen state changed.
std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
uint64_t timestampNs, const android::view::DisplayStateEnum state);
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index 4385964..cfc1de4 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -120,9 +120,9 @@
}
int32_t getUidIfExists(const FieldValue& value) {
- // the field is uid field if the field is the uid field in attribution node or marked as
- // is_uid in atoms.proto
- bool isUid = isAttributionUidField(value) || isUidField(value.mField, value.mValue);
+ // the field is uid field if the field is the uid field in attribution node
+ // or annotated as such in the atom
+ bool isUid = isAttributionUidField(value) || isUidField(value);
return isUid ? value.mValue.int_value : -1;
}
@@ -134,16 +134,8 @@
return false;
}
-bool isUidField(const Field& field, const Value& value) {
- auto it = android::util::AtomsInfo::kAtomsWithUidField.find(field.getTag());
-
- if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
- int uidField = it->second; // uidField is the field number in proto
- return field.getDepth() == 0 && field.getPosAtDepth(0) == uidField &&
- value.getType() == INT;
- }
-
- return false;
+bool isUidField(const FieldValue& fieldValue) {
+ return fieldValue.mAnnotations.isUidField();
}
Value::Value(const Value& from) {
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 3536e5a..92e09ea 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -367,7 +367,8 @@
enum {
NESTED_POS = 0x0,
PRIMARY_POS = 0x1,
- EXCLUSIVE_POS = 0x2
+ EXCLUSIVE_POS = 0x2,
+ UID_POS = 0x3
};
inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); }
@@ -376,6 +377,8 @@
inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); }
+ inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); }
+
inline void setResetState(int resetState) { mResetState = resetState; }
// Default value = false
@@ -387,6 +390,9 @@
// Default value = false
inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); }
+ // Default value = false
+ inline bool isUidField() const { return getValueFromBitmask(UID_POS); }
+
// If a reset state is not sent in the StatsEvent, returns -1. Note that a
// reset satate is only sent if and only if a reset should be triggered.
inline int getResetState() const { return mResetState; }
@@ -402,7 +408,7 @@
}
// This is a bitmask over all annotations stored in boolean form. Because
- // there are only 3 booleans, just one byte is required.
+ // there are only 4 booleans, just one byte is required.
uint8_t mBooleanBitmask = 0;
int mResetState = -1;
@@ -449,7 +455,7 @@
void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output);
bool isAttributionUidField(const Field& field, const Value& value);
-bool isUidField(const Field& field, const Value& value);
+bool isUidField(const FieldValue& fieldValue);
bool equalDimensions(const std::vector<Matcher>& dimension_a,
const std::vector<Matcher>& dimension_b);
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 982a63e..325cbc7 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -138,13 +138,6 @@
}
}
-void updateUid(Value* value, int hostUid) {
- int uid = value->int_value;
- if (uid != hostUid) {
- value->setInt(hostUid);
- }
-}
-
void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const {
if (android::util::AtomsInfo::kAtomsWithAttributionChain.find(event->GetTagId()) !=
android::util::AtomsInfo::kAtomsWithAttributionChain.end()) {
@@ -154,22 +147,15 @@
}
if (isAttributionUidField(value)) {
const int hostUid = mUidMap->getHostUidOrSelf(value.mValue.int_value);
- updateUid(&value.mValue, hostUid);
+ value.mValue.setInt(hostUid);
}
}
} else {
- auto it = android::util::AtomsInfo::kAtomsWithUidField.find(event->GetTagId());
- if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
- int uidField = it->second; // uidField is the field number in proto,
- // starting from 1
- if (uidField > 0 && (int)event->getValues().size() >= uidField &&
- (event->getValues())[uidField - 1].mValue.getType() == INT) {
- Value& value = (*event->getMutableValues())[uidField - 1].mValue;
- const int hostUid = mUidMap->getHostUidOrSelf(value.int_value);
- updateUid(&value, hostUid);
- } else {
- ALOGE("Malformed log, uid not found. %s", event->ToString().c_str());
- }
+ int uidFieldIndex = event->getUidFieldIndex();
+ if (uidFieldIndex != -1) {
+ Value& value = (*event->getMutableValues())[uidFieldIndex].mValue;
+ const int hostUid = mUidMap->getHostUidOrSelf(value.int_value);
+ value.setInt(hostUid);
}
}
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index d1995bc..2a1716e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -419,6 +419,7 @@
SharesheetStarted sharesheet_started = 259 [(module) = "framework"];
RankingSelected ranking_selected = 260 [(module) = "framework"];
TvSettingsUIInteracted tvsettings_ui_interacted = 261 [(module) = "tv_settings"];
+ LauncherStaticLayout launcher_snapshot = 262 [(module) = "sysui"];
SdkExtensionStatus sdk_extension_status = 354;
}
@@ -2979,14 +2980,98 @@
optional int32 duration_millis = 7;
}
+/**
+ * Logs when Launcher (HomeScreen) UI has changed or was interacted.
+ *
+ * Logged from:
+ * packages/apps/Launcher3
+ */
message LauncherUIChanged {
- optional android.stats.launcher.LauncherAction action = 1;
+ optional android.stats.launcher.LauncherAction action = 1 [deprecated = true];
optional android.stats.launcher.LauncherState src_state = 2;
optional android.stats.launcher.LauncherState dst_state = 3;
- optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES];
- optional bool is_swipe_up_enabled = 5;
+ optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES, deprecated = true];
+ optional bool is_swipe_up_enabled = 5 [deprecated = true];
+
+ // The event id (e.g., app launch, drag and drop, long press)
+ optional int32 event_id = 6;
+ // The event's source or target id (e.g., icon, task, button)
+ optional int32 target_id = 7;
+ // If the target needs to be tracked, use this id field
+ optional int32 instance_id = 8;
+ optional int32 uid = 9 [(is_uid) = true];
+ optional string package_name = 10;
+ optional string component_name = 11;
+
+ // (x, y) coordinate and the index information of the target on the container
+ optional int32 grid_x = 12;
+ optional int32 grid_y = 13;
+ optional int32 page_id = 14;
+
+ // e.g., folder icon's (x, y) location and index information on the workspace
+ optional int32 grid_x_parent = 15;
+ optional int32 grid_y_parent = 16;
+ optional int32 page_id_parent = 17;
+
+ // e.g., SEARCHBOX_ALLAPPS, FOLDER_WORKSPACE
+ optional int32 hierarchy = 18;
+
+ optional bool is_work_profile = 19;
+
+ // Used to store the predicted rank of the target
+ optional int32 rank = 20;
+
+ // e.g., folderLabelState can be captured in the following two fields
+ optional int32 from_state = 21;
+ optional int32 to_state = 22;
+
+ // e.g., autofilled or suggested texts that are not user entered
+ optional string edittext = 23;
}
+/**
+ * Used for snapshot of the HomeScreen UI elements
+ *
+ * Logged from:
+ * packages/apps/Launcher3
+ */
+message LauncherStaticLayout {
+ // The event id (e.g., snapshot, drag and drop)
+ optional int32 event_id = 1;
+ // The event's source or target id (e.g., icon, shortcut, widget)
+ optional int32 target_id = 2;
+ // If the target needs to be tracked, use this id field
+ optional int32 instance_id = 3;
+ optional int32 uid = 4 [(is_uid) = true];
+ optional string package_name = 5;
+ optional string component_name = 6;
+
+ // (x, y) coordinate and the index information of the target on the container
+ optional int32 grid_x = 7;
+ optional int32 grid_y = 8;
+ optional int32 page_id = 9;
+
+ // e.g., folder icon's (x, y) location and index information on the workspace
+ optional int32 grid_x_parent = 10;
+ optional int32 grid_y_parent = 11;
+ optional int32 page_id_parent = 12;
+
+ // e.g., WORKSPACE, HOTSEAT, FOLDER_WORKSPACE, FOLDER_HOTSEAT
+ optional int32 hierarchy = 13;
+
+ optional bool is_work_profile = 14;
+
+ // e.g., PIN, WIDGET TRAY, APPS TRAY, PREDICTION
+ optional int32 origin = 15;
+}
+
+/**
+ * Logs when Wallpaper or ThemePicker UI has changed.
+ *
+ * Logged from:
+ * packages/apps/ThemePicker
+ * packages/apps/WallpaperPicker2
+ */
message StyleUIChanged {
optional android.stats.style.Action action = 1;
optional int32 color_package_hash = 2;
diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp
index aee7256..90247cf 100644
--- a/cmds/statsd/src/external/puller_util.cpp
+++ b/cmds/statsd/src/external/puller_util.cpp
@@ -49,10 +49,14 @@
*/
void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const sp<UidMap>& uidMap,
int tagId, const vector<int>& additiveFieldsVec) {
- if ((android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) ==
- android::util::AtomsInfo::kAtomsWithAttributionChain.end()) &&
- (android::util::AtomsInfo::kAtomsWithUidField.find(tagId) ==
- android::util::AtomsInfo::kAtomsWithUidField.end())) {
+ bool hasAttributionChain = (android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) !=
+ android::util::AtomsInfo::kAtomsWithAttributionChain.end());
+ // To check if any LogEvent has a uid field, we can just check the first
+ // LogEvent because all atoms with this tagId should have the uid
+ // annotation.
+ bool hasUidField = (data[0]->getUidFieldIndex() != -1);
+
+ if (!hasAttributionChain && !hasUidField) {
VLOG("No uid or attribution chain to merge, atom %d", tagId);
return;
}
@@ -75,19 +79,13 @@
}
}
} else {
- auto it = android::util::AtomsInfo::kAtomsWithUidField.find(tagId);
- if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
- int uidField = it->second; // uidField is the field number in proto,
- // starting from 1
- if (uidField > 0 && (int)event->getValues().size() >= uidField &&
- (event->getValues())[uidField - 1].mValue.getType() == INT) {
- Value& value = (*event->getMutableValues())[uidField - 1].mValue;
- const int hostUid = uidMap->getHostUidOrSelf(value.int_value);
- value.setInt(hostUid);
- } else {
- ALOGE("Malformed log, uid not found. %s", event->ToString().c_str());
- return;
- }
+ int uidFieldIndex = event->getUidFieldIndex();
+ if (uidFieldIndex != -1) {
+ Value& value = (*event->getMutableValues())[uidFieldIndex].mValue;
+ const int hostUid = uidMap->getHostUidOrSelf(value.int_value);
+ value.setInt(hostUid);
+ } else {
+ ALOGE("Malformed log, uid not found. %s", event->ToString().c_str());
}
}
}
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 96bf04f..8b6a864 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -240,14 +240,20 @@
last[1] = last[2] = false;
}
+// Assumes that mValues is not empty
+bool LogEvent::checkPreviousValueType(Type expected) {
+ return mValues[mValues.size() - 1].mValue.getType() == expected;
+}
+
void LogEvent::parseIsUidAnnotation(uint8_t annotationType) {
- if (mValues.empty() || annotationType != BOOL_TYPE) {
+ if (mValues.empty() || !checkPreviousValueType(INT) || annotationType != BOOL_TYPE) {
mValid = false;
return;
}
bool isUid = readNextValue<uint8_t>();
if (isUid) mUidFieldIndex = mValues.size() - 1;
+ mValues[mValues.size() - 1].mAnnotations.setUidField(isUid);
}
void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 9e21c77..4eeb7d6 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -213,6 +213,7 @@
void parseExclusiveStateAnnotation(uint8_t annotationType);
void parseTriggerStateResetAnnotation(uint8_t annotationType);
void parseStateNestedAnnotation(uint8_t annotationType);
+ bool checkPreviousValueType(Type expected);
/**
* The below three variables are only valid during the execution of
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 1f8bbd7..2b4c6a3 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -81,18 +81,17 @@
return matched;
}
-bool tryMatchString(const UidMap& uidMap, const Field& field, const Value& value,
- const string& str_match) {
- if (isAttributionUidField(field, value) || isUidField(field, value)) {
- int uid = value.int_value;
+bool tryMatchString(const UidMap& uidMap, const FieldValue& fieldValue, const string& str_match) {
+ if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) {
+ int uid = fieldValue.mValue.int_value;
auto aidIt = UidMap::sAidToUidMapping.find(str_match);
if (aidIt != UidMap::sAidToUidMapping.end()) {
return ((int)aidIt->second) == uid;
}
std::set<string> packageNames = uidMap.getAppNamesFromUid(uid, true /* normalize*/);
return packageNames.find(str_match) != packageNames.end();
- } else if (value.getType() == STRING) {
- return value.str_value == str_match;
+ } else if (fieldValue.mValue.getType() == STRING) {
+ return fieldValue.mValue.str_value == str_match;
}
return false;
}
@@ -228,8 +227,7 @@
}
case FieldValueMatcher::ValueMatcherCase::kEqString: {
for (int i = start; i < end; i++) {
- if (tryMatchString(uidMap, values[i].mField, values[i].mValue,
- matcher.eq_string())) {
+ if (tryMatchString(uidMap, values[i], matcher.eq_string())) {
return true;
}
}
@@ -240,7 +238,7 @@
for (int i = start; i < end; i++) {
bool notEqAll = true;
for (const auto& str : str_list.str_value()) {
- if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) {
+ if (tryMatchString(uidMap, values[i], str)) {
notEqAll = false;
break;
}
@@ -255,7 +253,7 @@
const auto& str_list = matcher.eq_any_string();
for (int i = start; i < end; i++) {
for (const auto& str : str_list.str_value()) {
- if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) {
+ if (tryMatchString(uidMap, values[i], str)) {
return true;
}
}
diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp
index 0bf24f1..f5ba8fd 100644
--- a/cmds/statsd/tests/FieldValue_test.cpp
+++ b/cmds/statsd/tests/FieldValue_test.cpp
@@ -41,22 +41,10 @@
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
- vector<const char*> cTags(attributionTags.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = attributionTags[i].c_str();
- }
-
- AStatsEvent_writeAttributionChain(statsEvent,
- reinterpret_cast<const uint32_t*>(attributionUids.data()),
- cTags.data(), attributionUids.size());
+ writeAttribution(statsEvent, attributionUids, attributionTags);
AStatsEvent_writeString(statsEvent, name.c_str());
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
-
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -66,22 +54,10 @@
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
- vector<const char*> cTags(attributionTags.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = attributionTags[i].c_str();
- }
-
- AStatsEvent_writeAttributionChain(statsEvent,
- reinterpret_cast<const uint32_t*>(attributionUids.data()),
- cTags.data(), attributionUids.size());
+ writeAttribution(statsEvent, attributionUids, attributionTags);
AStatsEvent_writeInt32(statsEvent, value);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
-
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
} // anonymous namespace
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 6f4c8e4..26423d4 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -18,6 +18,7 @@
#include <log/logprint.h>
#include <stdio.h>
+#include "annotations.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "matchers/matcher_util.h"
#include "stats_event.h"
@@ -48,15 +49,9 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
-
AStatsEvent_writeInt32(statsEvent, value);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
-
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
void makeFloatLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -64,15 +59,9 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
-
AStatsEvent_writeFloat(statsEvent, floatValue);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
-
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
void makeStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -80,32 +69,20 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
-
AStatsEvent_writeString(statsEvent, name.c_str());
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
-
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
-void makeIntStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
- const int32_t value, const string& name) {
+void makeIntWithBoolAnnotationLogEvent(LogEvent* logEvent, const int32_t atomId,
+ const int32_t field, const uint8_t annotationId,
+ const bool annotationValue) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, atomId);
- AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
+ AStatsEvent_writeInt32(statsEvent, field);
+ AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
- AStatsEvent_writeInt32(statsEvent, value);
- AStatsEvent_writeString(statsEvent, name.c_str());
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
-
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
void makeAttributionLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -115,22 +92,10 @@
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
- vector<const char*> cTags(attributionTags.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = attributionTags[i].c_str();
- }
-
- AStatsEvent_writeAttributionChain(statsEvent,
- reinterpret_cast<const uint32_t*>(attributionUids.data()),
- cTags.data(), attributionUids.size());
+ writeAttribution(statsEvent, attributionUids, attributionTags);
AStatsEvent_writeString(statsEvent, name.c_str());
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
-
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
void makeBoolLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -141,13 +106,8 @@
AStatsEvent_writeBool(statsEvent, bool1);
AStatsEvent_writeBool(statsEvent, bool2);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
-
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
} // anonymous namespace
@@ -416,21 +376,20 @@
simpleMatcher->add_field_value_matcher()->set_field(1);
simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("pkg0");
- // Set up the event
+ // Make event without is_uid annotation.
LogEvent event1(/*uid=*/0, /*pid=*/0);
makeIntLogEvent(&event1, TAG_ID, 0, 1111);
-
- LogEvent event2(/*uid=*/0, /*pid=*/0);
- makeIntStringLogEvent(&event2, TAG_ID_2, 0, 1111, "some value");
-
- // Tag not in kAtomsWithUidField
EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
- // Tag found in kAtomsWithUidField and has matching uid
+ // Make event with is_uid annotation.
+ LogEvent event2(/*uid=*/0, /*pid=*/0);
+ makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ANNOTATION_ID_IS_UID, true);
+
+ // Event has is_uid annotation, so mapping from uid to package name occurs.
simpleMatcher->set_atom_id(TAG_ID_2);
EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
- // Tag found in kAtomsWithUidField but has non-matching uid
+ // Event has is_uid annotation, but uid maps to different package name.
simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("Pkg2");
EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
}
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 7febb35..ba5b032 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -67,22 +67,12 @@
AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
vector<std::string> tags(uids.size()); // vector of empty strings
- vector<const char*> cTags(uids.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = tags[i].c_str();
- }
- AStatsEvent_writeAttributionChain(statsEvent, reinterpret_cast<const uint32_t*>(uids.data()),
- cTags.data(), uids.size());
+ writeAttribution(statsEvent, uids, tags);
AStatsEvent_writeString(statsEvent, wl.c_str());
AStatsEvent_writeInt32(statsEvent, acquire);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
-
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
} // anonymous namespace
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
index 81e1c05..60403f2 100644
--- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
@@ -84,14 +84,9 @@
AStatsEvent_writeString(statsEvent, calling_pkg_name.c_str());
AStatsEvent_writeInt32(statsEvent, is_instant_app);
AStatsEvent_writeInt32(statsEvent, activity_start_msec);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
diff --git a/cmds/statsd/tests/external/StatsPuller_test.cpp b/cmds/statsd/tests/external/StatsPuller_test.cpp
index e8200d5..5043358 100644
--- a/cmds/statsd/tests/external/StatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsPuller_test.cpp
@@ -64,16 +64,10 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, pullTagId);
AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-
AStatsEvent_writeInt64(statsEvent, value);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp
index 15425d8..c2cfb37 100644
--- a/cmds/statsd/tests/external/puller_util_test.cpp
+++ b/cmds/statsd/tests/external/puller_util_test.cpp
@@ -21,8 +21,10 @@
#include <vector>
#include "../metrics/metrics_test_helper.h"
+#include "annotations.h"
#include "stats_event.h"
#include "statslog_statsdtest.h"
+#include "tests/statsd_test_util.h"
#ifdef __ANDROID__
@@ -52,15 +54,15 @@
void extractIntoVector(vector<shared_ptr<LogEvent>> events,
vector<vector<int>>& ret) {
- ret.clear();
- status_t err;
- for (const auto& event : events) {
- vector<int> vec;
- vec.push_back(event->GetInt(1, &err));
- vec.push_back(event->GetInt(2, &err));
- vec.push_back(event->GetInt(3, &err));
- ret.push_back(vec);
- }
+ ret.clear();
+ status_t err;
+ for (const auto& event : events) {
+ vector<int> vec;
+ vec.push_back(event->GetInt(1, &err));
+ vec.push_back(event->GetInt(2, &err));
+ vec.push_back(event->GetInt(3, &err));
+ ret.push_back(vec);
+ }
}
std::shared_ptr<LogEvent> makeUidLogEvent(uint64_t timestampNs, int uid, int data1, int data2) {
@@ -69,16 +71,12 @@
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
AStatsEvent_writeInt32(statsEvent, uid);
+ AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
AStatsEvent_writeInt32(statsEvent, data1);
AStatsEvent_writeInt32(statsEvent, data2);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::shared_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -86,16 +84,10 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, nonUidAtomTagId);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
AStatsEvent_writeInt32(statsEvent, data1);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::shared_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
diff --git a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
index 6dc041f..a15f95b 100644
--- a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
+++ b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
@@ -21,6 +21,7 @@
#include <thread>
#include "stats_event.h"
+#include "tests/statsd_test_util.h"
namespace android {
namespace os {
@@ -37,14 +38,9 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, 10);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index d55996c..65f8de6 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -46,26 +46,17 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId, string uid) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
AStatsEvent_writeString(statsEvent, uid.c_str());
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
} // namespace
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index 6143dc0..30f8159 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -26,6 +26,7 @@
#include "src/condition/ConditionWizard.h"
#include "src/stats_log_util.h"
#include "stats_event.h"
+#include "tests/statsd_test_util.h"
using namespace android::os::statsd;
using namespace testing;
@@ -48,12 +49,8 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
} // namespace
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index e58bbb7..97647a7 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -43,14 +43,9 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
AStatsEvent_writeString(statsEvent, str.c_str());
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
} // anonymous namespace
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 2fe05a4..42d0d5d 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -64,14 +64,9 @@
AStatsEvent_writeInt32(statsEvent, value1);
AStatsEvent_writeString(statsEvent, str1.c_str());
AStatsEvent_writeInt32(statsEvent, value2);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
-
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
} // anonymous namespace
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index b623a09..009e49a 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -611,7 +611,7 @@
vector<shared_ptr<LogEvent>> allData;
allData.clear();
- allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 110));
+ allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110));
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8});
@@ -656,7 +656,7 @@
valueProducer.prepareFirstBucket();
LogEvent event1(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+ CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
@@ -665,14 +665,14 @@
EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
LogEvent event2(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 1, 10);
+ CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 10);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
// Next value should create a new bucket.
LogEvent event3(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 1, 10);
+ CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 10);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs);
@@ -812,10 +812,10 @@
valueProducer.prepareFirstBucket();
LogEvent event1(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+ CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
LogEvent event2(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+ CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
// has one slice
@@ -856,7 +856,7 @@
valueProducer.mCondition = ConditionState::kFalse;
LogEvent event1(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+ CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
// has 1 slice
EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size());
@@ -864,7 +864,7 @@
valueProducer.onConditionChangedLocked(true, bucketStartTimeNs + 15);
LogEvent event2(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+ CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
// has one slice
@@ -875,7 +875,7 @@
EXPECT_EQ(20, curInterval.value.long_value);
LogEvent event3(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 1, 30);
+ CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 30);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
// has one slice
@@ -886,7 +886,7 @@
valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35);
LogEvent event4(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 1, 40);
+ CreateRepeatedValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 40);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
// has one slice
@@ -1195,10 +1195,10 @@
valueProducer.prepareFirstBucket();
LogEvent event1(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+ CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
LogEvent event2(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+ CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
// has one slice
@@ -1238,10 +1238,10 @@
valueProducer.prepareFirstBucket();
LogEvent event1(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+ CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
LogEvent event2(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+ CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
// has one slice
@@ -1283,10 +1283,10 @@
valueProducer.prepareFirstBucket();
LogEvent event1(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+ CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
LogEvent event2(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 15);
+ CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
// has one slice
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
@@ -1331,10 +1331,10 @@
valueProducer.prepareFirstBucket();
LogEvent event1(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+ CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
LogEvent event2(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 15);
+ CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
// has one slice
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
@@ -1374,10 +1374,10 @@
valueProducer.prepareFirstBucket();
LogEvent event1(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+ CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
LogEvent event2(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 1, 15);
+ CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
// has one slice
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
@@ -1398,7 +1398,7 @@
// no change in data.
LogEvent event3(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 1, 15);
+ CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
@@ -1408,7 +1408,7 @@
EXPECT_EQ(true, curInterval.hasValue);
LogEvent event4(/*uid=*/0, /*pid=*/0);
- CreateTwoValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 1, 15);
+ CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
@@ -2166,7 +2166,7 @@
// Bucket start.
vector<shared_ptr<LogEvent>> allData;
allData.clear();
- allData.push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 1, 1, 110));
+ allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110));
valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs);
valueProducer->onConditionChanged(false, bucketStartTimeNs + 2);
@@ -2174,7 +2174,7 @@
// Bucket end.
allData.clear();
- allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 140));
+ allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140));
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1);
diff --git a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
index ac3ad69..7b952d7 100644
--- a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
+++ b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
@@ -171,13 +171,9 @@
AStatsEvent_overwriteTimestamp(statsEvent, 1111L);
AStatsEvent_writeInt32(statsEvent, uid);
AStatsEvent_writeInt64(statsEvent, timeMillis);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index a5b8e1c..78c80bc 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -62,7 +62,7 @@
// START: build event functions.
// Incorrect event - missing fields
-std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName,
+std::unique_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName,
int state) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED);
@@ -72,14 +72,9 @@
AStatsEvent_writeString(statsEvent, packageName.c_str());
// Missing field 3 - using_alert_window.
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -93,14 +88,9 @@
AStatsEvent_writeString(statsEvent, packageName.c_str());
AStatsEvent_writeInt32(statsEvent, true); // using_alert_window
AStatsEvent_writeString(statsEvent, "string"); // exclusive state: string instead of int
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
// END: build event functions.
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 7d765d3..ed3cf5b 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -507,23 +507,26 @@
}
// END: get primary key functions
-shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
- int32_t value2) {
- AStatsEvent* statsEvent = AStatsEvent_obtain();
- AStatsEvent_setAtomId(statsEvent, atomId);
- AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+ const vector<string>& attributionTags) {
+ vector<const char*> cTags(attributionTags.size());
+ for (int i = 0; i < cTags.size(); i++) {
+ cTags[i] = attributionTags[i].c_str();
+ }
- AStatsEvent_writeInt32(statsEvent, value1);
- AStatsEvent_writeInt32(statsEvent, value2);
+ AStatsEvent_writeAttributionChain(statsEvent,
+ reinterpret_cast<const uint32_t*>(attributionUids.data()),
+ cTags.data(), attributionUids.size());
+}
+
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) {
AStatsEvent_build(statsEvent);
size_t size;
uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
- return logEvent;
+ AStatsEvent_release(statsEvent);
}
void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1,
@@ -534,31 +537,14 @@
AStatsEvent_writeInt32(statsEvent, value1);
AStatsEvent_writeInt32(statsEvent, value2);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
-shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
- int32_t value2, int32_t value3) {
- AStatsEvent* statsEvent = AStatsEvent_obtain();
- AStatsEvent_setAtomId(statsEvent, atomId);
- AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-
- AStatsEvent_writeInt32(statsEvent, value1);
- AStatsEvent_writeInt32(statsEvent, value2);
- AStatsEvent_writeInt32(statsEvent, value3);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
+ int32_t value2) {
shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
-
+ CreateTwoValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2);
return logEvent;
}
@@ -571,29 +557,14 @@
AStatsEvent_writeInt32(statsEvent, value1);
AStatsEvent_writeInt32(statsEvent, value2);
AStatsEvent_writeInt32(statsEvent, value3);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
-shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) {
- AStatsEvent* statsEvent = AStatsEvent_obtain();
- AStatsEvent_setAtomId(statsEvent, atomId);
- AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-
- AStatsEvent_writeInt32(statsEvent, value);
- AStatsEvent_writeInt32(statsEvent, value);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
+ int32_t value2, int32_t value3) {
shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
-
+ CreateThreeValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2, value3);
return logEvent;
}
@@ -605,26 +576,13 @@
AStatsEvent_writeInt32(statsEvent, value);
AStatsEvent_writeInt32(statsEvent, value);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
}
-shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) {
- AStatsEvent* statsEvent = AStatsEvent_obtain();
- AStatsEvent_setAtomId(statsEvent, atomId);
- AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) {
shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
-
+ CreateRepeatedValueLogEvent(logEvent.get(), atomId, eventTimeNs, value);
return logEvent;
}
@@ -632,12 +590,14 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
- AStatsEvent_build(statsEvent);
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) {
+ shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+ CreateNoValuesLogEvent(logEvent.get(), atomId, eventTimeNs);
+ return logEvent;
}
std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
@@ -645,16 +605,10 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -662,16 +616,10 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::ON);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -679,16 +627,10 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::OFF);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -696,16 +638,10 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -713,16 +649,10 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
AStatsEvent_writeInt32(statsEvent, level);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -733,24 +663,12 @@
AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
- vector<const char*> cTags(attributionTags.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = attributionTags[i].c_str();
- }
-
- AStatsEvent_writeAttributionChain(statsEvent,
- reinterpret_cast<const uint32_t*>(attributionUids.data()),
- cTags.data(), attributionUids.size());
+ writeAttribution(statsEvent, attributionUids, attributionTags);
AStatsEvent_writeString(statsEvent, jobName.c_str());
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -780,25 +698,13 @@
AStatsEvent_setAtomId(statsEvent, util::WAKELOCK_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
- vector<const char*> cTags(attributionTags.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = attributionTags[i].c_str();
- }
-
- AStatsEvent_writeAttributionChain(statsEvent,
- reinterpret_cast<const uint32_t*>(attributionUids.data()),
- cTags.data(), attributionUids.size());
+ writeAttribution(statsEvent, attributionUids, attributionTags);
AStatsEvent_writeInt32(statsEvent, android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK);
AStatsEvent_writeString(statsEvent, wakelockName.c_str());
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -828,14 +734,9 @@
AStatsEvent_writeString(statsEvent, "pkg_name");
AStatsEvent_writeString(statsEvent, "class_name");
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -858,24 +759,12 @@
AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
- vector<const char*> cTags(attributionTags.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = attributionTags[i].c_str();
- }
-
- AStatsEvent_writeAttributionChain(statsEvent,
- reinterpret_cast<const uint32_t*>(attributionUids.data()),
- cTags.data(), attributionUids.size());
+ writeAttribution(statsEvent, attributionUids, attributionTags);
AStatsEvent_writeString(statsEvent, name.c_str());
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -904,14 +793,9 @@
AStatsEvent_writeInt32(statsEvent, uid);
AStatsEvent_writeString(statsEvent, "");
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -928,14 +812,9 @@
AStatsEvent_writeInt32(statsEvent, uid);
AStatsEvent_writeString(statsEvent, "eventType");
AStatsEvent_writeString(statsEvent, "processName");
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -948,14 +827,9 @@
AStatsEvent_writeInt32(statsEvent, hostUid);
AStatsEvent_writeInt32(statsEvent, isolatedUid);
AStatsEvent_writeInt32(statsEvent, is_create);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -967,14 +841,9 @@
AStatsEvent_writeInt32(statsEvent, uid);
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -988,26 +857,14 @@
AStatsEvent_setAtomId(statsEvent, util::BLE_SCAN_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
- vector<const char*> cTags(attributionTags.size());
- for (int i = 0; i < cTags.size(); i++) {
- cTags[i] = attributionTags[i].c_str();
- }
-
- AStatsEvent_writeAttributionChain(statsEvent,
- reinterpret_cast<const uint32_t*>(attributionUids.data()),
- cTags.data(), attributionUids.size());
+ writeAttribution(statsEvent, attributionUids, attributionTags);
AStatsEvent_writeInt32(statsEvent, state);
AStatsEvent_writeInt32(statsEvent, filtered); // filtered
AStatsEvent_writeInt32(statsEvent, firstMatch); // first match
AStatsEvent_writeInt32(statsEvent, opportunistic); // opportunistic
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
@@ -1023,14 +880,9 @@
AStatsEvent_writeString(statsEvent, packageName.c_str());
AStatsEvent_writeInt32(statsEvent, usingAlertWindow);
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_build(statsEvent);
-
- size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
- logEvent->parseBuffer(buf, size);
- AStatsEvent_release(statsEvent);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
return logEvent;
}
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index f24705a..d6ea77eb 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -25,6 +25,7 @@
#include "src/hash.h"
#include "src/logd/LogEvent.h"
#include "src/stats_log_util.h"
+#include "stats_event.h"
#include "statslog_statsdtest.h"
namespace android {
@@ -189,6 +190,12 @@
void getPartialWakelockKey(int uid, HashableDimensionKey* key);
// END: get primary key functions
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+ const vector<string>& attributionTags);
+
+// Builds statsEvent to get buffer that is parsed into logEvent then releases statsEvent.
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent);
+
shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
int32_t value2);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 46b06fb..3a708a6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1395,6 +1395,7 @@
public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
/** @hide Access all external storage */
@SystemApi
+ @TestApi
public static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
"android:manage_external_storage";
diff --git a/core/java/android/app/IWindowToken.aidl b/core/java/android/app/IWindowToken.aidl
index 8ea881f..3627b0f 100644
--- a/core/java/android/app/IWindowToken.aidl
+++ b/core/java/android/app/IWindowToken.aidl
@@ -30,4 +30,6 @@
*/
oneway interface IWindowToken {
void onConfigurationChanged(in Configuration newConfig, int newDisplayId);
+
+ void onWindowTokenRemoved();
}
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java
index 878993e..3a06c9d 100644
--- a/core/java/android/app/WindowContext.java
+++ b/core/java/android/app/WindowContext.java
@@ -28,6 +28,8 @@
import android.view.WindowManagerGlobal;
import android.view.WindowManagerImpl;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.ref.Reference;
/**
@@ -75,8 +77,6 @@
// config back to the client.
result = mWms.addWindowTokenWithOptions(
mToken, type, getDisplayId(), options, getPackageName());
-
- // TODO(window-context): remove token with a DeathObserver
} catch (RemoteException e) {
mOwnsToken = false;
throw e.rethrowFromSystemServer();
@@ -100,6 +100,13 @@
@Override
protected void finalize() throws Throwable {
+ release();
+ super.finalize();
+ }
+
+ /** Used for test to invoke because we can't invoke finalize directly. */
+ @VisibleForTesting
+ public void release() {
if (mOwnsToken) {
try {
mWms.removeWindowToken(mToken, getDisplayId());
@@ -108,6 +115,12 @@
throw e.rethrowFromSystemServer();
}
}
- super.finalize();
+ destroy();
+ }
+
+ void destroy() {
+ final ContextImpl impl = (ContextImpl) getBaseContext();
+ impl.scheduleFinalCleanup(getClass().getName(), "WindowContext");
+ Reference.reachabilityFence(this);
}
}
diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/app/WindowTokenClient.java
index ed0179b..301960e 100644
--- a/core/java/android/app/WindowTokenClient.java
+++ b/core/java/android/app/WindowTokenClient.java
@@ -20,6 +20,9 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
+import android.view.WindowManagerGlobal;
+
+import java.lang.ref.WeakReference;
/**
* Client implementation of {@link IWindowToken}. It can receive configuration change callbacks from
@@ -31,9 +34,9 @@
public class WindowTokenClient extends IWindowToken.Stub {
/**
* Attached {@link Context} for this window token to update configuration and resources.
- * Initialized by {@link #attachContext(Context)}.
+ * Initialized by {@link #attachContext(WindowContext)}.
*/
- private Context mContext = null;
+ private WeakReference<WindowContext> mContextRef = null;
private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
@@ -47,30 +50,46 @@
* @param context context to be attached
* @throws IllegalStateException if attached context has already existed.
*/
- void attachContext(@NonNull Context context) {
- if (mContext != null) {
+ void attachContext(@NonNull WindowContext context) {
+ if (mContextRef != null) {
throw new IllegalStateException("Context is already attached.");
}
- mContext = context;
- ContextImpl impl = ContextImpl.getImpl(mContext);
+ mContextRef = new WeakReference<>(context);
+ final ContextImpl impl = ContextImpl.getImpl(context);
impl.setResources(impl.createWindowContextResources());
}
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
- final int currentDisplayId = mContext.getDisplayId();
+ final Context context = mContextRef.get();
+ if (context == null) {
+ return;
+ }
+ final int currentDisplayId = context.getDisplayId();
final boolean displayChanged = newDisplayId != currentDisplayId;
- final Configuration config = new Configuration(mContext.getResources()
+ final Configuration config = new Configuration(context.getResources()
.getConfiguration());
final boolean configChanged = config.isOtherSeqNewer(newConfig)
&& config.updateFrom(newConfig) != 0;
if (displayChanged || configChanged) {
// TODO(ag/9789103): update resource manager logic to track non-activity tokens
- mResourcesManager.updateResourcesForActivity(asBinder(), config, newDisplayId,
+ mResourcesManager.updateResourcesForActivity(this, config, newDisplayId,
displayChanged);
}
if (displayChanged) {
- mContext.updateDisplay(newDisplayId);
+ context.updateDisplay(newDisplayId);
}
}
+
+ @Override
+ public void onWindowTokenRemoved() {
+ final WindowContext context = mContextRef.get();
+ if (context != null) {
+ context.destroy();
+ mContextRef.clear();
+ }
+ // If a secondary display is detached, release all views attached to this token.
+ WindowManagerGlobal.getInstance().closeAll(this, mContextRef.getClass().getName(),
+ "WindowContext");
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyKeyguardService.java b/core/java/android/app/admin/DevicePolicyKeyguardService.java
index db833ec..473725f 100644
--- a/core/java/android/app/admin/DevicePolicyKeyguardService.java
+++ b/core/java/android/app/admin/DevicePolicyKeyguardService.java
@@ -16,12 +16,15 @@
package android.app.admin;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.view.SurfaceControlViewHost;
@@ -41,27 +44,34 @@
@SystemApi
public class DevicePolicyKeyguardService extends Service {
private static final String TAG = "DevicePolicyKeyguardService";
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
private IKeyguardCallback mCallback;
private final IKeyguardClient mClient = new IKeyguardClient.Stub() {
+ @MainThread
@Override
public void onCreateKeyguardSurface(@Nullable IBinder hostInputToken,
- IKeyguardCallback callback) {
+ @NonNull IKeyguardCallback callback) {
mCallback = callback;
- SurfaceControlViewHost.SurfacePackage surfacePackage =
- DevicePolicyKeyguardService.this.onCreateKeyguardSurface(hostInputToken);
+ mHandler.post(() -> {
+ SurfaceControlViewHost.SurfacePackage surfacePackage =
+ DevicePolicyKeyguardService.this.onCreateKeyguardSurface(hostInputToken);
- if (mCallback != null) {
try {
mCallback.onRemoteContentReady(surfacePackage);
} catch (RemoteException e) {
Log.e(TAG, "Failed to return created SurfacePackage", e);
}
- }
+ });
}
};
@Override
+ public void onDestroy() {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
+ @Override
@Nullable
public final IBinder onBind(@Nullable Intent intent) {
return mClient.asBinder();
@@ -97,6 +107,10 @@
*/
@Nullable
public void dismiss() {
+ if (mCallback == null) {
+ Log.w(TAG, "KeyguardCallback was unexpectedly null");
+ return;
+ }
try {
mCallback.onDismiss();
} catch (RemoteException e) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index fc48e7f..f216db6 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -979,14 +979,17 @@
8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) {
@Override
protected Integer recompute(Void query) {
- // This function must be called while holding the
- // mServiceLock, and with mService not null. The public
- // getState() method makes this guarantee.
try {
- return mService.getState();
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ return mService.getState();
+ }
} catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ Log.e(TAG, "", e);
+ } finally {
+ mServiceLock.readLock().unlock();
}
+ return BluetoothAdapter.STATE_OFF;
}
};
@@ -1013,24 +1016,7 @@
@RequiresPermission(Manifest.permission.BLUETOOTH)
@AdapterState
public int getState() {
- int state = BluetoothAdapter.STATE_OFF;
-
- try {
- mServiceLock.readLock().lock();
- // The test for mService must either be outside the cache, or
- // the cache must be invalidated when mService changes.
- if (mService != null) {
- state = mBluetoothGetStateCache.query(null);
- }
- } catch (RuntimeException e) {
- if (e.getCause() instanceof RemoteException) {
- Log.e(TAG, "", e.getCause());
- } else {
- throw e;
- }
- } finally {
- mServiceLock.readLock().unlock();
- }
+ int state = mBluetoothGetStateCache.query(null);
// Consider all internal states as OFF
if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index e446f4f..0a4627d 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -687,6 +687,19 @@
public static final int NOTIFY_DELETE = 1 << 4;
/**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+ * by a {@link ContentProvider} to indicate that this notification should
+ * not be subject to any delays when dispatching to apps running in the
+ * background.
+ * <p>
+ * Using this flag may negatively impact system health and performance, and
+ * should be used sparingly.
+ *
+ * @hide
+ */
+ public static final int NOTIFY_NO_DELAY = 1 << 15;
+
+ /**
* No exception, throttled by app standby normally.
* @hide
*/
diff --git a/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java b/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java
index 6f01796..5d4b48d 100644
--- a/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java
+++ b/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java
@@ -22,4 +22,9 @@
public SQLiteCantOpenDatabaseException(String error) {
super(error);
}
+
+ /** @hide */
+ public SQLiteCantOpenDatabaseException(String error, Throwable cause) {
+ super(error, cause);
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index bcb3934..f7c96a3 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -36,6 +36,9 @@
import java.io.File;
import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@@ -215,12 +218,31 @@
}
private void open() {
+ final String file = mConfiguration.path;
final int cookie = mRecentOperations.beginOperation("open", null, null);
try {
- mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
+ mConnectionPtr = nativeOpen(file, mConfiguration.openFlags,
mConfiguration.label,
NoPreloadHolder.DEBUG_SQL_STATEMENTS, NoPreloadHolder.DEBUG_SQL_TIME,
mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
+ } catch (SQLiteCantOpenDatabaseException e) {
+ String message = String.format("Cannot open database '%s'", file);
+
+ final Path path = FileSystems.getDefault().getPath(file);
+ final Path dir = path.getParent();
+
+ if (!Files.isDirectory(dir)) {
+ message += ": Directory " + dir + " doesn't exist";
+ } else if (!Files.exists(path)) {
+ message += ": File " + path + " doesn't exist";
+ } else if (!Files.isReadable(path)) {
+ message += ": File " + path + " is not readable";
+ } else if (Files.isDirectory(path)) {
+ message += ": Path " + path + " is a directory";
+ } else {
+ message += ": Unknown reason";
+ }
+ throw new SQLiteCantOpenDatabaseException(message, e);
} finally {
mRecentOperations.endOperation(cookie);
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 91dae66..aa75f60 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -17,6 +17,7 @@
package android.hardware.camera2;
import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
@@ -100,6 +101,8 @@
*
* @hide
*/
+ @UnsupportedAppUsage(publicAlternatives = "This method is exposed for native "
+ + "{@code ACameraMetadata_fromCameraMetadata} in {@code libcamera2ndk}.")
public long getNativeMetadataPtr() {
if (mNativeInstance == null) {
return 0;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 84ac90b..b0bacb9 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -124,13 +124,20 @@
* @param type Window type to be used with this token.
* @param options A bundle used to pass window-related options.
* @param displayId The ID of the display where this token should be added.
- * @param packageName The name of package to request to add window token.
+ * @param packageName The name of package to request to add window token. Could be {@code null}
+ * if callers holds the MANAGE_APP_TOKENS permission.
* @return {@link WindowManagerGlobal#ADD_OKAY} if the addition was successful, an error code
* otherwise.
*/
int addWindowTokenWithOptions(IBinder token, int type, int displayId, in Bundle options,
String packageName);
void addWindowToken(IBinder token, int type, int displayId);
+ /**
+ * Remove window token on a specific display.
+ *
+ * @param token Token to be removed
+ * @displayId The ID of the display where this token should be removed.
+ */
void removeWindowToken(IBinder token, int displayId);
void prepareAppTransition(int transit, boolean alwaysKeepCurrent);
@@ -735,4 +742,11 @@
* Called to show global actions.
*/
void showGlobalActions();
+
+ /**
+ * Sets layer tracing flags for SurfaceFlingerTrace.
+ *
+ * @param flags see definition in SurfaceTracing.cpp
+ */
+ void setLayerTracingFlags(int flags);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index ab65e3a..1086774 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2888,7 +2888,8 @@
/**
* Acquire a frame rate flexibility token, which allows surface flinger to freely switch display
* frame rates. This is used by CTS tests to put the device in a consistent state. See
- * ISurfaceComposer::acquireFrameRateFlexibilityToken().
+ * ISurfaceComposer::acquireFrameRateFlexibilityToken(). The caller must have the
+ * ACCESS_SURFACE_FLINGER permission, or else the call will fail, returning 0.
* @hide
*/
@TestApi
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index 842ba29..6ad5cb9 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -27,8 +27,6 @@
import android.os.Parcelable;
import android.text.SpannedString;
-import com.android.internal.util.Preconditions;
-
import java.lang.annotation.Retention;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@@ -491,7 +489,11 @@
*/
@NonNull
public Builder setMaxSuggestions(@IntRange(from = -1) int maxSuggestions) {
- mMaxSuggestions = Preconditions.checkArgumentNonnegative(maxSuggestions);
+ if (maxSuggestions < -1) {
+ throw new IllegalArgumentException("maxSuggestions has to be greater than or "
+ + "equal to -1.");
+ }
+ mMaxSuggestions = maxSuggestions;
return this;
}
diff --git a/core/java/android/window/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java
index 1afbfeb..0f26d5d 100644
--- a/core/java/android/window/VirtualDisplayTaskEmbedder.java
+++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java
@@ -16,7 +16,6 @@
package android.window;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
@@ -247,7 +246,6 @@
protected ActivityOptions prepareActivityOptions(ActivityOptions options) {
options = super.prepareActivityOptions(options);
options.setLaunchDisplayId(getDisplayId());
- options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
return options;
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 71cf5ca..b6c58e1 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -68,7 +68,6 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.LayerDrawable;
-import android.os.Build.VERSION_CODES;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
@@ -120,7 +119,6 @@
import com.android.internal.widget.FloatingToolbar;
import java.util.List;
-import java.util.function.Function;
/** @hide */
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
@@ -283,11 +281,6 @@
private Insets mLastBackgroundInsets = Insets.NONE;
private boolean mDrawLegacyNavigationBarBackground;
- /**
- * Whether the app targets an SDK that uses the new insets APIs.
- */
- private boolean mUseNewInsetsApi;
-
private PendingInsetsController mPendingInsetsController = new PendingInsetsController();
DecorView(Context context, int featureId, PhoneWindow window,
@@ -319,7 +312,6 @@
initResizingPaints();
mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK);
- mUseNewInsetsApi = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.R;
}
void setBackgroundFallback(@Nullable Drawable fallbackDrawable) {
@@ -1189,23 +1181,23 @@
// these flags wouldn't make the window draw behind the navigation bar, unless
// LAYOUT_HIDE_NAVIGATION was set.
//
- // Note: Once the app targets R+, we no longer do this logic because we can't rely on
- // SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION to indicate whether the app wants to handle it by
- // themselves.
+ // Note: Once the app uses the R+ Window.setDecorFitsSystemWindows(false) API we no longer
+ // consume insets because they might no longer set SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.
boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
|| !(controller == null || controller.isRequestedVisible(ITYPE_NAVIGATION_BAR));
+ boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows;
boolean forceConsumingNavBar = (mForceWindowDrawsBarBackgrounds
&& (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
&& (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
+ && decorFitsSystemWindows
&& !hideNavigation)
|| (mLastShouldAlwaysConsumeSystemBars && hideNavigation);
boolean consumingNavBar =
((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
&& (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
- && !hideNavigation
- // TODO IME wrap_content windows need to have margin to work properly
- && (!mUseNewInsetsApi || isImeWindow))
+ && decorFitsSystemWindows
+ && !hideNavigation)
|| forceConsumingNavBar;
// If we didn't request fullscreen layout, but we still got it because of the
@@ -1216,6 +1208,7 @@
|| (attrs.flags & FLAG_FULLSCREEN) != 0
|| !(controller == null || controller.isRequestedVisible(ITYPE_STATUS_BAR));
boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
+ && decorFitsSystemWindows
&& (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
&& (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
&& mForceWindowDrawsBarBackgrounds
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 138d0dd..25c114f 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -343,8 +343,7 @@
/** @see ViewRootImpl#mActivityConfigCallback */
private ActivityConfigCallback mActivityConfigCallback;
- private OnContentApplyWindowInsetsListener mPendingOnContentApplyWindowInsetsListener =
- sDefaultContentInsetsApplier;
+ boolean mDecorFitsSystemWindows = true;
static class WindowManagerHolder {
static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
@@ -2138,9 +2137,7 @@
/** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */
void onViewRootImplSet(ViewRootImpl viewRoot) {
viewRoot.setActivityConfigCallback(mActivityConfigCallback);
- viewRoot.setOnContentApplyWindowInsetsListener(
- mPendingOnContentApplyWindowInsetsListener);
- mPendingOnContentApplyWindowInsetsListener = null;
+ applyDecorFitsSystemWindows();
}
static private final String FOCUSED_ID_TAG = "android:focusedViewId";
@@ -3907,14 +3904,16 @@
@Override
public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
+ mDecorFitsSystemWindows = decorFitsSystemWindows;
+ applyDecorFitsSystemWindows();
+ }
+
+ private void applyDecorFitsSystemWindows() {
ViewRootImpl impl = getViewRootImplOrNull();
- OnContentApplyWindowInsetsListener listener = decorFitsSystemWindows
- ? sDefaultContentInsetsApplier
- : null;
if (impl != null) {
- impl.setOnContentApplyWindowInsetsListener(listener);
- } else {
- mPendingOnContentApplyWindowInsetsListener = listener;
+ impl.setOnContentApplyWindowInsetsListener(mDecorFitsSystemWindows
+ ? sDefaultContentInsetsApplier
+ : null);
}
}
}
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 684a292..896ee4f 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -186,4 +186,15 @@
RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK= 159;
RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED= 160;
RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET = 161;
+ CROSS_PROFILE_SETTINGS_PAGE_LAUNCHED_FROM_APP = 162;
+ CROSS_PROFILE_SETTINGS_PAGE_LAUNCHED_FROM_SETTINGS = 163;
+ CROSS_PROFILE_SETTINGS_PAGE_ADMIN_RESTRICTED = 164;
+ CROSS_PROFILE_SETTINGS_PAGE_MISSING_WORK_APP = 165;
+ CROSS_PROFILE_SETTINGS_PAGE_MISSING_PERSONAL_APP = 166;
+ CROSS_PROFILE_SETTINGS_PAGE_MISSING_INSTALL_BANNER_INTENT = 167;
+ CROSS_PROFILE_SETTINGS_PAGE_INSTALL_BANNER_CLICKED = 168;
+ CROSS_PROFILE_SETTINGS_PAGE_INSTALL_BANNER_NO_INTENT_CLICKED = 169;
+ CROSS_PROFILE_SETTINGS_PAGE_USER_CONSENTED = 170;
+ CROSS_PROFILE_SETTINGS_PAGE_USER_DECLINED_CONSENT = 171;
+ CROSS_PROFILE_SETTINGS_PAGE_PERMISSION_REVOKED = 172;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 451363f..c72239c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4122,7 +4122,10 @@
set of pages referenced over time.
<p>Declaring the permission implies intention to use the API and the user of the
device can grant permission through the Settings application.
- <p>Protection level: signature|privileged|appop -->
+ <p>Protection level: signature|privileged|appop
+ <p>A data loader has to be the one which provides data to install an app.
+ <p>A data loader has to have both permission:LOADER_USAGE_STATS AND
+ appop:LOADER_USAGE_STATS allowed to be able to access the read logs. -->
<permission android:name="android.permission.LOADER_USAGE_STATS"
android:protectionLevel="signature|privileged|appop" />
<uses-permission android:name="android.permission.LOADER_USAGE_STATS" />
diff --git a/core/tests/coretests/src/android/app/WindowContextTest.java b/core/tests/coretests/src/android/app/WindowContextTest.java
new file mode 100644
index 0000000..630e16a
--- /dev/null
+++ b/core/tests/coretests/src/android/app/WindowContextTest.java
@@ -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.
+ */
+
+package android.app;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link WindowContext}
+ *
+ * <p>Build/Install/Run:
+ * atest FrameworksCoreTests:WindowContextTest
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
+ */
+@FlakyTest(bugId = 150812449, detail = "Remove after confirmed it's stable.")
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowContextTest {
+ @Test
+ public void testWindowContextRelease_doRemoveWindowToken() throws Throwable {
+ final Context instContext = InstrumentationRegistry.getInstrumentation()
+ .getTargetContext();
+ final Display display = instContext.getSystemService(DisplayManager.class)
+ .getDisplay(DEFAULT_DISPLAY);
+ final Context context = instContext.createDisplayContext(display);
+ final WindowContext windowContext = new WindowContext(context, TYPE_APPLICATION_OVERLAY,
+ null /* options */);
+
+ final IBinder token = windowContext.getActivityToken();
+
+ final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
+ assertTrue("Token must be registered to WMS", wms.isWindowToken(token));
+
+ windowContext.release();
+
+ assertFalse("Token must be unregistered to WMS", wms.isWindowToken(token));
+ }
+}
diff --git a/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java
index 74524bf..ddc977d 100644
--- a/core/tests/coretests/src/android/view/WindowMetricsTest.java
+++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java
@@ -56,17 +56,17 @@
@Before
public void setUp() {
- final Context insetContext = InstrumentationRegistry.getInstrumentation()
+ final Context instContext = InstrumentationRegistry.getInstrumentation()
.getTargetContext();
- final Display display = insetContext.getSystemService(DisplayManager.class)
+ final Display display = instContext.getSystemService(DisplayManager.class)
.getDisplay(DEFAULT_DISPLAY);
- mWindowContext = insetContext.createDisplayContext(display)
+ mWindowContext = instContext.createDisplayContext(display)
.createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
mWm = mWindowContext.getSystemService(WindowManager.class);
}
@Test
- public void testAddViewANdRemoveView_GetMetrics_DoNotCrash() {
+ public void testAddViewAndRemoveView_GetMetrics_DoNotCrash() {
final View view = new View(mWindowContext);
final WindowManager.LayoutParams params =
new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 07cf415..19ad6f7 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1507,6 +1507,18 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "838570988": {
+ "message": "Could not report token removal to the window token client.",
+ "level": "WARN",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowToken.java"
+ },
+ "845234215": {
+ "message": "App is requesting an orientation, return %d for display id=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"853091290": {
"message": "Moved stack=%s behind stack=%s",
"level": "DEBUG",
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 4dd1a29..5f0acc8 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -264,8 +264,8 @@
/* numUpdates= */ Integer.MAX_VALUE,
/* smallestDisplacement= */ 0,
/* hideFromAppOps= */ false,
- /* lowPowerMode= */ false,
/* locationSettingsIgnored= */ false,
+ /* lowPowerMode= */ false,
/* workSource= */ null);
}
@@ -282,8 +282,8 @@
src.mNumUpdates,
src.mSmallestDisplacement,
src.mHideFromAppOps,
- src.mLowPowerMode,
src.mLocationSettingsIgnored,
+ src.mLowPowerMode,
src.mWorkSource);
}
diff --git a/packages/CarSystemUI/res/layout/notification_center_activity.xml b/packages/CarSystemUI/res/layout/notification_center_activity.xml
index 4fef489..0af74c4 100644
--- a/packages/CarSystemUI/res/layout/notification_center_activity.xml
+++ b/packages/CarSystemUI/res/layout/notification_center_activity.xml
@@ -20,8 +20,6 @@
android:id="@+id/notification_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:visibility="invisible"
- android:layout_marginBottom="@dimen/navigation_bar_height"
android:background="@color/notification_shade_background_color">
<View
diff --git a/packages/CarSystemUI/res/layout/notification_panel_container.xml b/packages/CarSystemUI/res/layout/notification_panel_container.xml
index bf71396..3b53c6a 100644
--- a/packages/CarSystemUI/res/layout/notification_panel_container.xml
+++ b/packages/CarSystemUI/res/layout/notification_panel_container.xml
@@ -18,4 +18,5 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notification_container"
android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:layout_height="match_parent"
+ android:visibility="invisible"/>
diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
index 1b0a211..067e359 100644
--- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
+++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
@@ -22,10 +22,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <!-- TODO(b/151617493): replace marginBottom with insets. -->
<ViewStub android:id="@+id/notification_panel_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout="@layout/notification_panel_container"/>
+ android:layout="@layout/notification_panel_container"
+ android:layout_marginBottom="@dimen/navigation_bar_height"/>
<ViewStub android:id="@+id/fullscreen_user_switcher_stub"
android:layout_width="match_parent"
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java
index b057198..44e43fe 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java
@@ -24,7 +24,7 @@
*/
public interface CarDeviceProvisionedController extends DeviceProvisionedController {
/**
- * Returns {@code true} then SUW is in progress for the given user.
+ * Returns {@code true} when SUW is in progress for the given user.
*/
boolean isUserSetupInProgress(int user);
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index 1901a2d..d8a894c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -16,9 +16,6 @@
package com.android.systemui.car.notification;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.car.Car;
import android.car.drivingstate.CarUxRestrictionsManager;
@@ -33,7 +30,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
@@ -54,7 +50,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.window.OverlayViewController;
+import com.android.systemui.window.OverlayPanelViewController;
import com.android.systemui.window.OverlayViewGlobalStateController;
import javax.inject.Inject;
@@ -62,39 +58,22 @@
/** View controller for the notification panel. */
@Singleton
-public class NotificationPanelViewController extends OverlayViewController {
+public class NotificationPanelViewController extends OverlayPanelViewController {
- // used to calculate how fast to open or close the window
- private static final float DEFAULT_FLING_VELOCITY = 0;
- // max time a fling animation takes
- private static final float FLING_ANIMATION_MAX_TIME = 0.5f;
- // acceleration rate for the fling animation
- private static final float FLING_SPEED_UP_FACTOR = 0.6f;
-
- private static final int SWIPE_DOWN_MIN_DISTANCE = 25;
- private static final int SWIPE_MAX_OFF_PATH = 75;
- private static final int SWIPE_THRESHOLD_VELOCITY = 200;
private static final boolean DEBUG = true;
private static final String TAG = "NotificationPanelViewController";
private final Context mContext;
private final Resources mResources;
private final CarServiceProvider mCarServiceProvider;
- private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final IStatusBarService mBarService;
private final CommandQueue mCommandQueue;
private final NotificationDataManager mNotificationDataManager;
private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
private final CarNotificationListener mCarNotificationListener;
private final NotificationClickHandlerFactory mNotificationClickHandlerFactory;
- private final FlingAnimationUtils mFlingAnimationUtils;
private final StatusBarStateController mStatusBarStateController;
- private final int mSettleClosePercentage;
-
- private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
- private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
-
private float mInitialBackgroundAlpha;
private float mBackgroundAlphaDiff;
@@ -108,13 +87,7 @@
private float mFirstTouchDownOnGlassPane;
private boolean mNotificationListAtBottomAtTimeOfTouch;
private boolean mIsSwipingVerticallyToClose;
- private int mPercentageFromBottom;
- private boolean mIsNotificationAnimating;
private boolean mIsNotificationCardSwiping;
- private boolean mPanelExpanded = false;
-
- private View.OnTouchListener mTopNavBarNotificationTouchListener;
- private View.OnTouchListener mNavBarNotificationTouchListener;
private OnUnseenCountUpdateListener mUnseenCountUpdateListener;
@@ -123,6 +96,7 @@
Context context,
@Main Resources resources,
OverlayViewGlobalStateController overlayViewGlobalStateController,
+ FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
/* Other things */
CarServiceProvider carServiceProvider,
@@ -135,26 +109,21 @@
CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
CarNotificationListener carNotificationListener,
NotificationClickHandlerFactory notificationClickHandlerFactory,
- FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
/* Things that need to be replaced */
StatusBarStateController statusBarStateController
) {
- super(R.id.notification_panel_stub, overlayViewGlobalStateController);
+ super(context, resources, R.id.notification_panel_stub, overlayViewGlobalStateController,
+ flingAnimationUtilsBuilder, carDeviceProvisionedController);
mContext = context;
mResources = resources;
mCarServiceProvider = carServiceProvider;
- mCarDeviceProvisionedController = carDeviceProvisionedController;
mBarService = barService;
mCommandQueue = commandQueue;
mNotificationDataManager = notificationDataManager;
mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper;
mCarNotificationListener = carNotificationListener;
mNotificationClickHandlerFactory = notificationClickHandlerFactory;
- mFlingAnimationUtils = flingAnimationUtilsBuilder
- .setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME)
- .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
- .build();
mStatusBarStateController = statusBarStateController;
// Notification background setup.
@@ -175,60 +144,6 @@
+ " percentage");
}
mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha;
-
- // Notification Panel param setup
- mSettleClosePercentage = mResources.getInteger(
- R.integer.notification_settle_close_percentage);
-
- // Attached to the top navigation bar (i.e. status bar) to detect pull down of the
- // notification shade.
- GestureDetector openGestureDetector = new GestureDetector(mContext,
- new OpenNotificationGestureListener() {
- @Override
- protected void openNotification() {
- animateExpandNotificationsPanel();
- }
- });
-
- // Attached to the NavBars to close the notification shade
- GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext,
- new NavBarCloseNotificationGestureListener() {
- @Override
- protected void close() {
- if (mPanelExpanded) {
- animateCollapsePanels();
- }
- }
- });
-
- mTopNavBarNotificationTouchListener = (v, event) -> {
- if (!isInflated()) {
- getOverlayViewGlobalStateController().inflateView(this);
- }
- if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
- return true;
- }
-
- boolean consumed = openGestureDetector.onTouchEvent(event);
- if (consumed) {
- return true;
- }
- maybeCompleteAnimation(event);
- return true;
- };
-
- mNavBarNotificationTouchListener =
- (v, event) -> {
- if (!isInflated()) {
- return true;
- }
- boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
- if (consumed) {
- return true;
- }
- maybeCompleteAnimation(event);
- return true;
- };
}
@Override
@@ -252,14 +167,13 @@
private void onNotificationViewInflated() {
// Find views.
mNotificationView = getLayout().findViewById(R.id.notification_view);
- View glassPane = mNotificationView.findViewById(R.id.glass_pane);
- mHandleBar = mNotificationView.findViewById(R.id.handle_bar);
- mNotificationList = mNotificationView.findViewById(R.id.notifications);
+ setupHandleBar();
+ setupNotificationPanel();
mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
if (launchResult == ActivityManager.START_TASK_TO_FRONT
|| launchResult == ActivityManager.START_SUCCESS) {
- animateCollapsePanels();
+ animateCollapsePanel();
}
});
@@ -269,39 +183,52 @@
mNotificationDataManager.getUnseenNotificationCount());
}
});
+
mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
mNotificationView.setNotificationDataManager(mNotificationDataManager);
- mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
- super.onScrolled(recyclerView, dx, dy);
- if (!mNotificationList.canScrollVertically(1)) {
- mNotificationListAtBottom = true;
- return;
- }
- mNotificationListAtBottom = false;
- mIsSwipingVerticallyToClose = false;
- mNotificationListAtBottomAtTimeOfTouch = false;
- }
- });
+ mCarServiceProvider.addListener(car -> {
+ CarUxRestrictionsManager carUxRestrictionsManager =
+ (CarUxRestrictionsManager)
+ car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
+ mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
+ carUxRestrictionsManager);
- // Attached to the notification ui to detect close request of the notification shade.
+ mNotificationViewController = new NotificationViewController(
+ mNotificationView,
+ PreprocessingManager.getInstance(mContext),
+ mCarNotificationListener,
+ mCarUxRestrictionManagerWrapper,
+ mNotificationDataManager);
+ mNotificationViewController.enable();
+ });
+ }
+
+ private void setupHandleBar() {
+ mHandleBar = mNotificationView.findViewById(R.id.handle_bar);
+ GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
+ new HandleBarCloseGestureListener());
+ mHandleBar.setOnTouchListener((v, event) -> {
+ handleBarCloseNotificationGestureDetector.onTouchEvent(event);
+ maybeCompleteAnimation(event);
+ return true;
+ });
+ }
+
+ private void setupNotificationPanel() {
+ View glassPane = mNotificationView.findViewById(R.id.glass_pane);
+ mNotificationList = mNotificationView.findViewById(R.id.notifications);
GestureDetector closeGestureDetector = new GestureDetector(mContext,
- new CloseNotificationGestureListener() {
+ new CloseGestureListener() {
@Override
protected void close() {
- if (mPanelExpanded) {
- animateCollapsePanels();
+ if (isPanelExpanded()) {
+ animateCollapsePanel();
}
}
});
- // Attached to the Handle bar to close the notification shade
- GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
- new HandleBarCloseNotificationGestureListener());
-
// The glass pane is used to view touch events before passed to the notification list.
// This allows us to initialize gesture listeners and detect when to close the notifications
glassPane.setOnTouchListener((v, event) -> {
@@ -320,6 +247,21 @@
return false;
});
+ mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+ // Check if we can scroll vertically downwards.
+ if (!mNotificationList.canScrollVertically(/* direction= */ 1)) {
+ mNotificationListAtBottom = true;
+ return;
+ }
+ mNotificationListAtBottom = false;
+ mIsSwipingVerticallyToClose = false;
+ mNotificationListAtBottomAtTimeOfTouch = false;
+ }
+ });
+
mNotificationList.setOnTouchListener((v, event) -> {
mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
> SWIPE_MAX_OFF_PATH;
@@ -341,19 +283,19 @@
boolean handled = closeGestureDetector.onTouchEvent(event);
boolean isTracking = mIsTracking;
- Rect rect = mNotificationView.getClipBounds();
+ Rect rect = getLayout().getClipBounds();
float clippedHeight = 0;
if (rect != null) {
clippedHeight = rect.bottom;
}
if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
&& mIsSwipingVerticallyToClose) {
- if (mSettleClosePercentage < mPercentageFromBottom && isTracking) {
- animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
- } else if (clippedHeight != mNotificationView.getHeight() && isTracking) {
+ if (getSettleClosePercentage() < getPercentageFromBottom() && isTracking) {
+ animatePanel(DEFAULT_FLING_VELOCITY, false);
+ } else if (clippedHeight != getLayout().getHeight() && isTracking) {
// this can be caused when user is at the end of the list and trying to
// fling to top of the list by scrolling down.
- animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
+ animatePanel(DEFAULT_FLING_VELOCITY, true);
}
}
@@ -365,28 +307,6 @@
}
return handled || isTracking;
});
-
- mCarServiceProvider.addListener(car -> {
- CarUxRestrictionsManager carUxRestrictionsManager =
- (CarUxRestrictionsManager)
- car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
- mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
- carUxRestrictionsManager);
-
- mNotificationViewController = new NotificationViewController(
- mNotificationView,
- PreprocessingManager.getInstance(mContext),
- mCarNotificationListener,
- mCarUxRestrictionManagerWrapper,
- mNotificationDataManager);
- mNotificationViewController.enable();
- });
-
- mHandleBar.setOnTouchListener((v, event) -> {
- handleBarCloseNotificationGestureDetector.onTouchEvent(event);
- maybeCompleteAnimation(event);
- return true;
- });
}
/** Called when the car power state is changed to ON. */
@@ -397,139 +317,40 @@
mNotificationDataManager.clearAll();
}
- View.OnTouchListener getTopNavBarNotificationTouchListener() {
- return mTopNavBarNotificationTouchListener;
+ @Override
+ protected boolean shouldAnimateCollapsePanel() {
+ return true;
}
- View.OnTouchListener getNavBarNotificationTouchListener() {
- return mNavBarNotificationTouchListener;
+ @Override
+ protected void onAnimateCollapsePanel() {
+ // No op.
}
- private void maybeCompleteAnimation(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_UP
- && mNotificationView.getVisibility() == View.VISIBLE) {
- if (mSettleClosePercentage < mPercentageFromBottom) {
- animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
- } else {
- animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
- }
- }
+ @Override
+ protected boolean shouldAnimateExpandPanel() {
+ return mCommandQueue.panelsEnabled();
}
- /**
- * Animates the notification shade from one position to other. This is used to either open or
- * close the notification shade completely with a velocity. If the animation is to close the
- * notification shade this method also makes the view invisible after animation ends.
- */
- private void animateNotificationPanel(float velocity, boolean isClosing) {
- float to = 0;
- if (!isClosing) {
- to = mNotificationView.getHeight();
- }
-
- Rect rect = mNotificationView.getClipBounds();
- if (rect != null && rect.bottom != to) {
- float from = rect.bottom;
- animate(from, to, velocity, isClosing);
- return;
- }
-
- // We will only be here if the shade is being opened programmatically or via button when
- // height of the layout was not calculated.
- ViewTreeObserver notificationTreeObserver = mNotificationView.getViewTreeObserver();
- notificationTreeObserver.addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- ViewTreeObserver obs = mNotificationView.getViewTreeObserver();
- obs.removeOnGlobalLayoutListener(this);
- float to = mNotificationView.getHeight();
- animate(/* from= */ 0, to, velocity, isClosing);
- }
- });
- }
-
- private void animateCollapsePanels() {
- if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) {
- return;
- }
- getOverlayViewGlobalStateController().setWindowFocusable(false);
- animateNotificationPanel(mClosingVelocity, true);
- }
-
- private void animateExpandNotificationsPanel() {
- if (!mCommandQueue.panelsEnabled()
- || !mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
- return;
- }
- // scroll to top
+ @Override
+ protected void onAnimateExpandPanel() {
mNotificationList.scrollToPosition(0);
- setPanelVisible(true);
- mNotificationView.setVisibility(View.VISIBLE);
- animateNotificationPanel(mOpeningVelocity, false);
-
- setPanelExpanded(true);
}
- private void animate(float from, float to, float velocity, boolean isClosing) {
- if (mIsNotificationAnimating) {
- return;
- }
- mIsNotificationAnimating = true;
- mIsTracking = true;
- ValueAnimator animator = ValueAnimator.ofFloat(from, to);
- animator.addUpdateListener(
- animation -> {
- float animatedValue = (Float) animation.getAnimatedValue();
- setNotificationViewClipBounds((int) animatedValue);
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mIsNotificationAnimating = false;
- mIsTracking = false;
- mOpeningVelocity = DEFAULT_FLING_VELOCITY;
- mClosingVelocity = DEFAULT_FLING_VELOCITY;
- if (isClosing) {
- setPanelVisible(false);
- mNotificationView.setVisibility(View.INVISIBLE);
- mNotificationView.setClipBounds(null);
- mNotificationViewController.onVisibilityChanged(false);
- // let the status bar know that the panel is closed
- setPanelExpanded(false);
- } else {
- mNotificationViewController.onVisibilityChanged(true);
- // let the status bar know that the panel is open
- mNotificationView.setVisibleNotificationsAsSeen();
- setPanelExpanded(true);
- }
- }
- });
- mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
- animator.start();
+ @Override
+ protected void onCollapseAnimationEnd() {
+ mNotificationViewController.onVisibilityChanged(false);
}
- /**
- * Set the panel view to be visible.
- */
- public void setPanelVisible(boolean visible) {
- if (visible && !getOverlayViewGlobalStateController().isWindowVisible()) {
- getOverlayViewGlobalStateController().setWindowVisible(true);
- }
- if (!visible && getOverlayViewGlobalStateController().isWindowVisible()) {
- getOverlayViewGlobalStateController().setWindowVisible(false);
- }
- getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- getOverlayViewGlobalStateController().setWindowFocusable(visible);
+ @Override
+ protected void onExpandAnimationEnd() {
+ mNotificationViewController.onVisibilityChanged(true);
+ mNotificationView.setVisibleNotificationsAsSeen();
}
- /**
- * Set the panel state to expanded. This will expand or collapse the overlay window if
- * necessary.
- */
- public void setPanelExpanded(boolean expand) {
- mPanelExpanded = expand;
+ @Override
+ protected void onPanelExpanded(boolean expand) {
+ super.onPanelExpanded(expand);
if (expand && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
if (DEBUG) {
@@ -550,19 +371,19 @@
}
}
- private void setNotificationViewClipBounds(int height) {
- if (height > mNotificationView.getHeight()) {
- height = mNotificationView.getHeight();
- }
- Rect clipBounds = new Rect();
- clipBounds.set(0, 0, mNotificationView.getWidth(), height);
- // Sets the clip region on the notification list view.
- mNotificationView.setClipBounds(clipBounds);
+ @Override
+ protected void onOpenScrollStart() {
+ mNotificationList.scrollToPosition(0);
+ }
+
+ @Override
+ protected void onScroll(int height) {
if (mHandleBar != null) {
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
}
+
if (mNotificationView.getHeight() > 0) {
Drawable background = mNotificationView.getBackground().mutate();
background.setAlpha((int) (getBackgroundAlpha(height) * 255));
@@ -570,6 +391,13 @@
}
}
+ @Override
+ protected boolean shouldAllowClosingScroll() {
+ // Unless the notification list is at the bottom, the panel shouldn't be allowed to
+ // collapse on scroll.
+ return mNotificationListAtBottomAtTimeOfTouch;
+ }
+
/**
* Calculates the alpha value for the background based on how much of the notification
* shade is visible to the user. When the notification shade is completely open then
@@ -580,30 +408,6 @@
+ ((float) height / mNotificationView.getHeight() * mBackgroundAlphaDiff);
}
- private void calculatePercentageFromBottom(float height) {
- if (mNotificationView.getHeight() > 0) {
- mPercentageFromBottom = (int) Math.abs(
- height / mNotificationView.getHeight() * 100);
- }
- }
-
- /** Toggles the visibility of the notification panel. */
- public void toggle() {
- if (!isInflated()) {
- getOverlayViewGlobalStateController().inflateView(this);
- }
- if (mPanelExpanded) {
- animateCollapsePanels();
- } else {
- animateExpandNotificationsPanel();
- }
- }
-
- /** Returns {@code true} if the notification panel is expanded. */
- public boolean isPanelExpanded() {
- return mPanelExpanded;
- }
-
/** Sets the unseen count listener. */
public void setOnUnseenCountUpdateListener(OnUnseenCountUpdateListener listener) {
mUnseenCountUpdateListener = listener;
@@ -619,154 +423,9 @@
}
/**
- * Only responsible for open hooks. Since once the panel opens it covers all elements
- * there is no need to merge with close.
- */
- private abstract class OpenNotificationGestureListener extends
- GestureDetector.SimpleOnGestureListener {
-
- @Override
- public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
- float distanceY) {
-
- if (mNotificationView.getVisibility() == View.INVISIBLE) {
- // when the on-scroll is called for the first time to open.
- mNotificationList.scrollToPosition(0);
- }
- setPanelVisible(true);
- mNotificationView.setVisibility(View.VISIBLE);
-
- // clips the view for the notification shade when the user scrolls to open.
- setNotificationViewClipBounds((int) event2.getRawY());
-
- // Initially the scroll starts with height being zero. This checks protects from divide
- // by zero error.
- calculatePercentageFromBottom(event2.getRawY());
-
- mIsTracking = true;
- return true;
- }
-
-
- @Override
- public boolean onFling(MotionEvent event1, MotionEvent event2,
- float velocityX, float velocityY) {
- if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
- mOpeningVelocity = velocityY;
- openNotification();
- return true;
- }
- animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
-
- return false;
- }
-
- protected abstract void openNotification();
- }
-
- /**
- * To be installed on the open panel notification panel
- */
- private abstract class CloseNotificationGestureListener extends
- GestureDetector.SimpleOnGestureListener {
-
- @Override
- public boolean onSingleTapUp(MotionEvent motionEvent) {
- if (mPanelExpanded) {
- animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
- }
- return true;
- }
-
- @Override
- public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
- float distanceY) {
- // should not clip while scroll to the bottom of the list.
- if (!mNotificationListAtBottomAtTimeOfTouch) {
- return false;
- }
- float actualNotificationHeight =
- mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY());
- if (actualNotificationHeight > mNotificationView.getHeight()) {
- actualNotificationHeight = mNotificationView.getHeight();
- }
- if (mNotificationView.getHeight() > 0) {
- mPercentageFromBottom = (int) Math.abs(
- actualNotificationHeight / mNotificationView.getHeight() * 100);
- boolean isUp = distanceY > 0;
-
- // This check is to figure out if onScroll was called while swiping the card at
- // bottom of the list. At that time we should not allow notification shade to
- // close. We are also checking for the upwards swipe gesture here because it is
- // possible if a user is closing the notification shade and while swiping starts
- // to open again but does not fling. At that time we should allow the
- // notification shade to close fully or else it would stuck in between.
- if (Math.abs(mNotificationView.getHeight() - actualNotificationHeight)
- > SWIPE_DOWN_MIN_DISTANCE && isUp) {
- setNotificationViewClipBounds((int) actualNotificationHeight);
- mIsTracking = true;
- } else if (!isUp) {
- setNotificationViewClipBounds((int) actualNotificationHeight);
- }
- }
- // if we return true the items in RV won't be scrollable.
- return false;
- }
-
-
- @Override
- public boolean onFling(MotionEvent event1, MotionEvent event2,
- float velocityX, float velocityY) {
- // should not fling if the touch does not start when view is at the bottom of the list.
- if (!mNotificationListAtBottomAtTimeOfTouch) {
- return false;
- }
- if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
- || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
- // swipe was not vertical or was not fast enough
- return false;
- }
- boolean isUp = velocityY < 0;
- if (isUp) {
- close();
- return true;
- } else {
- // we should close the shade
- animateNotificationPanel(velocityY, false);
- }
- return false;
- }
-
- protected abstract void close();
- }
-
- /**
- * To be installed on the nav bars.
- */
- private abstract class NavBarCloseNotificationGestureListener extends
- CloseNotificationGestureListener {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- mClosingVelocity = DEFAULT_FLING_VELOCITY;
- if (mPanelExpanded) {
- close();
- }
- return super.onSingleTapUp(e);
- }
-
- @Override
- public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
- float distanceY) {
- calculatePercentageFromBottom(event2.getRawY());
- setNotificationViewClipBounds((int) event2.getRawY());
- return true;
- }
- }
-
- /**
* To be installed on the handle bar.
*/
- private class HandleBarCloseNotificationGestureListener extends
+ private class HandleBarCloseGestureListener extends
GestureDetector.SimpleOnGestureListener {
@Override
@@ -777,9 +436,8 @@
// the handle bar we should calculate the height using the diff of event1 and event2.
// This will help the notification shade to clip smoothly as the event2 value changes
// as event1 value will be fixed.
- int clipHeight =
- mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY());
- setNotificationViewClipBounds(clipHeight);
+ int clipHeight = getLayout().getHeight() - (int) (event1.getRawY() - event2.getRawY());
+ setViewClipBounds(clipHeight);
return true;
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
index 110c2ee..9d71797 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
@@ -59,13 +59,13 @@
@Override
public void registerListeners() {
mCarNavigationBarController.registerTopBarTouchListener(
- mNotificationPanelViewController.getTopNavBarNotificationTouchListener());
+ mNotificationPanelViewController.getDragOpenTouchListener());
mCarNavigationBarController.registerBottomBarTouchListener(
- mNotificationPanelViewController.getNavBarNotificationTouchListener());
+ mNotificationPanelViewController.getDragCloseTouchListener());
mCarNavigationBarController.registerLeftBarTouchListener(
- mNotificationPanelViewController.getNavBarNotificationTouchListener());
+ mNotificationPanelViewController.getDragCloseTouchListener());
mCarNavigationBarController.registerRightBarTouchListener(
- mNotificationPanelViewController.getNavBarNotificationTouchListener());
+ mNotificationPanelViewController.getDragCloseTouchListener());
mCarNavigationBarController.registerNotificationController(
new CarNavigationBarController.NotificationsShadeController() {
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayPanelViewController.java
new file mode 100644
index 0000000..58022f1
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayPanelViewController.java
@@ -0,0 +1,563 @@
+/*
+ * 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.window;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import androidx.annotation.CallSuper;
+
+import com.android.systemui.R;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+
+/**
+ * The {@link OverlayPanelViewController} provides additional dragging animation capabilities to
+ * {@link OverlayViewController}.
+ */
+public abstract class OverlayPanelViewController extends OverlayViewController {
+
+ private static final boolean DEBUG = true;
+ private static final String TAG = "OverlayPanelViewController";
+
+ // used to calculate how fast to open or close the window
+ protected static final float DEFAULT_FLING_VELOCITY = 0;
+ // max time a fling animation takes
+ protected static final float FLING_ANIMATION_MAX_TIME = 0.5f;
+ // acceleration rate for the fling animation
+ protected static final float FLING_SPEED_UP_FACTOR = 0.6f;
+
+ protected static final int SWIPE_DOWN_MIN_DISTANCE = 25;
+ protected static final int SWIPE_MAX_OFF_PATH = 75;
+ protected static final int SWIPE_THRESHOLD_VELOCITY = 200;
+
+ private final FlingAnimationUtils mFlingAnimationUtils;
+ private final CarDeviceProvisionedController mCarDeviceProvisionedController;
+ private final View.OnTouchListener mDragOpenTouchListener;
+ private final View.OnTouchListener mDragCloseTouchListener;
+
+ private final int mSettleClosePercentage;
+ private int mPercentageFromBottom;
+
+ private boolean mPanelVisible;
+ private boolean mPanelExpanded;
+
+ private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
+ private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
+
+ private boolean mIsAnimating;
+ private boolean mIsTracking;
+
+ public OverlayPanelViewController(
+ Context context,
+ @Main Resources resources,
+ int stubId,
+ OverlayViewGlobalStateController overlayViewGlobalStateController,
+ FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+ CarDeviceProvisionedController carDeviceProvisionedController
+ ) {
+ super(stubId, overlayViewGlobalStateController);
+
+ mFlingAnimationUtils = flingAnimationUtilsBuilder
+ .setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME)
+ .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
+ .build();
+ mCarDeviceProvisionedController = carDeviceProvisionedController;
+
+ mSettleClosePercentage = resources.getInteger(
+ R.integer.notification_settle_close_percentage);
+
+ // Attached to the top navigation bar (i.e. status bar) to detect pull down of the
+ // notification shade.
+ GestureDetector openGestureDetector = new GestureDetector(context,
+ new OpenGestureListener() {
+ @Override
+ protected void open() {
+ animateExpandPanel();
+ }
+ });
+
+ // Attached to the NavBars to close the notification shade
+ GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(context,
+ new SystemBarCloseGestureListener() {
+ @Override
+ protected void close() {
+ if (isPanelExpanded()) {
+ animateCollapsePanel();
+ }
+ }
+ });
+
+ mDragOpenTouchListener = (v, event) -> {
+ if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
+ return true;
+ }
+ if (!isInflated()) {
+ getOverlayViewGlobalStateController().inflateView(this);
+ }
+
+ boolean consumed = openGestureDetector.onTouchEvent(event);
+ if (consumed) {
+ return true;
+ }
+ maybeCompleteAnimation(event);
+ return true;
+ };
+
+ mDragCloseTouchListener = (v, event) -> {
+ if (!isInflated()) {
+ return true;
+ }
+ boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
+ if (consumed) {
+ return true;
+ }
+ maybeCompleteAnimation(event);
+ return true;
+ };
+ }
+
+ /** Toggles the visibility of the panel. */
+ public void toggle() {
+ if (!isInflated()) {
+ getOverlayViewGlobalStateController().inflateView(this);
+ }
+ if (isPanelExpanded()) {
+ animateCollapsePanel();
+ } else {
+ animateExpandPanel();
+ }
+ }
+
+ /* ***************************************************************************************** *
+ * Panel Animation
+ * ***************************************************************************************** */
+
+ /** Animates the closing of the panel. */
+ protected void animateCollapsePanel() {
+ if (!shouldAnimateCollapsePanel()) {
+ return;
+ }
+
+ if (!isPanelExpanded() || !isPanelVisible()) {
+ return;
+ }
+
+ onAnimateCollapsePanel();
+ getOverlayViewGlobalStateController().setWindowFocusable(false);
+ animatePanel(mClosingVelocity, /* isClosing= */ true);
+ }
+
+ /** Determines whether {@link #animateCollapsePanel()} should collapse the panel. */
+ protected abstract boolean shouldAnimateCollapsePanel();
+
+ /** Called when the panel is beginning to collapse. */
+ protected abstract void onAnimateCollapsePanel();
+
+ /** Animates the expansion of the panel. */
+ protected void animateExpandPanel() {
+ if (!shouldAnimateExpandPanel()) {
+ return;
+ }
+
+ if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
+ return;
+ }
+
+ onAnimateExpandPanel();
+ setPanelVisible(true);
+ animatePanel(mOpeningVelocity, /* isClosing= */ false);
+
+ setPanelExpanded(true);
+ }
+
+ /** Determines whether {@link #animateExpandPanel()}} should expand the panel. */
+ protected abstract boolean shouldAnimateExpandPanel();
+
+ /** Called when the panel is beginning to expand. */
+ protected abstract void onAnimateExpandPanel();
+
+ /**
+ * Depending on certain conditions, determines whether to fully expand or collapse the panel.
+ */
+ protected void maybeCompleteAnimation(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_UP
+ && isPanelVisible()) {
+ if (mSettleClosePercentage < mPercentageFromBottom) {
+ animatePanel(DEFAULT_FLING_VELOCITY, false);
+ } else {
+ animatePanel(DEFAULT_FLING_VELOCITY, true);
+ }
+ }
+ }
+
+ /**
+ * Animates the panel from one position to other. This is used to either open or
+ * close the panel completely with a velocity. If the animation is to close the
+ * panel this method also makes the view invisible after animation ends.
+ */
+ protected void animatePanel(float velocity, boolean isClosing) {
+ float to = 0;
+ if (!isClosing) {
+ to = getLayout().getHeight();
+ }
+
+ Rect rect = getLayout().getClipBounds();
+ if (rect != null && rect.bottom != to) {
+ float from = rect.bottom;
+ animate(from, to, velocity, isClosing);
+ return;
+ }
+
+ // We will only be here if the shade is being opened programmatically or via button when
+ // height of the layout was not calculated.
+ ViewTreeObserver notificationTreeObserver = getLayout().getViewTreeObserver();
+ notificationTreeObserver.addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ ViewTreeObserver obs = getLayout().getViewTreeObserver();
+ obs.removeOnGlobalLayoutListener(this);
+ float to = getLayout().getHeight();
+ animate(/* from= */ 0, to, velocity, isClosing);
+ }
+ });
+ }
+
+ private void animate(float from, float to, float velocity, boolean isClosing) {
+ if (mIsAnimating) {
+ return;
+ }
+ mIsAnimating = true;
+ mIsTracking = true;
+ ValueAnimator animator = ValueAnimator.ofFloat(from, to);
+ animator.addUpdateListener(
+ animation -> {
+ float animatedValue = (Float) animation.getAnimatedValue();
+ setViewClipBounds((int) animatedValue);
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mIsAnimating = false;
+ mIsTracking = false;
+ mOpeningVelocity = DEFAULT_FLING_VELOCITY;
+ mClosingVelocity = DEFAULT_FLING_VELOCITY;
+ if (isClosing) {
+ setPanelVisible(false);
+ getLayout().setClipBounds(null);
+ onCollapseAnimationEnd();
+ setPanelExpanded(false);
+ } else {
+ onExpandAnimationEnd();
+ setPanelExpanded(true);
+ }
+ }
+ });
+ getFlingAnimationUtils().apply(animator, from, to, Math.abs(velocity));
+ animator.start();
+ }
+
+ /**
+ * Called in {@link Animator.AnimatorListener#onAnimationEnd(Animator)} when the panel is
+ * closing.
+ */
+ protected abstract void onCollapseAnimationEnd();
+
+ /**
+ * Called in {@link Animator.AnimatorListener#onAnimationEnd(Animator)} when the panel is
+ * opening.
+ */
+ protected abstract void onExpandAnimationEnd();
+
+ /* ***************************************************************************************** *
+ * Panel Visibility
+ * ***************************************************************************************** */
+
+ /** Set the panel view to be visible. */
+ protected final void setPanelVisible(boolean visible) {
+ mPanelVisible = visible;
+ onPanelVisible(visible);
+ }
+
+ /** Returns {@code true} if panel is visible. */
+ public final boolean isPanelVisible() {
+ return mPanelVisible;
+ }
+
+ /** Business logic run when panel visibility is set. */
+ @CallSuper
+ protected void onPanelVisible(boolean visible) {
+ if (DEBUG) {
+ Log.e(TAG, "onPanelVisible: " + visible);
+ }
+
+ if (visible && !getOverlayViewGlobalStateController().isWindowVisible()) {
+ getOverlayViewGlobalStateController().setWindowVisible(true);
+ }
+ if (!visible && getOverlayViewGlobalStateController().isWindowVisible()) {
+ getOverlayViewGlobalStateController().setWindowVisible(false);
+ }
+ getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ getOverlayViewGlobalStateController().setWindowFocusable(visible);
+ }
+
+ /* ***************************************************************************************** *
+ * Panel Expansion
+ * ***************************************************************************************** */
+
+ /**
+ * Set the panel state to expanded. This will expand or collapse the overlay window if
+ * necessary.
+ */
+ protected final void setPanelExpanded(boolean expand) {
+ mPanelExpanded = expand;
+ onPanelExpanded(expand);
+ }
+
+ /** Returns {@code true} if panel is expanded. */
+ public final boolean isPanelExpanded() {
+ return mPanelExpanded;
+ }
+
+ @CallSuper
+ protected void onPanelExpanded(boolean expand) {
+ if (DEBUG) {
+ Log.e(TAG, "onPanelExpanded: " + expand);
+ }
+ }
+
+ /* ***************************************************************************************** *
+ * Misc
+ * ***************************************************************************************** */
+
+ protected void calculatePercentageFromBottom(float height) {
+ if (getLayout().getHeight() > 0) {
+ mPercentageFromBottom = (int) Math.abs(
+ height / getLayout().getHeight() * 100);
+ }
+ }
+
+ protected void setViewClipBounds(int height) {
+ if (height > getLayout().getHeight()) {
+ height = getLayout().getHeight();
+ }
+ Rect clipBounds = new Rect();
+ clipBounds.set(0, 0, getLayout().getWidth(), height);
+ getLayout().setClipBounds(clipBounds);
+ onScroll(height);
+ }
+
+ /** Called while scrolling. */
+ protected abstract void onScroll(int height);
+
+ /* ***************************************************************************************** *
+ * Getters
+ * ***************************************************************************************** */
+
+ /** Returns the open touch listener. */
+ public final View.OnTouchListener getDragOpenTouchListener() {
+ return mDragOpenTouchListener;
+ }
+
+ /** Returns the close touch listener. */
+ public final View.OnTouchListener getDragCloseTouchListener() {
+ return mDragCloseTouchListener;
+ }
+
+ /** Gets the fling animation utils used for animating this panel. */
+ protected final FlingAnimationUtils getFlingAnimationUtils() {
+ return mFlingAnimationUtils;
+ }
+
+ /** Returns {@code true} if the panel is currently tracking. */
+ protected final boolean isTracking() {
+ return mIsTracking;
+ }
+
+ /** Returns {@code true} if the panel is currently animating. */
+ protected final boolean isAnimating() {
+ return mIsAnimating;
+ }
+
+ /** Returns the percentage of the panel that is open from the bottom. */
+ protected final int getPercentageFromBottom() {
+ return mPercentageFromBottom;
+ }
+
+ /** Returns the percentage at which we've determined whether to open or close the panel. */
+ protected final int getSettleClosePercentage() {
+ return mSettleClosePercentage;
+ }
+
+ /* ***************************************************************************************** *
+ * Gesture Listeners
+ * ***************************************************************************************** */
+
+ /** Called when the user is beginning to scroll down the panel. */
+ protected abstract void onOpenScrollStart();
+
+ /**
+ * Only responsible for open hooks. Since once the panel opens it covers all elements
+ * there is no need to merge with close.
+ */
+ protected abstract class OpenGestureListener extends
+ GestureDetector.SimpleOnGestureListener {
+
+ @Override
+ public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
+ float distanceY) {
+
+ if (!isPanelVisible()) {
+ onOpenScrollStart();
+ }
+ setPanelVisible(true);
+
+ // clips the view for the notification shade when the user scrolls to open.
+ setViewClipBounds((int) event2.getRawY());
+
+ // Initially the scroll starts with height being zero. This checks protects from divide
+ // by zero error.
+ calculatePercentageFromBottom(event2.getRawY());
+
+ mIsTracking = true;
+ return true;
+ }
+
+
+ @Override
+ public boolean onFling(MotionEvent event1, MotionEvent event2,
+ float velocityX, float velocityY) {
+ if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
+ mOpeningVelocity = velocityY;
+ open();
+ return true;
+ }
+ animatePanel(DEFAULT_FLING_VELOCITY, true);
+
+ return false;
+ }
+
+ protected abstract void open();
+ }
+
+ /** Determines whether the scroll event should allow closing of the panel. */
+ protected abstract boolean shouldAllowClosingScroll();
+
+ protected abstract class CloseGestureListener extends
+ GestureDetector.SimpleOnGestureListener {
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent motionEvent) {
+ if (isPanelExpanded()) {
+ animatePanel(DEFAULT_FLING_VELOCITY, true);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
+ float distanceY) {
+ // should not clip while scroll to the bottom of the list.
+ if (!shouldAllowClosingScroll()) {
+ return false;
+ }
+ float actualNotificationHeight =
+ getLayout().getHeight() - (event1.getRawY() - event2.getRawY());
+ if (actualNotificationHeight > getLayout().getHeight()) {
+ actualNotificationHeight = getLayout().getHeight();
+ }
+ if (getLayout().getHeight() > 0) {
+ mPercentageFromBottom = (int) Math.abs(
+ actualNotificationHeight / getLayout().getHeight() * 100);
+ boolean isUp = distanceY > 0;
+
+ // This check is to figure out if onScroll was called while swiping the card at
+ // bottom of the list. At that time we should not allow notification shade to
+ // close. We are also checking for the upwards swipe gesture here because it is
+ // possible if a user is closing the notification shade and while swiping starts
+ // to open again but does not fling. At that time we should allow the
+ // notification shade to close fully or else it would stuck in between.
+ if (Math.abs(getLayout().getHeight() - actualNotificationHeight)
+ > SWIPE_DOWN_MIN_DISTANCE && isUp) {
+ setViewClipBounds((int) actualNotificationHeight);
+ mIsTracking = true;
+ } else if (!isUp) {
+ setViewClipBounds((int) actualNotificationHeight);
+ }
+ }
+ // if we return true the items in RV won't be scrollable.
+ return false;
+ }
+
+
+ @Override
+ public boolean onFling(MotionEvent event1, MotionEvent event2,
+ float velocityX, float velocityY) {
+ // should not fling if the touch does not start when view is at the bottom of the list.
+ if (!shouldAllowClosingScroll()) {
+ return false;
+ }
+ if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
+ || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
+ // swipe was not vertical or was not fast enough
+ return false;
+ }
+ boolean isUp = velocityY < 0;
+ if (isUp) {
+ close();
+ return true;
+ } else {
+ // we should close the shade
+ animatePanel(velocityY, false);
+ }
+ return false;
+ }
+
+ protected abstract void close();
+ }
+
+ protected abstract class SystemBarCloseGestureListener extends CloseGestureListener {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ mClosingVelocity = DEFAULT_FLING_VELOCITY;
+ if (isPanelExpanded()) {
+ close();
+ }
+ return super.onSingleTapUp(e);
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
+ float distanceY) {
+ calculatePercentageFromBottom(event2.getRawY());
+ setViewClipBounds((int) event2.getRawY());
+ return true;
+ }
+ }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
index f2748b8..76557fd 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
@@ -20,13 +20,14 @@
import static org.mockito.Mockito.when;
import android.os.Handler;
-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.WindowManager;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarDeviceProvisionedController;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayPanelViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayPanelViewControllerTest.java
new file mode 100644
index 0000000..04f2d06
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayPanelViewControllerTest.java
@@ -0,0 +1,431 @@
+/*
+ * 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.window;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.tests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class OverlayPanelViewControllerTest extends SysuiTestCase {
+ private TestOverlayPanelViewController mOverlayPanelViewController;
+ private ViewGroup mBaseLayout;
+
+ @Mock
+ private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
+ @Mock
+ private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+ @Mock
+ private FlingAnimationUtils mFlingAnimationUtils;
+ @Mock
+ private CarDeviceProvisionedController mCarDeviceProvisionedController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
+ R.layout.overlay_view_controller_test, /* root= */ null);
+
+ when(mFlingAnimationUtilsBuilder.setMaxLengthSeconds(anyFloat())).thenReturn(
+ mFlingAnimationUtilsBuilder);
+ when(mFlingAnimationUtilsBuilder.setSpeedUpFactor(anyFloat())).thenReturn(
+ mFlingAnimationUtilsBuilder);
+ when(mFlingAnimationUtilsBuilder.build()).thenReturn(mFlingAnimationUtils);
+ mOverlayPanelViewController = new TestOverlayPanelViewController(
+ getContext(),
+ getContext().getOrCreateTestableResources().getResources(),
+ R.id.overlay_view_controller_stub,
+ mOverlayViewGlobalStateController,
+ mFlingAnimationUtilsBuilder,
+ mCarDeviceProvisionedController);
+ }
+
+ @Test
+ public void toggle_notInflated_inflates() {
+ assertThat(mOverlayPanelViewController.isInflated()).isFalse();
+
+ mOverlayPanelViewController.toggle();
+
+ verify(mOverlayViewGlobalStateController).inflateView(mOverlayPanelViewController);
+ }
+
+ @Test
+ public void toggle_inflated_doesNotInflate() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ assertThat(mOverlayPanelViewController.isInflated()).isTrue();
+
+ mOverlayPanelViewController.toggle();
+
+ verify(mOverlayViewGlobalStateController, never()).inflateView(mOverlayPanelViewController);
+ }
+
+ @Test
+ public void toggle_notExpanded_panelExpands() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setPanelExpanded(false);
+
+ mOverlayPanelViewController.toggle();
+
+ assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue();
+ }
+
+ @Test
+ public void toggle_expanded_panelCollapses() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setPanelExpanded(true);
+
+ mOverlayPanelViewController.toggle();
+
+ assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
+ }
+
+ @Test
+ public void animateCollapsePanel_shouldNotAnimateCollapsePanel_doesNotCollapse() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setShouldAnimateCollapsePanel(false);
+
+ mOverlayPanelViewController.animateCollapsePanel();
+
+ assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
+ assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse();
+ }
+
+ @Test
+ public void animateCollapsePanel_isNotExpanded_doesNotCollapse() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
+ mOverlayPanelViewController.setPanelExpanded(false);
+
+ mOverlayPanelViewController.animateCollapsePanel();
+
+ assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
+ assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse();
+ }
+
+ @Test
+ public void animateCollapsePanel_isNotVisible_doesNotCollapse() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
+ mOverlayPanelViewController.setPanelExpanded(true);
+ mOverlayPanelViewController.setPanelVisible(false);
+
+ mOverlayPanelViewController.animateCollapsePanel();
+
+ assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
+ assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse();
+ }
+
+ @Test
+ public void animateCollapsePanel_collapses() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
+ mOverlayPanelViewController.setPanelExpanded(true);
+ mOverlayPanelViewController.setPanelVisible(true);
+
+ mOverlayPanelViewController.animateCollapsePanel();
+
+ assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isTrue();
+ }
+
+ @Test
+ public void animateCollapsePanel_removesWindowFocus() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
+ mOverlayPanelViewController.setPanelExpanded(true);
+ mOverlayPanelViewController.setPanelVisible(true);
+
+ mOverlayPanelViewController.animateCollapsePanel();
+
+ verify(mOverlayViewGlobalStateController).setWindowFocusable(false);
+ }
+
+ @Test
+ public void animateExpandPanel_shouldNotAnimateExpandPanel_doesNotExpand() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setShouldAnimateExpandPanel(false);
+
+ mOverlayPanelViewController.animateExpandPanel();
+
+ assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue();
+ assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isFalse();
+ }
+
+ @Test
+ public void animateExpandPanel_userNotSetup_doesNotExpand() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
+ when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(false);
+
+ mOverlayPanelViewController.animateExpandPanel();
+
+ assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue();
+ assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isFalse();
+ }
+
+ @Test
+ public void animateExpandPanel_expands() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
+ when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
+
+ mOverlayPanelViewController.animateExpandPanel();
+
+ assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isTrue();
+ }
+
+ @Test
+ public void animateExpandPanel_setsPanelVisible() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
+ when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
+
+ mOverlayPanelViewController.animateExpandPanel();
+
+ assertThat(mOverlayPanelViewController.isPanelVisible()).isTrue();
+ }
+
+ @Test
+ public void animateExpandPanel_setsPanelExpanded() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
+ when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
+
+ mOverlayPanelViewController.animateExpandPanel();
+
+ assertThat(mOverlayPanelViewController.isPanelExpanded()).isTrue();
+ }
+
+ @Test
+ public void setPanelVisible_setTrue_windowNotVisible_setsWindowVisible() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(false);
+
+ mOverlayPanelViewController.setPanelVisible(true);
+
+ verify(mOverlayViewGlobalStateController).setWindowVisible(true);
+ }
+
+ @Test
+ public void setPanelVisible_setTrue_windowVisible_doesNotSetWindowVisible() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(true);
+
+ mOverlayPanelViewController.setPanelVisible(true);
+
+ verify(mOverlayViewGlobalStateController, never()).setWindowVisible(true);
+ }
+
+ @Test
+ public void setPanelVisible_setTrue_setLayoutVisible() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.getLayout().setVisibility(View.INVISIBLE);
+
+ mOverlayPanelViewController.setPanelVisible(true);
+
+ assertThat(mOverlayPanelViewController.getLayout().getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void setPanelVisible_setTrue_setWindowFocusable() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.setPanelVisible(true);
+
+ verify(mOverlayViewGlobalStateController).setWindowFocusable(true);
+ }
+
+ @Test
+ public void setPanelVisible_setFalse_windowVisible_setsWindowNotVisible() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(true);
+
+ mOverlayPanelViewController.setPanelVisible(false);
+
+ verify(mOverlayViewGlobalStateController).setWindowVisible(false);
+ }
+
+ @Test
+ public void setPanelVisible_setFalse_windowNotVisible_doesNotSetWindowNotVisible() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(false);
+
+ mOverlayPanelViewController.setPanelVisible(false);
+
+ verify(mOverlayViewGlobalStateController, never()).setWindowVisible(false);
+ }
+
+ @Test
+ public void setPanelVisible_setFalse_setLayoutInvisible() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+ mOverlayPanelViewController.getLayout().setVisibility(View.VISIBLE);
+
+ mOverlayPanelViewController.setPanelVisible(false);
+
+ assertThat(mOverlayPanelViewController.getLayout().getVisibility()).isEqualTo(
+ View.INVISIBLE);
+ }
+
+ @Test
+ public void setPanelVisible_setFalse_setWindowNotFocusable() {
+ mOverlayPanelViewController.inflate(mBaseLayout);
+
+ mOverlayPanelViewController.setPanelVisible(false);
+
+ verify(mOverlayViewGlobalStateController).setWindowFocusable(false);
+ }
+
+ @Test
+ public void dragOpenTouchListener_isNotInflated_inflatesView() {
+ when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
+ assertThat(mOverlayPanelViewController.isInflated()).isFalse();
+
+ mOverlayPanelViewController.getDragOpenTouchListener().onTouch(/* v= */ null,
+ MotionEvent.obtain(/* downTime= */ 200, /* eventTime= */ 300,
+ MotionEvent.ACTION_MOVE, /* x= */ 0, /* y= */ 0, /* metaState= */ 0));
+
+ verify(mOverlayViewGlobalStateController).inflateView(mOverlayPanelViewController);
+ }
+
+ private static class TestOverlayPanelViewController extends OverlayPanelViewController {
+
+ private boolean mShouldAnimateCollapsePanel;
+ private boolean mShouldAnimateExpandPanel;
+ private boolean mShouldAllowClosingScroll;
+
+ boolean mOnAnimateCollapsePanelCalled;
+ boolean mAnimateCollapsePanelCalled;
+ boolean mOnAnimateExpandPanelCalled;
+ boolean mAnimateExpandPanelCalled;
+ boolean mOnCollapseAnimationEndCalled;
+ boolean mOnExpandAnimationEndCalled;
+ boolean mOnOpenScrollStartEnd;
+ List<Integer> mOnScrollHeights;
+
+ TestOverlayPanelViewController(
+ Context context,
+ Resources resources,
+ int stubId,
+ OverlayViewGlobalStateController overlayViewGlobalStateController,
+ FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+ CarDeviceProvisionedController carDeviceProvisionedController) {
+ super(context, resources, stubId, overlayViewGlobalStateController,
+ flingAnimationUtilsBuilder,
+ carDeviceProvisionedController);
+
+ mOnScrollHeights = new ArrayList<>();
+ }
+
+ public void setShouldAnimateCollapsePanel(boolean shouldAnimate) {
+ mShouldAnimateCollapsePanel = shouldAnimate;
+ }
+
+ @Override
+ protected boolean shouldAnimateCollapsePanel() {
+ return mShouldAnimateCollapsePanel;
+ }
+
+ @Override
+ protected void animateCollapsePanel() {
+ super.animateCollapsePanel();
+ mAnimateCollapsePanelCalled = true;
+ }
+
+ @Override
+ protected void onAnimateCollapsePanel() {
+ mOnAnimateCollapsePanelCalled = true;
+ }
+
+ public void setShouldAnimateExpandPanel(boolean shouldAnimate) {
+ mShouldAnimateExpandPanel = shouldAnimate;
+ }
+
+ @Override
+ protected boolean shouldAnimateExpandPanel() {
+ return mShouldAnimateExpandPanel;
+ }
+
+ @Override
+ protected void animateExpandPanel() {
+ super.animateExpandPanel();
+ mAnimateExpandPanelCalled = true;
+ }
+
+ @Override
+ protected void onAnimateExpandPanel() {
+ mOnAnimateExpandPanelCalled = true;
+ }
+
+ @Override
+ protected void onCollapseAnimationEnd() {
+ mOnCollapseAnimationEndCalled = true;
+ }
+
+ @Override
+ protected void onExpandAnimationEnd() {
+ mOnExpandAnimationEndCalled = true;
+ }
+
+ @Override
+ protected void onScroll(int height) {
+ mOnScrollHeights.add(height);
+ }
+
+ @Override
+ protected void onOpenScrollStart() {
+ mOnOpenScrollStartEnd = true;
+ }
+
+ public void setShouldAllowClosingScroll(boolean shouldAllow) {
+ mShouldAllowClosingScroll = shouldAllow;
+ }
+
+ @Override
+ protected boolean shouldAllowClosingScroll() {
+ return mShouldAllowClosingScroll;
+ }
+ }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java
index 3be9626..3313261 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java
@@ -43,7 +43,7 @@
@TestableLooper.RunWithLooper
@SmallTest
public class OverlayViewControllerTest extends SysuiTestCase {
- private MockOverlayViewController mOverlayViewController;
+ private TestOverlayViewController mOverlayViewController;
private ViewGroup mBaseLayout;
@Mock
@@ -56,7 +56,7 @@
public void setUp() {
MockitoAnnotations.initMocks(/* testClass= */ this);
- mOverlayViewController = new MockOverlayViewController(R.id.overlay_view_controller_stub,
+ mOverlayViewController = new TestOverlayViewController(R.id.overlay_view_controller_stub,
mOverlayViewGlobalStateController);
mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
@@ -130,12 +130,12 @@
assertThat(mOverlayViewController.mHideInternalCalled).isFalse();
}
- private static class MockOverlayViewController extends OverlayViewController {
+ private static class TestOverlayViewController extends OverlayViewController {
boolean mOnFinishInflateCalled = false;
boolean mShowInternalCalled = false;
boolean mHideInternalCalled = false;
- MockOverlayViewController(int stubId,
+ TestOverlayViewController(int stubId,
OverlayViewGlobalStateController overlayViewGlobalStateController) {
super(stubId, overlayViewGlobalStateController);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index 8aa0aec..a53bc9f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -102,12 +102,11 @@
// Turn off divider
view.findViewById(R.id.two_target_divider).setVisibility(View.INVISIBLE);
- // Enable the icon button when this Entry is a canManageSubscription entry.
+ // Enable the icon button when the help string in this WifiEntry is not null.
final ImageButton imageButton = (ImageButton) view.findViewById(R.id.icon_button);
final ImageView frictionImageView = (ImageView) view.findViewById(
R.id.friction_icon);
- if (mWifiEntry.canManageSubscription() && !mWifiEntry.isSaved()
- && !mWifiEntry.isSubscription()
+ if (mWifiEntry.getHelpUriString() != null
&& mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
final Drawable drawablehelp = getDrawable(R.drawable.ic_help);
drawablehelp.setTintList(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
index a9f31ce..46e699d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
@@ -64,7 +64,7 @@
private static final String MOCK_TITLE = "title";
private static final String MOCK_SUMMARY = "summary";
-
+ private static final String FAKE_URI_STRING = "fakeuri";
@Before
public void setUp() {
@@ -155,22 +155,23 @@
}
@Test
- public void canManageSubscription_shouldSetImageButtonVisible() {
- when(mMockWifiEntry.canManageSubscription()).thenReturn(true);
+ public void notNull_whenGetHelpUriString_shouldSetImageButtonVisible() {
+ when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING);
final WifiEntryPreference pref =
new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
final LayoutInflater inflater = LayoutInflater.from(mContext);
final View view = inflater.inflate(pref.getLayoutResource(), new LinearLayout(mContext),
false);
final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view);
+
pref.onBindViewHolder(holder);
assertThat(view.findViewById(R.id.icon_button).getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
- public void helpButton_whenCanManageSubscription_shouldSetCorrectContentDescription() {
- when(mMockWifiEntry.canManageSubscription()).thenReturn(true);
+ public void helpButton_whenGetHelpUriStringNotNull_shouldSetCorrectContentDescription() {
+ when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING);
final WifiEntryPreference pref =
new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
final LayoutInflater inflater = LayoutInflater.from(mContext);
diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml
index fc3bf94..e5ac5f8 100644
--- a/packages/SystemUI/res/layout/qs_media_panel.xml
+++ b/packages/SystemUI/res/layout/qs_media_panel.xml
@@ -23,7 +23,8 @@
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal|fill_vertical"
- android:padding="16dp"
+ android:paddingTop="@dimen/qs_media_panel_outer_padding"
+ android:paddingBottom="@dimen/qs_media_panel_outer_padding"
android:background="@drawable/qs_media_background"
>
@@ -42,7 +43,9 @@
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="16dp"
+ android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+ android:paddingStart="@dimen/qs_media_panel_outer_padding"
+ android:paddingEnd="16dp"
>
<ImageView
@@ -139,6 +142,7 @@
<!-- Seek Bar -->
<SeekBar
android:id="@+id/media_progress_bar"
+ style="@android:style/Widget.ProgressBar.Horizontal"
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -154,6 +158,9 @@
android:id="@+id/notification_media_progress_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingStart="@dimen/qs_media_panel_outer_padding"
+ android:paddingEnd="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginBottom="10dp"
android:layout_gravity="center"
>
<!-- width is set to "match_parent" to avoid extra layout calls -->
@@ -184,6 +191,8 @@
android:layoutDirection="ltr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingStart="@dimen/qs_media_panel_outer_padding"
+ android:paddingEnd="@dimen/qs_media_panel_outer_padding"
android:gravity="center"
>
<ImageButton
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index bce5fac..344479f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1211,6 +1211,7 @@
<!-- Size of media cards in the QSPanel carousel -->
<dimen name="qs_media_width">350dp</dimen>
<dimen name="qs_media_padding">8dp</dimen>
+ <dimen name="qs_media_panel_outer_padding">16dp</dimen>
<dimen name="qs_media_corner_radius">10dp</dimen>
<dimen name="qs_media_album_size">72dp</dimen>
<dimen name="qs_seamless_icon_size">20dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
index 7eb5a8f..e99245f 100644
--- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -34,6 +34,8 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.util.NoSuchElementException;
+
/**
* Encapsulates all logic for secondary lockscreen state management.
*/
@@ -79,7 +81,9 @@
private final IKeyguardCallback mCallback = new IKeyguardCallback.Stub() {
@Override
public void onDismiss() {
- dismiss(UserHandle.getCallingUserId());
+ mHandler.post(() -> {
+ dismiss(UserHandle.getCallingUserId());
+ });
}
@Override
@@ -91,7 +95,9 @@
if (surfacePackage != null) {
mView.setChildSurfacePackage(surfacePackage);
} else {
- dismiss(KeyguardUpdateMonitor.getCurrentUser());
+ mHandler.post(() -> {
+ dismiss(KeyguardUpdateMonitor.getCurrentUser());
+ });
}
}
};
@@ -122,6 +128,7 @@
// If the remote content is not readied within the timeout period,
// move on without the secondary lockscreen.
dismiss(userId);
+ Log.w(TAG, "Timed out waiting for secondary lockscreen content.");
},
REMOTE_CONTENT_READY_TIMEOUT_MILLIS);
}
@@ -150,8 +157,12 @@
* Displays the Admin security Surface view.
*/
public void show(Intent serviceIntent) {
- mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
- mParent.addView(mView);
+ if (mClient == null) {
+ mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
+ }
+ if (!mView.isAttachedToWindow()) {
+ mParent.addView(mView);
+ }
}
/**
@@ -162,7 +173,11 @@
mParent.removeView(mView);
}
if (mClient != null) {
- mClient.asBinder().unlinkToDeath(mKeyguardClientDeathRecipient, 0);
+ try {
+ mClient.asBinder().unlinkToDeath(mKeyguardClientDeathRecipient, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "IKeyguardClient death recipient already released");
+ }
mContext.unbindService(mConnection);
mClient = null;
}
@@ -185,10 +200,12 @@
private void dismiss(int userId) {
mHandler.removeCallbacksAndMessages(null);
- if (mView != null && mView.isAttachedToWindow()
- && userId == KeyguardUpdateMonitor.getCurrentUser()) {
+ if (mView.isAttachedToWindow() && userId == KeyguardUpdateMonitor.getCurrentUser()) {
hide();
- mKeyguardCallback.dismiss(true, userId);
+ if (mKeyguardCallback != null) {
+ mKeyguardCallback.dismiss(/* securityVerified= */ true, userId,
+ /* bypassSecondaryLockScreen= */true);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index d5a08dd..aa2fe3c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -85,7 +85,8 @@
// the user proved presence via some other way to the trust agent.
Log.i(TAG, "TrustAgent dismissed Keyguard.");
}
- dismiss(false /* authenticated */, userId);
+ dismiss(false /* authenticated */, userId,
+ /* bypassSecondaryLockScreen */ false);
} else {
mViewMediatorCallback.playTrustedSound();
}
@@ -190,7 +191,7 @@
* @return True if the keyguard is done.
*/
public boolean dismiss(int targetUserId) {
- return dismiss(false, targetUserId);
+ return dismiss(false, targetUserId, false);
}
public boolean handleBackKey() {
@@ -206,8 +207,10 @@
}
@Override
- public boolean dismiss(boolean authenticated, int targetUserId) {
- return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId);
+ public boolean dismiss(boolean authenticated, int targetUserId,
+ boolean bypassSecondaryLockScreen) {
+ return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId,
+ bypassSecondaryLockScreen);
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
index 49dcfff..e384727 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
@@ -25,6 +25,15 @@
void dismiss(boolean securityVerified, int targetUserId);
/**
+ * Dismiss the given security screen.
+ * @param securityVerified true if the user correctly entered credentials for the given screen.
+ * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
+ * @param bypassSecondaryLockScreen true if the user can bypass the secondary lock screen,
+ * if any, during this dismissal.
+ */
+ void dismiss(boolean securityVerified, int targetUserId, boolean bypassSecondaryLockScreen);
+
+ /**
* Manually report user activity to keep the device awake.
*/
void userActivity();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index ba8a1a9..1e1ce4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -115,7 +115,8 @@
// Used to notify the container when something interesting happens.
public interface SecurityCallback {
- public boolean dismiss(boolean authenticated, int targetUserId);
+ public boolean dismiss(boolean authenticated, int targetUserId,
+ boolean bypassSecondaryLockScreen);
public void userActivity();
public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
@@ -504,9 +505,12 @@
* @param authenticated true if the user entered the correct authentication
* @param targetUserId a user that needs to be the foreground user at the finish (if called)
* completion.
+ * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary
+ * secondary lock screen requirement, if any.
* @return true if keyguard is done
*/
- boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId) {
+ boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
+ boolean bypassSecondaryLockScreen) {
if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
boolean finish = false;
boolean strongAuth = false;
@@ -555,7 +559,7 @@
}
}
// Check for device admin specified additional security measures.
- if (finish) {
+ if (finish && !bypassSecondaryLockScreen) {
Intent secondaryLockscreenIntent =
mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId);
if (secondaryLockscreenIntent != null) {
@@ -636,8 +640,15 @@
mUpdateMonitor.cancelFaceAuth();
}
+ @Override
public void dismiss(boolean authenticated, int targetId) {
- mSecurityCallback.dismiss(authenticated, targetId);
+ dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false);
+ }
+
+ @Override
+ public void dismiss(boolean authenticated, int targetId,
+ boolean bypassSecondaryLockScreen) {
+ mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen);
}
public boolean isVerifyUnlockOnly() {
@@ -689,6 +700,9 @@
@Override
public void dismiss(boolean securityVerified, int targetUserId) { }
@Override
+ public void dismiss(boolean authenticated, int targetId,
+ boolean bypassSecondaryLockScreen) { }
+ @Override
public void onUserInput() { }
@Override
public void reset() {}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
index 362014f..e17d4e6 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.time.SystemClock;
/**
* Extends the lifetime of foreground notification services such that they show for at least
@@ -39,8 +40,10 @@
private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback;
private ArraySet<NotificationEntry> mManagedEntries = new ArraySet<>();
private Handler mHandler = new Handler(Looper.getMainLooper());
+ private final SystemClock mSystemClock;
- public ForegroundServiceLifetimeExtender() {
+ public ForegroundServiceLifetimeExtender(SystemClock systemClock) {
+ mSystemClock = systemClock;
}
@Override
@@ -55,8 +58,8 @@
return false;
}
- long currentTime = System.currentTimeMillis();
- return currentTime - entry.getSbn().getPostTime() < MIN_FGS_TIME_MS;
+ long currentTime = mSystemClock.uptimeMillis();
+ return currentTime - entry.getCreationTime() < MIN_FGS_TIME_MS;
}
@Override
@@ -84,7 +87,7 @@
}
};
long delayAmt = MIN_FGS_TIME_MS
- - (System.currentTimeMillis() - entry.getSbn().getPostTime());
+ - (mSystemClock.uptimeMillis() - entry.getCreationTime());
mHandler.postDelayed(r, delayAmt);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index f01fa81..ef1f4e0 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.util.time.SystemClock;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -49,7 +50,8 @@
public ForegroundServiceNotificationListener(Context context,
ForegroundServiceController foregroundServiceController,
NotificationEntryManager notificationEntryManager,
- NotifPipeline notifPipeline) {
+ NotifPipeline notifPipeline,
+ SystemClock systemClock) {
mContext = context;
mForegroundServiceController = foregroundServiceController;
@@ -76,7 +78,8 @@
removeNotification(entry.getSbn());
}
});
- mEntryManager.addNotificationLifetimeExtender(new ForegroundServiceLifetimeExtender());
+ mEntryManager.addNotificationLifetimeExtender(
+ new ForegroundServiceLifetimeExtender(systemClock));
notifPipeline.addCollectionListener(new NotifCollectionListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 9d885fd..99e5eb6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -719,13 +719,6 @@
}
/**
- * Tell the stack of bubbles to expand.
- */
- public void expandStack() {
- mBubbleData.setExpanded(true);
- }
-
- /**
* Tell the stack of bubbles to collapse.
*/
public void collapseStack() {
@@ -753,12 +746,6 @@
return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
}
- @VisibleForTesting
- void selectBubble(String key) {
- Bubble bubble = mBubbleData.getBubbleWithKey(key);
- mBubbleData.setSelectedBubble(bubble);
- }
-
void promoteBubbleFromOverflow(Bubble bubble) {
bubble.setInflateSynchronously(mInflateSynchronously);
mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
@@ -778,13 +765,6 @@
}
/**
- * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
- */
- void dismissStack(@DismissReason int reason) {
- mBubbleData.dismissAll(reason);
- }
-
- /**
* Directs a back gesture at the bubble stack. When opened, the current expanded bubble
* is forwarded a back key down/up pair.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index be9cd5f..4c149dd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -28,6 +28,7 @@
import android.service.notification.NotificationListenerService;
import android.util.Log;
import android.util.Pair;
+import android.view.View;
import androidx.annotation.Nullable;
@@ -751,6 +752,7 @@
}
@VisibleForTesting(visibility = PRIVATE)
+ @Nullable
Bubble getBubbleWithKey(String key) {
for (int i = 0; i < mBubbles.size(); i++) {
Bubble bubble = mBubbles.get(i);
@@ -761,6 +763,17 @@
return null;
}
+ @Nullable
+ Bubble getBubbleWithView(View view) {
+ for (int i = 0; i < mBubbles.size(); i++) {
+ Bubble bubble = mBubbles.get(i);
+ if (bubble.getIconView() != null && bubble.getIconView().equals(view)) {
+ return bubble;
+ }
+ }
+ return null;
+ }
+
@VisibleForTesting(visibility = PRIVATE)
Bubble getOverflowBubbleWithKey(String key) {
for (int i = 0; i < mOverflowBubbles.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 496456d..93fb697 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -16,6 +16,7 @@
package com.android.systemui.bubbles;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.view.Display.INVALID_DISPLAY;
@@ -126,6 +127,7 @@
ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
0 /* enterResId */, 0 /* exitResId */);
options.setTaskAlwaysOnTop(true);
+ options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
// Post to keep the lifecycle normal
post(() -> {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 044feaa..644e54f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -30,6 +30,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
@@ -43,6 +44,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.Region;
import android.os.Bundle;
import android.os.Vibrator;
import android.util.Log;
@@ -83,6 +85,7 @@
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.util.DismissCircleView;
import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.systemui.util.RelativeTouchListener;
import com.android.systemui.util.animation.PhysicsAnimator;
import com.android.systemui.util.magnetictarget.MagnetizedObject;
@@ -239,7 +242,6 @@
mExpandedAnimationController.dump(fd, pw, args);
}
- private BubbleTouchHandler mTouchHandler;
private BubbleController.BubbleExpandListener mExpandListener;
private SysUiState mSysUiState;
@@ -296,7 +298,7 @@
@Override
public void setValue(Object o, float v) {
- onFlyoutDragged(v);
+ setFlyoutStateForDragLength(v);
}
};
@@ -337,13 +339,6 @@
private MagnetizedObject<?> mMagnetizedObject;
/**
- * The action to run when the magnetized object is released in the dismiss target.
- *
- * This will actually perform the dismissal of either the stack or an individual bubble.
- */
- private Runnable mReleasedInDismissTargetAction;
-
- /**
* The MagneticTarget instance for our circular dismiss view. This is added to the
* MagnetizedObject instances for the stack and any dragged-out bubbles.
*/
@@ -377,7 +372,7 @@
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
mExpandedAnimationController.dismissDraggedOutBubble(
mExpandedAnimationController.getDraggedOutBubble(),
- mReleasedInDismissTargetAction);
+ BubbleStackView.this::dismissMagnetizedObject);
hideDismissTarget();
}
};
@@ -410,7 +405,7 @@
mStackAnimationController.implodeStack(
() -> {
resetDesaturationAndDarken();
- mReleasedInDismissTargetAction.run();
+ dismissMagnetizedObject();
}
);
@@ -418,6 +413,197 @@
}
};
+ /**
+ * Click listener set on each bubble view. When collapsed, clicking a bubble expands the stack.
+ * When expanded, clicking a bubble either expands that bubble, or collapses the stack.
+ */
+ private OnClickListener mBubbleClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ final Bubble clickedBubble = mBubbleData.getBubbleWithView(view);
+
+ // If the bubble has since left us, ignore the click.
+ if (clickedBubble == null) {
+ return;
+ }
+
+ final boolean clickedBubbleIsCurrentlyExpandedBubble =
+ clickedBubble.getKey().equals(mExpandedBubble.getKey());
+
+ if (isExpanded() && !clickedBubbleIsCurrentlyExpandedBubble) {
+ if (clickedBubble != mBubbleData.getSelectedBubble()) {
+ // Select the clicked bubble.
+ mBubbleData.setSelectedBubble(clickedBubble);
+ } else {
+ // If the clicked bubble is the selected bubble (but not the expanded bubble),
+ // that means overflow was previously expanded. Set the selected bubble
+ // internally without going through BubbleData (which would ignore it since it's
+ // already selected).
+ setSelectedBubble(clickedBubble);
+
+ }
+ } else {
+ // Otherwise, we either tapped the stack (which means we're collapsed
+ // and should expand) or the currently selected bubble (we're expanded
+ // and should collapse).
+ if (!maybeShowStackUserEducation()) {
+ mBubbleData.setExpanded(!mBubbleData.isExpanded());
+ }
+ }
+ }
+ };
+
+ /**
+ * Touch listener set on each bubble view. This enables dragging and dismissing the stack (when
+ * collapsed), or individual bubbles (when expanded).
+ */
+ private RelativeTouchListener mBubbleTouchListener = new RelativeTouchListener() {
+
+ @Override
+ public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
+ // If we're expanding or collapsing, consume but ignore all touch events.
+ if (mIsExpansionAnimating) {
+ return true;
+ }
+
+ if (mBubbleData.isExpanded()) {
+ maybeShowManageEducation(false /* show */);
+
+ // If we're expanded, tell the animation controller to prepare to drag this bubble,
+ // dispatching to the individual bubble magnet listener.
+ mExpandedAnimationController.prepareForBubbleDrag(
+ v /* bubble */,
+ mMagneticTarget,
+ mIndividualBubbleMagnetListener);
+
+ // Save the magnetized individual bubble so we can dispatch touch events to it.
+ mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
+ } else {
+ // If we're collapsed, prepare to drag the stack. Cancel active animations, set the
+ // animation controller, and hide the flyout.
+ mStackAnimationController.cancelStackPositionAnimations();
+ mBubbleContainer.setActiveController(mStackAnimationController);
+ hideFlyoutImmediate();
+
+ // Also, save the magnetized stack so we can dispatch touch events to it.
+ mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
+ mMagnetizedObject.setMagnetListener(mStackMagnetListener);
+ }
+
+ passEventToMagnetizedObject(ev);
+
+ // Bubbles are always interested in all touch events!
+ return true;
+ }
+
+ @Override
+ public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+ float viewInitialY, float dx, float dy) {
+ // If we're expanding or collapsing, ignore all touch events.
+ if (mIsExpansionAnimating) {
+ return;
+ }
+
+ // Show the dismiss target, if we haven't already.
+ springInDismissTargetMaybe();
+
+ // First, see if the magnetized object consumes the event - if so, we shouldn't move the
+ // bubble since it's stuck to the target.
+ if (!passEventToMagnetizedObject(ev)) {
+ if (mBubbleData.isExpanded()) {
+ mExpandedAnimationController.dragBubbleOut(
+ v, viewInitialX + dx, viewInitialY + dy);
+ } else {
+ hideStackUserEducation(false /* fromExpansion */);
+ mStackAnimationController.moveStackFromTouch(
+ viewInitialX + dx, viewInitialY + dy);
+ }
+ }
+ }
+
+ @Override
+ public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+ float viewInitialY, float dx, float dy, float velX, float velY) {
+ // If we're expanding or collapsing, ignore all touch events.
+ if (mIsExpansionAnimating) {
+ return;
+ }
+
+ // First, see if the magnetized object consumes the event - if so, the bubble was
+ // released in the target or flung out of it, and we should ignore the event.
+ if (!passEventToMagnetizedObject(ev)) {
+ if (mBubbleData.isExpanded()) {
+ mExpandedAnimationController.snapBubbleBack(v, velX, velY);
+ } else {
+ // Fling the stack to the edge, and save whether or not it's going to end up on
+ // the left side of the screen.
+ mStackOnLeftOrWillBe =
+ mStackAnimationController.flingStackThenSpringToEdge(
+ viewInitialX + dx, velX, velY) <= 0;
+
+ updateBubbleZOrdersAndDotPosition(true /* animate */);
+
+ logBubbleEvent(null /* no bubble associated with bubble stack move */,
+ SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
+ }
+
+ hideDismissTarget();
+ }
+ }
+ };
+
+ /** Click listener set on the flyout, which expands the stack when the flyout is tapped. */
+ private OnClickListener mFlyoutClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (maybeShowStackUserEducation()) {
+ // If we're showing user education, don't open the bubble show the education first
+ mBubbleToExpandAfterFlyoutCollapse = null;
+ } else {
+ mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
+ }
+
+ mFlyout.removeCallbacks(mHideFlyout);
+ mHideFlyout.run();
+ }
+ };
+
+ /** Touch listener for the flyout. This enables the drag-to-dismiss gesture on the flyout. */
+ private RelativeTouchListener mFlyoutTouchListener = new RelativeTouchListener() {
+
+ @Override
+ public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
+ mFlyout.removeCallbacks(mHideFlyout);
+ return true;
+ }
+
+ @Override
+ public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+ float viewInitialY, float dx, float dy) {
+ setFlyoutStateForDragLength(dx);
+ }
+
+ @Override
+ public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+ float viewInitialY, float dx, float dy, float velX, float velY) {
+ final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+ final boolean metRequiredVelocity =
+ onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY;
+ final boolean metRequiredDeltaX =
+ onLeft
+ ? dx < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS
+ : dx > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS;
+ final boolean isCancelFling = onLeft ? velX > 0 : velX < 0;
+ final boolean shouldDismiss = metRequiredVelocity
+ || (metRequiredDeltaX && !isCancelFling);
+
+ mFlyout.removeCallbacks(mHideFlyout);
+ animateFlyoutCollapsed(shouldDismiss, velX);
+
+ maybeShowStackUserEducation();
+ }
+ };
+
private ViewGroup mDismissTargetContainer;
private PhysicsAnimator<View> mDismissTargetAnimator;
private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig(
@@ -436,6 +622,7 @@
private BubbleManageEducationView mManageEducationView;
private boolean mAnimatingManageEducationAway;
+ @SuppressLint("ClickableViewAccessibility")
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
@@ -444,8 +631,6 @@
mBubbleData = data;
mInflater = LayoutInflater.from(context);
- mTouchHandler = new BubbleTouchHandler(this, data, context);
- setOnTouchListener(mTouchHandler);
mSysUiState = sysUiState;
@@ -641,6 +826,18 @@
mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix));
mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint);
});
+
+ // If the stack itself is touched, it means none of its touchable views (bubbles, flyouts,
+ // ActivityViews, etc.) were touched. Collapse the stack if it's expanded.
+ setOnTouchListener((view, ev) -> {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ if (mBubbleData.isExpanded()) {
+ mBubbleData.setExpanded(false);
+ }
+ }
+
+ return false;
+ });
}
private void setUpUserEducation() {
@@ -690,6 +887,7 @@
}
}
+ @SuppressLint("ClickableViewAccessibility")
private void setUpFlyout() {
if (mFlyout != null) {
removeView(mFlyout);
@@ -699,6 +897,8 @@
mFlyout.animate()
.setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
.setInterpolator(new AccelerateDecelerateInterpolator());
+ mFlyout.setOnClickListener(mFlyoutClickListener);
+ mFlyout.setOnTouchListener(mFlyoutTouchListener);
addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
}
@@ -718,6 +918,7 @@
mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex,
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ mBubbleOverflow.getBtn().setOnClickListener(v -> setSelectedBubble(mBubbleOverflow));
}
/**
* Handle theme changes.
@@ -920,6 +1121,7 @@
}
// via BubbleData.Listener
+ @SuppressLint("ClickableViewAccessibility")
void addBubble(Bubble bubble) {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "addBubble: " + bubble);
@@ -944,6 +1146,9 @@
bubble.getIconView().setDotPositionOnLeft(
!mStackOnLeftOrWillBe /* onLeft */, false /* animate */);
+ bubble.getIconView().setOnClickListener(mBubbleClickListener);
+ bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
+
mBubbleContainer.addView(bubble.getIconView(), 0,
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters);
@@ -1009,10 +1214,6 @@
updatePointerPosition();
}
- void showOverflow() {
- setSelectedBubble(mBubbleOverflow);
- }
-
/**
* Changes the currently selected bubble. If the stack is already expanded, the newly selected
* bubble will be shown immediately. This does not change the expanded state or change the
@@ -1177,14 +1378,6 @@
}
}
- /*
- * Sets the action to run to dismiss the currently dragging object (either the stack or an
- * individual bubble).
- */
- public void setReleasedInDismissTargetAction(Runnable action) {
- mReleasedInDismissTargetAction = action;
- }
-
/**
* Dismiss the stack of bubbles.
*
@@ -1201,54 +1394,6 @@
}
/**
- * @return the view the touch event is on
- */
- @Nullable
- public View getTargetView(MotionEvent event) {
- float x = event.getRawX();
- float y = event.getRawY();
- if (mIsExpanded) {
- if (isIntersecting(mBubbleContainer, x, y)) {
- if (BubbleExperimentConfig.allowBubbleOverflow(mContext)
- && isIntersecting(mBubbleOverflow.getBtn(), x, y)) {
- return mBubbleOverflow.getBtn();
- }
- // Could be tapping or dragging a bubble while expanded
- for (int i = 0; i < getBubbleCount(); i++) {
- BadgedImageView view = (BadgedImageView) mBubbleContainer.getChildAt(i);
- if (isIntersecting(view, x, y)) {
- return view;
- }
- }
- }
- BubbleExpandedView bev = (BubbleExpandedView) mExpandedViewContainer.getChildAt(0);
- if (bev.intersectingTouchableContent((int) x, (int) y)) {
- return bev;
- }
- // Outside of the parts we care about.
- return null;
- } else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
- return mFlyout;
- } else if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) {
- View bubbleChild = mBubbleContainer.getChildAt(0);
- if (isIntersecting(bubbleChild, x, y)) {
- return this;
- } else if (isIntersecting(mUserEducationView, x, y)) {
- return mUserEducationView;
- } else {
- return null;
- }
- }
-
- // If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
- return this;
- }
-
- View getFlyoutView() {
- return mFlyout;
- }
-
- /**
* @deprecated use {@link #setExpanded(boolean)} and
* {@link BubbleData#setSelectedBubble(Bubble)}
*/
@@ -1385,124 +1530,70 @@
}
}
- /** Called when the collapsed stack is tapped on. */
- void onStackTapped() {
- if (!maybeShowStackUserEducation()) {
- mBubbleData.setExpanded(true);
- }
+ /**
+ * This method is called by {@link android.app.ActivityView} because the BubbleStackView has a
+ * higher Z-index than the ActivityView (so that dragged-out bubbles are visible over the AV).
+ * ActivityView is asking BubbleStackView to subtract the stack's bounds from the provided
+ * touchable region, so that the ActivityView doesn't consume events meant for the stack. Due to
+ * the special nature of ActivityView, it does not respect the standard
+ * {@link #dispatchTouchEvent} and {@link #onInterceptTouchEvent} methods typically used for
+ * this purpose.
+ *
+ * BubbleStackView is MATCH_PARENT, so that bubbles can be positioned via their translation
+ * properties for performance reasons. This means that the default implementation of this method
+ * subtracts the entirety of the screen from the ActivityView's touchable region, resulting in
+ * it not receiving any touch events. This was previously addressed by returning false in the
+ * stack's {@link View#canReceivePointerEvents()} method, but this precluded the use of any
+ * touch handlers in the stack or its child views.
+ *
+ * To support touch handlers, we're overriding this method to leave the ActivityView's touchable
+ * region alone. The only touchable part of the stack that can ever overlap the AV is a
+ * dragged-out bubble that is animating back into the row of bubbles. It's not worth continually
+ * updating the touchable region to allow users to grab a bubble while it completes its ~50ms
+ * animation back to the bubble row.
+ *
+ * NOTE: Any future additions to the stack that obscure the ActivityView region will need their
+ * bounds subtracted here in order to receive touch events.
+ */
+ @Override
+ public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
+
}
- /** Called when a drag operation on an individual bubble has started. */
- public void onBubbleDragStart(View bubble) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "onBubbleDragStart: bubble=" + ((BadgedImageView) bubble).getKey());
- }
-
- if (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView())) {
- return;
- }
-
- mExpandedAnimationController.prepareForBubbleDrag(bubble, mMagneticTarget);
-
- // We're dragging an individual bubble, so set the magnetized object to the magnetized
- // bubble.
- mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
- mMagnetizedObject.setMagnetListener(mIndividualBubbleMagnetListener);
-
- maybeShowManageEducation(false);
+ /**
+ * If you're here because you're not receiving touch events on a view that is a descendant of
+ * BubbleStackView, and you think BSV is intercepting them - it's not! You need to subtract the
+ * bounds of the view in question in {@link #subtractObscuredTouchableRegion}. The ActivityView
+ * consumes all touch events within its bounds, even for views like the BubbleStackView that are
+ * above it. It ignores typical view touch handling methods like this one and
+ * dispatchTouchEvent.
+ */
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ return super.onInterceptTouchEvent(ev);
}
- /** Called with the coordinates to which an individual bubble has been dragged. */
- public void onBubbleDragged(View bubble, float x, float y) {
- if (!mIsExpanded || mIsExpansionAnimating
- || (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView()))) {
- return;
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ boolean dispatched = super.dispatchTouchEvent(ev);
+
+ // If a new bubble arrives while the collapsed stack is being dragged, it will be positioned
+ // at the front of the stack (under the touch position). Subsequent ACTION_MOVE events will
+ // then be passed to the new bubble, which will not consume them since it hasn't received an
+ // ACTION_DOWN yet. Work around this by passing MotionEvents directly to the touch handler
+ // until the current gesture ends with an ACTION_UP event.
+ if (!dispatched && !mIsExpanded && mIsGestureInProgress) {
+ dispatched = mBubbleTouchListener.onTouch(this /* view */, ev);
}
- mExpandedAnimationController.dragBubbleOut(bubble, x, y);
- springInDismissTarget();
+ mIsGestureInProgress =
+ ev.getAction() != MotionEvent.ACTION_UP
+ && ev.getAction() != MotionEvent.ACTION_CANCEL;
+
+ return dispatched;
}
- /** Called when a drag operation on an individual bubble has finished. */
- public void onBubbleDragFinish(
- View bubble, float x, float y, float velX, float velY) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble);
- }
-
- if (!mIsExpanded || mIsExpansionAnimating
- || (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView()))) {
- return;
- }
-
- mExpandedAnimationController.snapBubbleBack(bubble, velX, velY);
- hideDismissTarget();
- }
-
- /** Expands the clicked bubble. */
- public void expandBubble(Bubble bubble) {
- if (bubble != null && bubble.equals(mBubbleData.getSelectedBubble())) {
- // If the bubble we're supposed to expand is the selected bubble, that means the
- // overflow bubble is currently expanded. Don't tell BubbleData to set this bubble as
- // selected, since it already is. Just call the stack's setSelectedBubble to expand it.
- setSelectedBubble(bubble);
- } else {
- mBubbleData.setSelectedBubble(bubble);
- }
- }
-
- void onDragStart() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "onDragStart()");
- }
- if (mIsExpanded || mIsExpansionAnimating) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "mIsExpanded or mIsExpansionAnimating");
- }
- return;
- }
- mStackAnimationController.cancelStackPositionAnimations();
- mBubbleContainer.setActiveController(mStackAnimationController);
- hideFlyoutImmediate();
-
- // Since we're dragging the stack, set the magnetized object to the magnetized stack.
- mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mStackMagnetListener);
- }
-
- void onDragged(float x, float y) {
- if (mIsExpanded || mIsExpansionAnimating) {
- return;
- }
-
- hideStackUserEducation(false /* fromExpansion */);
- springInDismissTarget();
- mStackAnimationController.moveStackFromTouch(x, y);
- }
-
- void onDragFinish(float x, float y, float velX, float velY) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "onDragFinish");
- }
-
- if (mIsExpanded || mIsExpansionAnimating) {
- return;
- }
-
- final float newStackX = mStackAnimationController.flingStackThenSpringToEdge(x, velX, velY);
- logBubbleEvent(null /* no bubble associated with bubble stack move */,
- SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
-
- mStackOnLeftOrWillBe = newStackX <= 0;
- updateBubbleZOrdersAndDotPosition(true /* animate */);
- hideDismissTarget();
- }
-
- void onFlyoutDragStart() {
- mFlyout.removeCallbacks(mHideFlyout);
- }
-
- void onFlyoutDragged(float deltaX) {
+ void setFlyoutStateForDragLength(float deltaX) {
// This shouldn't happen, but if it does, just wait until the flyout lays out. This method
// is continually called.
if (mFlyout.getWidth() <= 0) {
@@ -1538,61 +1629,29 @@
mFlyout.setTranslationX(mFlyout.getRestingTranslationX() + overscrollTranslation);
}
- void onFlyoutTapped() {
- if (maybeShowStackUserEducation()) {
- // If we're showing user education, don't open the bubble show the education first
- mBubbleToExpandAfterFlyoutCollapse = null;
- } else {
- mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
- }
-
- mFlyout.removeCallbacks(mHideFlyout);
- mHideFlyout.run();
- }
-
- /**
- * Called when the flyout drag has finished, and returns true if the gesture successfully
- * dismissed the flyout.
- */
- void onFlyoutDragFinished(float deltaX, float velX) {
- final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
- final boolean metRequiredVelocity =
- onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY;
- final boolean metRequiredDeltaX =
- onLeft
- ? deltaX < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS
- : deltaX > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS;
- final boolean isCancelFling = onLeft ? velX > 0 : velX < 0;
- final boolean shouldDismiss = metRequiredVelocity || (metRequiredDeltaX && !isCancelFling);
-
- mFlyout.removeCallbacks(mHideFlyout);
- animateFlyoutCollapsed(shouldDismiss, velX);
-
- maybeShowStackUserEducation();
- }
-
- /**
- * Called when the first touch event of a gesture (stack drag, bubble drag, flyout drag, etc.)
- * is received.
- */
- void onGestureStart() {
- mIsGestureInProgress = true;
- }
-
- /** Called when a gesture is completed or cancelled. */
- void onGestureFinished() {
- mIsGestureInProgress = false;
-
- if (mIsExpanded) {
- mExpandedAnimationController.onGestureFinished();
- }
- }
-
/** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
- boolean passEventToMagnetizedObject(MotionEvent event) {
+ private boolean passEventToMagnetizedObject(MotionEvent event) {
return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
}
+ /**
+ * Dismisses the magnetized object - either an individual bubble, if we're expanded, or the
+ * stack, if we're collapsed.
+ */
+ private void dismissMagnetizedObject() {
+ if (mIsExpanded) {
+ final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
+ final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView);
+
+ if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) {
+ mBubbleData.notificationEntryRemoved(
+ draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ }
+ } else {
+ mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
+ }
+ }
+
/** Prepares and starts the desaturate/darken animation on the bubble stack. */
private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
mDesaturateAndDarkenTargetView = targetView;
@@ -1624,7 +1683,7 @@
}
/** Animates in the dismiss target. */
- private void springInDismissTarget() {
+ private void springInDismissTargetMaybe() {
if (mShowingDismiss) {
return;
}
@@ -1827,13 +1886,6 @@
return 0;
}
- private boolean isIntersecting(View view, float x, float y) {
- mTempLoc = view.getLocationOnScreen();
- mTempRect.set(mTempLoc[0], mTempLoc[1], mTempLoc[0] + view.getWidth(),
- mTempLoc[1] + view.getHeight());
- return mTempRect.contains(x, y);
- }
-
private void requestUpdate() {
if (mViewUpdatedRequested || mIsExpansionAnimating) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
deleted file mode 100644
index 132c45f..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2012 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;
-
-import android.content.Context;
-import android.graphics.PointF;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.systemui.Dependency;
-
-/**
- * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
- * dismissing, and flings.
- */
-class BubbleTouchHandler implements View.OnTouchListener {
-
- private final PointF mTouchDown = new PointF();
- private final PointF mViewPositionOnTouchDown = new PointF();
- private final BubbleStackView mStack;
- private final BubbleData mBubbleData;
-
- private BubbleController mController = Dependency.get(BubbleController.class);
-
- private boolean mMovedEnough;
- private int mTouchSlopSquared;
- private VelocityTracker mVelocityTracker;
-
- /** View that was initially touched, when we received the first ACTION_DOWN event. */
- private View mTouchedView;
-
- BubbleTouchHandler(BubbleStackView stackView,
- BubbleData bubbleData, Context context) {
- final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
- mTouchSlopSquared = touchSlop * touchSlop;
- mBubbleData = bubbleData;
- mStack = stackView;
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- final int action = event.getActionMasked();
-
- // If we aren't currently in the process of touching a view, figure out what we're touching.
- // It'll be the stack, an individual bubble, or nothing.
- if (mTouchedView == null) {
- mTouchedView = mStack.getTargetView(event);
- }
-
- // If this is an ACTION_OUTSIDE event, or the stack reported that we aren't touching
- // anything, collapse the stack.
- if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) {
- mBubbleData.setExpanded(false);
- mStack.hideStackUserEducation(false /* fromExpansion */);
- resetForNextGesture();
- return false;
- }
-
- if (!(mTouchedView instanceof BadgedImageView)
- && !(mTouchedView instanceof BubbleStackView)
- && !(mTouchedView instanceof BubbleFlyoutView)) {
-
- // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
- // of expanded view).
- mStack.maybeShowManageEducation(false);
- resetForNextGesture();
- return false;
- }
-
- final boolean isStack = mStack.equals(mTouchedView);
- final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView);
- final float rawX = event.getRawX();
- final float rawY = event.getRawY();
-
- // The coordinates of the touch event, in terms of the touched view's position.
- final float viewX = mViewPositionOnTouchDown.x + rawX - mTouchDown.x;
- final float viewY = mViewPositionOnTouchDown.y + rawY - mTouchDown.y;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- trackMovement(event);
-
- mTouchDown.set(rawX, rawY);
- mStack.onGestureStart();
-
- if (isStack) {
- mViewPositionOnTouchDown.set(mStack.getStackPosition());
-
- // Dismiss the entire stack if it's released in the dismiss target.
- mStack.setReleasedInDismissTargetAction(
- () -> mController.dismissStack(BubbleController.DISMISS_USER_GESTURE));
- mStack.onDragStart();
- mStack.passEventToMagnetizedObject(event);
- } else if (isFlyout) {
- mStack.onFlyoutDragStart();
- } else {
- mViewPositionOnTouchDown.set(
- mTouchedView.getTranslationX(), mTouchedView.getTranslationY());
-
- // Dismiss only the dragged-out bubble if it's released in the target.
- final String individualBubbleKey = ((BadgedImageView) mTouchedView).getKey();
- mStack.setReleasedInDismissTargetAction(() -> {
- final Bubble bubble =
- mBubbleData.getBubbleWithKey(individualBubbleKey);
- // bubble can be null if the user is in the middle of
- // dismissing the bubble, but the app also sent a cancel
- if (bubble != null) {
- mController.removeBubble(bubble.getEntry(),
- BubbleController.DISMISS_USER_GESTURE);
- }
- });
-
- mStack.onBubbleDragStart(mTouchedView);
- mStack.passEventToMagnetizedObject(event);
- }
-
- break;
- case MotionEvent.ACTION_MOVE:
- trackMovement(event);
- final float deltaX = rawX - mTouchDown.x;
- final float deltaY = rawY - mTouchDown.y;
-
- if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) {
- mMovedEnough = true;
- }
-
- if (mMovedEnough) {
- if (isFlyout) {
- mStack.onFlyoutDragged(deltaX);
- } else if (!mStack.passEventToMagnetizedObject(event)) {
- // If the magnetic target doesn't consume the event, drag the stack or
- // bubble.
- if (isStack) {
- mStack.onDragged(viewX, viewY);
- } else {
- mStack.onBubbleDragged(mTouchedView, viewX, viewY);
- }
- }
- }
- break;
-
- case MotionEvent.ACTION_CANCEL:
- resetForNextGesture();
- break;
-
- case MotionEvent.ACTION_UP:
- trackMovement(event);
- mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
- final float velX = mVelocityTracker.getXVelocity();
- final float velY = mVelocityTracker.getYVelocity();
-
- if (isFlyout && mMovedEnough) {
- mStack.onFlyoutDragFinished(rawX - mTouchDown.x /* deltaX */, velX);
- } else if (isFlyout) {
- if (!mBubbleData.isExpanded() && !mMovedEnough) {
- mStack.onFlyoutTapped();
- }
- } else if (mMovedEnough) {
- if (!mStack.passEventToMagnetizedObject(event)) {
- // If the magnetic target didn't consume the event, tell the stack to finish
- // the drag.
- if (isStack) {
- mStack.onDragFinish(viewX, viewY, velX, velY);
- } else {
- mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY);
- }
- }
- } else if (mTouchedView == mStack.getExpandedBubbleView()) {
- mBubbleData.setExpanded(false);
- } else if (isStack) {
- mStack.onStackTapped();
- } else {
- final String key = ((BadgedImageView) mTouchedView).getKey();
- if (key == BubbleOverflow.KEY) {
- mStack.showOverflow();
- } else {
- mStack.expandBubble(mBubbleData.getBubbleWithKey(key));
- }
- }
- resetForNextGesture();
- break;
- }
-
- return true;
- }
-
- /** Clears all touch-related state. */
- private void resetForNextGesture() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
-
- mTouchedView = null;
- mMovedEnough = false;
-
- mStack.onGestureFinished();
- }
-
- private void trackMovement(MotionEvent event) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index a0b4938..d974adc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -252,8 +252,14 @@
mSpringToTouchOnNextMotionEvent = true;
}
- /** Prepares the given bubble to be dragged out. */
- public void prepareForBubbleDrag(View bubble, MagnetizedObject.MagneticTarget target) {
+ /**
+ * Prepares the given bubble view to be dragged out, using the provided magnetic target and
+ * listener.
+ */
+ public void prepareForBubbleDrag(
+ View bubble,
+ MagnetizedObject.MagneticTarget target,
+ MagnetizedObject.MagnetListener listener) {
mLayout.cancelAnimationsOnView(bubble);
bubble.setTranslationZ(Short.MAX_VALUE);
@@ -277,6 +283,7 @@
}
};
mMagnetizedBubbleDraggingOut.addTarget(target);
+ mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index c292769..b1bbafc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -1117,9 +1117,4 @@
mAssociatedController = controller;
}
}
-
- @Override
- protected boolean canReceivePointerEvents() {
- return false;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 5d03fc5..7e8fec7 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -45,7 +45,7 @@
companion object {
private const val TAG = "ControlsBindingControllerImpl"
private const val MAX_CONTROLS_REQUEST = 100000L
- private const val SUGGESTED_CONTROLS_REQUEST = 4L
+ private const val SUGGESTED_CONTROLS_REQUEST = 6L
}
private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
@@ -61,6 +61,11 @@
*/
private var statefulControlSubscriber: StatefulControlSubscriber? = null
+ /*
+ * Will track any active load subscriber. Only one can be active at any time.
+ */
+ private var loadSubscriber: LoadSubscriber? = null
+
private val actionCallbackService = object : IControlsActionCallback.Stub() {
override fun accept(
token: IBinder,
@@ -99,17 +104,24 @@
component: ComponentName,
callback: ControlsBindingController.LoadCallback
): Runnable {
- val subscriber = LoadSubscriber(callback, MAX_CONTROLS_REQUEST)
- retrieveLifecycleManager(component).maybeBindAndLoad(subscriber)
- return subscriber.loadCancel()
+ loadSubscriber?.loadCancel()
+
+ val ls = LoadSubscriber(callback, MAX_CONTROLS_REQUEST)
+ loadSubscriber = ls
+
+ retrieveLifecycleManager(component).maybeBindAndLoad(ls)
+ return ls.loadCancel()
}
override fun bindAndLoadSuggested(
component: ComponentName,
callback: ControlsBindingController.LoadCallback
) {
- val subscriber = LoadSubscriber(callback, SUGGESTED_CONTROLS_REQUEST)
- retrieveLifecycleManager(component).maybeBindAndLoadSuggested(subscriber)
+ loadSubscriber?.loadCancel()
+ val ls = LoadSubscriber(callback, SUGGESTED_CONTROLS_REQUEST)
+ loadSubscriber = ls
+
+ retrieveLifecycleManager(component).maybeBindAndLoadSuggested(ls)
}
override fun subscribe(structureInfo: StructureInfo) {
@@ -152,13 +164,16 @@
override fun changeUser(newUser: UserHandle) {
if (newUser == currentUser) return
- unsubscribe()
unbind()
- currentProvider = null
currentUser = newUser
}
private fun unbind() {
+ unsubscribe()
+
+ loadSubscriber?.loadCancel()
+ loadSubscriber = null
+
currentProvider?.unbindService()
currentProvider = null
}
@@ -210,6 +225,20 @@
val callback: ControlsBindingController.LoadCallback
) : CallbackRunnable(token) {
override fun doRun() {
+ Log.d(TAG, "LoadSubscription: Complete and loading controls")
+ callback.accept(list)
+ }
+ }
+
+ private inner class OnCancelAndLoadRunnable(
+ token: IBinder,
+ val list: List<Control>,
+ val subscription: IControlsSubscription,
+ val callback: ControlsBindingController.LoadCallback
+ ) : CallbackRunnable(token) {
+ override fun doRun() {
+ Log.d(TAG, "LoadSubscription: Canceling and loading controls")
+ provider?.cancelSubscription(subscription)
callback.accept(list)
}
}
@@ -220,6 +249,7 @@
val requestLimit: Long
) : CallbackRunnable(token) {
override fun doRun() {
+ Log.d(TAG, "LoadSubscription: Starting subscription")
provider?.startSubscription(subscription, requestLimit)
}
}
@@ -254,34 +284,54 @@
val requestLimit: Long
) : IControlsSubscriber.Stub() {
val loadedControls = ArrayList<Control>()
- var hasError = false
+ private var isTerminated = false
private var _loadCancelInternal: (() -> Unit)? = null
+ private lateinit var subscription: IControlsSubscription
+
fun loadCancel() = Runnable {
- Log.d(TAG, "Cancel load requested")
- _loadCancelInternal?.invoke()
- }
+ Log.d(TAG, "Cancel load requested")
+ _loadCancelInternal?.invoke()
+ }
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
- _loadCancelInternal = subs::cancel
+ subscription = subs
+ _loadCancelInternal = { currentProvider?.cancelSubscription(subscription) }
backgroundExecutor.execute(OnSubscribeRunnable(token, subs, requestLimit))
}
override fun onNext(token: IBinder, c: Control) {
- backgroundExecutor.execute { loadedControls.add(c) }
+ backgroundExecutor.execute {
+ if (isTerminated) return@execute
+
+ loadedControls.add(c)
+
+ // Once we have reached our requestLimit, send a request to cancel, and immediately
+ // load the results. Calls to onError() and onComplete() are not required after
+ // cancel.
+ if (loadedControls.size >= requestLimit) {
+ maybeTerminateAndRun(
+ OnCancelAndLoadRunnable(token, loadedControls, subscription, callback)
+ )
+ }
+ }
}
+
override fun onError(token: IBinder, s: String) {
- hasError = true
- _loadCancelInternal = {}
- currentProvider?.cancelLoadTimeout()
- backgroundExecutor.execute(OnLoadErrorRunnable(token, s, callback))
+ maybeTerminateAndRun(OnLoadErrorRunnable(token, s, callback))
}
override fun onComplete(token: IBinder) {
+ maybeTerminateAndRun(OnLoadRunnable(token, loadedControls, callback))
+ }
+
+ private fun maybeTerminateAndRun(postTerminateFn: Runnable) {
+ if (isTerminated) return
+
+ isTerminated = true
_loadCancelInternal = {}
- if (!hasError) {
- currentProvider?.cancelLoadTimeout()
- backgroundExecutor.execute(OnLoadRunnable(token, loadedControls, callback))
- }
+ currentProvider?.cancelLoadTimeout()
+
+ backgroundExecutor.execute(postTerminateFn)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index ae75dd4..568fb28 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -180,6 +180,11 @@
fun countFavoritesForComponent(componentName: ComponentName): Int
/**
+ * TEMPORARY for testing
+ */
+ fun resetFavorites()
+
+ /**
* Interface for structure to pass data to [ControlsFavoritingActivity].
*/
interface LoadData {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 3483339..8805694 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -365,6 +365,8 @@
componentName: ComponentName,
callback: Consumer<Boolean>
) {
+ if (seedingInProgress) return
+
Log.i(TAG, "Beginning request to seed favorites for: $componentName")
if (!confirmAvailability()) {
if (userChanging) {
@@ -495,6 +497,13 @@
}
}
+ override fun resetFavorites() {
+ executor.execute {
+ Favorites.clear()
+ persistenceWrapper.storeFavorites(Favorites.getAllStructures())
+ }
+ }
+
override fun refreshStatus(componentName: ComponentName, control: Control) {
if (!confirmAvailability()) {
Log.d(TAG, "Controls not available")
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index 895f1d2..a6af6a1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -63,8 +63,6 @@
) : IBinder.DeathRecipient {
val token: IBinder = Binder()
- @GuardedBy("subscriptions")
- private val subscriptions = mutableListOf<IControlsSubscription>()
private var requiresBound = false
@GuardedBy("queuedServiceMethods")
private val queuedServiceMethods: MutableSet<ServiceMethod> = ArraySet()
@@ -194,7 +192,7 @@
* Request a call to [IControlsProvider.loadSuggested].
*
* If the service is not bound, the call will be queued and the service will be bound first.
- * The service will be unbound after the controls are returned or the call times out.
+ * The service will be unbound if the call times out.
*
* @param subscriber the subscriber that manages coordination for loading controls
*/
@@ -245,9 +243,7 @@
if (DEBUG) {
Log.d(TAG, "startSubscription: $subscription")
}
- synchronized(subscriptions) {
- subscriptions.add(subscription)
- }
+
wrapper?.request(subscription, requestLimit)
}
@@ -261,9 +257,7 @@
if (DEBUG) {
Log.d(TAG, "cancelSubscription: $subscription")
}
- synchronized(subscriptions) {
- subscriptions.remove(subscription)
- }
+
wrapper?.cancel(subscription)
}
@@ -281,17 +275,6 @@
onLoadCanceller?.run()
onLoadCanceller = null
- // be sure to cancel all subscriptions
- val subs = synchronized(subscriptions) {
- ArrayList(subscriptions).also {
- subscriptions.clear()
- }
- }
-
- subs.forEach {
- wrapper?.cancel(it)
- }
-
bindService(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 208d911..02c6ff6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -16,18 +16,26 @@
package com.android.systemui.controls.ui
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.app.AlertDialog
import android.app.Dialog
import android.content.ComponentName
import android.content.Context
+import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
+import android.os.Process
import android.service.controls.Control
import android.service.controls.actions.ControlAction
import android.util.TypedValue
import android.util.Log
+import android.view.animation.AccelerateInterpolator
+import android.view.animation.DecelerateInterpolator
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
@@ -77,6 +85,8 @@
private const val PREF_COMPONENT = "controls_component"
private const val PREF_STRUCTURE = "controls_structure"
+ private const val FADE_IN_MILLIS = 225L
+
private val EMPTY_COMPONENT = ComponentName("", "")
private val EMPTY_STRUCTURE = StructureInfo(
EMPTY_COMPONENT,
@@ -153,7 +163,20 @@
private fun reload(parent: ViewGroup) {
if (hidden) return
- show(parent)
+
+ val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f)
+ fadeAnim.setInterpolator(AccelerateInterpolator(1.0f))
+ fadeAnim.setDuration(FADE_IN_MILLIS)
+ fadeAnim.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ show(parent)
+ val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f)
+ showAnim.setInterpolator(DecelerateInterpolator(1.0f))
+ showAnim.setDuration(FADE_IN_MILLIS)
+ showAnim.start()
+ }
+ })
+ fadeAnim.start()
}
private fun showSeedingView(items: List<SelectionItem>) {
@@ -229,7 +252,8 @@
private fun createMenu() {
val items = arrayOf(
- context.resources.getString(R.string.controls_menu_add)
+ context.resources.getString(R.string.controls_menu_add),
+ "Reset"
)
var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
@@ -249,6 +273,8 @@
when (pos) {
// 0: Add Control
0 -> startFavoritingActivity(view.context, selectedStructure)
+ // 1: TEMPORARY for reset controls
+ 1 -> showResetConfirmation()
else -> Log.w(ControlsUiController.TAG,
"Unsupported index ($pos) on 'more' menu selection")
}
@@ -275,6 +301,39 @@
})
}
+ private fun showResetConfirmation() {
+ val builder = AlertDialog.Builder(
+ context,
+ android.R.style.Theme_DeviceDefault_Dialog_Alert
+ ).apply {
+ setMessage("For testing purposes: Would you like to " +
+ "reset your favorited device controls?")
+ setPositiveButton(
+ android.R.string.ok,
+ DialogInterface.OnClickListener { dialog, _ ->
+ val userHandle = Process.myUserHandle()
+ val userContext = context.createContextAsUser(userHandle, 0)
+ val prefs = userContext.getSharedPreferences(
+ "controls_prefs", Context.MODE_PRIVATE)
+ prefs.edit().putBoolean("ControlsSeedingCompleted", false).apply()
+ controlsController.get().resetFavorites()
+ dialog.dismiss()
+ context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
+ })
+ setNegativeButton(
+ android.R.string.cancel,
+ DialogInterface.OnClickListener {
+ dialog, _ -> dialog.cancel()
+ }
+ )
+ }
+ builder.create().apply {
+ getWindow().apply {
+ setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+ }
+ }.show()
+ }
+
private fun createDropDown(items: List<SelectionItem>) {
items.forEach {
RenderInfo.registerComponentIcon(it.componentName, it.icon)
@@ -370,7 +429,7 @@
}
// add spacers if necessary to keep control size consistent
- var spacersToAdd = selectedStructure.controls.size % maxColumns
+ var spacersToAdd = maxColumns - (selectedStructure.controls.size % maxColumns)
while (spacersToAdd > 0) {
lastRow.addView(Space(context), LinearLayout.LayoutParams(0, 0, 1f))
spacersToAdd--
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 4dd5e87..79def1d 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -205,7 +205,9 @@
private final IWindowManager mIWindowManager;
private final Executor mBackgroundExecutor;
private final ControlsListingController mControlsListingController;
- private boolean mAnyControlsProviders = false;
+ private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
+ private ControlsController mControlsController;
+ private SharedPreferences mControlsPreferences;
@VisibleForTesting
public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -271,6 +273,7 @@
mBackgroundExecutor = backgroundExecutor;
mControlsListingController = controlsListingController;
mBlurUtils = blurUtils;
+ mControlsController = controlsController;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -309,45 +312,54 @@
}
});
- String preferredControlsPackage = mContext.getResources()
- .getString(com.android.systemui.R.string.config_controlsPreferredPackage);
mControlsListingController.addCallback(list -> {
- mAnyControlsProviders = !list.isEmpty();
-
- /*
- * See if any service providers match the preferred component. If they do,
- * and there are no current favorites, and we haven't successfully loaded favorites to
- * date, query the preferred component for a limited number of suggested controls.
- */
- ComponentName preferredComponent = null;
- for (ControlsServiceInfo info : list) {
- if (info.componentName.getPackageName().equals(preferredControlsPackage)) {
- preferredComponent = info.componentName;
- break;
- }
- }
-
- if (preferredComponent == null) return;
-
- SharedPreferences prefs = context.getSharedPreferences(PREFS_CONTROLS_FILE,
- Context.MODE_PRIVATE);
- boolean isSeeded = prefs.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false);
- boolean hasFavorites = controlsController.getFavorites().size() > 0;
- if (!isSeeded && !hasFavorites) {
- controlsController.seedFavoritesForComponent(
- preferredComponent,
- (accepted) -> {
- Log.i(TAG, "Controls seeded: " + accepted);
- prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED,
- accepted).apply();
- }
- );
- }
+ mControlsServiceInfos = list;
});
+
+ // Need to be user-specific with the context to make sure we read the correct prefs
+ Context userContext = context.createContextAsUser(
+ new UserHandle(mUserManager.getUserHandle()), 0);
+ mControlsPreferences = userContext.getSharedPreferences(PREFS_CONTROLS_FILE,
+ Context.MODE_PRIVATE);
+
}
+ private void seedFavorites() {
+ if (mControlsServiceInfos.isEmpty()
+ || mControlsController.getFavorites().size() > 0
+ || mControlsPreferences.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false)) {
+ return;
+ }
+ /*
+ * See if any service providers match the preferred component. If they do,
+ * and there are no current favorites, and we haven't successfully loaded favorites to
+ * date, query the preferred component for a limited number of suggested controls.
+ */
+ String preferredControlsPackage = mContext.getResources()
+ .getString(com.android.systemui.R.string.config_controlsPreferredPackage);
+ ComponentName preferredComponent = null;
+ for (ControlsServiceInfo info : mControlsServiceInfos) {
+ if (info.componentName.getPackageName().equals(preferredControlsPackage)) {
+ preferredComponent = info.componentName;
+ break;
+ }
+ }
+
+ if (preferredComponent == null) {
+ Log.i(TAG, "Controls seeding: No preferred component has been set, will not seed");
+ mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply();
+ }
+
+ mControlsController.seedFavoritesForComponent(
+ preferredComponent,
+ (accepted) -> {
+ Log.i(TAG, "Controls seeded: " + accepted);
+ mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED,
+ accepted).apply();
+ });
+ }
/**
* Show the global actions dialog (creating if necessary)
@@ -393,6 +405,7 @@
awakenIfNecessary();
mDialog = createDialog();
prepareDialog();
+ seedFavorites();
// If we only have 1 item and it's a simple press action, just do this action.
if (mAdapter.getCount() == 1
@@ -2017,6 +2030,6 @@
private boolean shouldShowControls() {
return mKeyguardStateController.isUnlocked()
&& mControlsUiController.getAvailable()
- && mAnyControlsProviders;
+ && !mControlsServiceInfos.isEmpty();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index aa5ebaa..b7658a9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -46,19 +46,6 @@
/** Updates seek bar views when the data model changes. */
@UiThread
override fun onChanged(data: SeekBarViewModel.Progress) {
- if (data.enabled && seekBarView.visibility == View.GONE) {
- seekBarView.visibility = View.VISIBLE
- elapsedTimeView.visibility = View.VISIBLE
- totalTimeView.visibility = View.VISIBLE
- } else if (!data.enabled && seekBarView.visibility == View.VISIBLE) {
- seekBarView.visibility = View.GONE
- elapsedTimeView.visibility = View.GONE
- totalTimeView.visibility = View.GONE
- return
- }
-
- // TODO: update the style of the disabled progress bar
- seekBarView.setEnabled(data.seekAvailable)
data.color?.let {
var tintList = ColorStateList.valueOf(it)
@@ -71,6 +58,17 @@
totalTimeView.setTextColor(it)
}
+ if (!data.enabled) {
+ seekBarView.setEnabled(false)
+ seekBarView.getThumb().setAlpha(0)
+ elapsedTimeView.setText("")
+ totalTimeView.setText("")
+ return
+ }
+
+ seekBarView.getThumb().setAlpha(if (data.seekAvailable) 255 else 0)
+ seekBarView.setEnabled(data.seekAvailable)
+
data.elapsedTime?.let {
seekBarView.setProgress(it)
elapsedTimeView.setText(DateUtils.formatElapsedTime(
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index b10dd93..d2994ae 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -103,6 +103,7 @@
@Override
public void onPipAnimationEnd(SurfaceControl.Transaction tx,
PipAnimationController.PipTransitionAnimator animator) {
+ finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection());
mMainHandler.post(() -> {
for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
@@ -110,7 +111,6 @@
animator.getTransitionDirection());
}
});
- finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index a192afc..a8a5d89 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -16,12 +16,10 @@
package com.android.systemui.pip.phone;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager.StackInfo;
import android.app.IActivityTaskManager;
import android.content.Context;
import android.graphics.Rect;
@@ -169,15 +167,7 @@
*/
void synchronizePinnedStackBounds() {
cancelAnimations();
- try {
- StackInfo stackInfo = mActivityTaskManager.getStackInfo(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- if (stackInfo != null) {
- mBounds.set(stackInfo.bounds);
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to get pinned stack bounds");
- }
+ mBounds.set(mPipTaskOrganizer.getLastReportedBounds());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 52c8960..99a01d3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -289,7 +289,6 @@
mMediaSessionManager =
(MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
- mPipTaskOrganizer.registerPipTransitionCallback(this);
try {
WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
@@ -334,6 +333,8 @@
* Shows the picture-in-picture menu if an activity is in picture-in-picture mode.
*/
public void showPictureInPictureMenu() {
+ if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), current state=" + getStateDescription());
+
if (getState() == STATE_PIP) {
resizePinnedStack(STATE_PIP_MENU);
}
@@ -343,10 +344,18 @@
* Closes PIP (PIPed activity and PIP system UI).
*/
public void closePip() {
+ if (DEBUG) Log.d(TAG, "closePip(), current state=" + getStateDescription());
+
closePipInternal(true);
}
private void closePipInternal(boolean removePipStack) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "closePipInternal() removePipStack=" + removePipStack + ", current state="
+ + getStateDescription());
+ }
+
mState = STATE_NO_PIP;
mPipTaskId = TASK_ID_NO_PIP;
mPipMediaController = null;
@@ -371,6 +380,8 @@
* Moves the PIPed activity to the fullscreen and closes PIP system UI.
*/
void movePipToFullscreen() {
+ if (DEBUG) Log.d(TAG, "movePipToFullscreen(), current state=" + getStateDescription());
+
mPipTaskId = TASK_ID_NO_PIP;
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onMoveToFullscreen();
@@ -386,6 +397,7 @@
public void suspendPipResizing(int reason) {
if (DEBUG) Log.d(TAG,
"suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
+
mSuspendPipResizingReason |= reason;
}
@@ -408,7 +420,11 @@
* @param state In Pip state also used to determine the new size for the Pip.
*/
void resizePinnedStack(int state) {
- if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state, new Exception());
+ if (DEBUG) {
+ Log.d(TAG, "resizePinnedStack() state=" + stateToName(state) + ", current state="
+ + getStateDescription(), new Exception());
+ }
+
boolean wasStateNoPip = (mState == STATE_NO_PIP);
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onPipResizeAboutToStart();
@@ -418,7 +434,7 @@
if (DEBUG) Log.d(TAG, "resizePinnedStack() deferring"
+ " mSuspendPipResizingReason=" + mSuspendPipResizingReason
+ " mResumeResizePinnedStackRunnableState="
- + mResumeResizePinnedStackRunnableState);
+ + stateToName(mResumeResizePinnedStackRunnableState));
return;
}
mState = state;
@@ -458,7 +474,8 @@
* stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
*/
private void showPipMenu() {
- if (DEBUG) Log.d(TAG, "showPipMenu()");
+ if (DEBUG) Log.d(TAG, "showPipMenu(), current state=" + getStateDescription());
+
mState = STATE_PIP_MENU;
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onShowPipMenu();
@@ -712,6 +729,7 @@
private void onPipTransitionFinishedOrCanceled() {
if (DEBUG) Log.d(TAG, "onPipTransitionFinishedOrCanceled()");
+
if (getState() == STATE_PIP_MENU) {
showPipMenu();
}
@@ -753,4 +771,28 @@
WindowManagerWrapper.getInstance().setPipVisibility(visible);
});
}
+
+ private String getStateDescription() {
+ if (mSuspendPipResizingReason == 0) {
+ return stateToName(mState);
+ }
+ return stateToName(mResumeResizePinnedStackRunnableState) + " (while " + stateToName(mState)
+ + " is suspended)";
+ }
+
+ private static String stateToName(int state) {
+ switch (state) {
+ case STATE_NO_PIP:
+ return "NO_PIP";
+
+ case STATE_PIP:
+ return "PIP";
+
+ case STATE_PIP_MENU:
+ return "PIP_MENU";
+
+ default:
+ return "UNKNOWN(" + state + ")";
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
index f43f8e7..c7e77cc 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
+import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.pip.tv.dagger.TvPipComponent;
@@ -34,6 +35,7 @@
* Activity to show the PIP menu to control PIP.
*/
public class PipMenuActivity extends Activity implements PipManager.Listener {
+ private static final boolean DEBUG = false;
private static final String TAG = "PipMenuActivity";
static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
@@ -47,7 +49,6 @@
private boolean mRestorePipSizeWhenClose;
private PipControlsViewController mPipControlsViewController;
-
@Inject
public PipMenuActivity(TvPipComponent.Builder pipComponentBuilder, PipManager pipManager) {
super();
@@ -57,6 +58,8 @@
@Override
protected void onCreate(Bundle bundle) {
+ if (DEBUG) Log.d(TAG, "onCreate()");
+
super.onCreate(bundle);
if (!mPipManager.isPipShown()) {
finish();
@@ -81,13 +84,18 @@
@Override
protected void onNewIntent(Intent intent) {
+ if (DEBUG) Log.d(TAG, "onNewIntent(), intent=" + intent);
super.onNewIntent(intent);
onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
}
private void restorePipAndFinish() {
+ if (DEBUG) Log.d(TAG, "restorePipAndFinish()");
+
if (mRestorePipSizeWhenClose) {
+ if (DEBUG) Log.d(TAG, " > restoring to the default position");
+
// When PIP menu activity is closed, restore to the default position.
mPipManager.resizePinnedStack(PipManager.STATE_PIP);
}
@@ -96,12 +104,16 @@
@Override
public void onResume() {
+ if (DEBUG) Log.d(TAG, "onResume()");
+
super.onResume();
mFadeInAnimation.start();
}
@Override
public void onPause() {
+ if (DEBUG) Log.d(TAG, "onPause()");
+
super.onPause();
mFadeOutAnimation.start();
restorePipAndFinish();
@@ -109,6 +121,8 @@
@Override
protected void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy()");
+
super.onDestroy();
mPipManager.removeListener(this);
mPipManager.resumePipResizing(
@@ -117,29 +131,41 @@
@Override
public void onBackPressed() {
+ if (DEBUG) Log.d(TAG, "onBackPressed()");
+
restorePipAndFinish();
}
@Override
- public void onPipEntered() { }
+ public void onPipEntered() {
+ if (DEBUG) Log.d(TAG, "onPipEntered()");
+ }
@Override
public void onPipActivityClosed() {
+ if (DEBUG) Log.d(TAG, "onPipActivityClosed()");
+
finish();
}
@Override
public void onPipMenuActionsChanged(ParceledListSlice actions) {
+ if (DEBUG) Log.d(TAG, "onPipMenuActionsChanged()");
+
boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
mPipControlsViewController.setActions(
hasCustomActions ? actions.getList() : Collections.EMPTY_LIST);
}
@Override
- public void onShowPipMenu() { }
+ public void onShowPipMenu() {
+ if (DEBUG) Log.d(TAG, "onShowPipMenu()");
+ }
@Override
public void onMoveToFullscreen() {
+ if (DEBUG) Log.d(TAG, "onMoveToFullscreen()");
+
// Moving PIP to fullscreen is implemented by resizing PINNED_STACK with null bounds.
// This conflicts with restoring PIP position, so disable it.
mRestorePipSizeWhenClose = false;
@@ -148,8 +174,17 @@
@Override
public void onPipResizeAboutToStart() {
+ if (DEBUG) Log.d(TAG, "onPipResizeAboutToStart()");
+
finish();
mPipManager.suspendPipResizing(
PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
}
+
+ @Override
+ public void finish() {
+ if (DEBUG) Log.d(TAG, "finish()", new RuntimeException());
+
+ super.finish();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 295adae..9324b14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -24,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
+import android.os.SystemClock;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
@@ -555,8 +556,8 @@
NotificationEntry entry = new NotificationEntry(
notification,
ranking,
- mFgsFeatureController.isForegroundServiceDismissalEnabled());
- mAllNotifications.add(entry);
+ mFgsFeatureController.isForegroundServiceDismissalEnabled(),
+ SystemClock.uptimeMillis());
mLeakDetector.trackInstance(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 9c2cac7..365862b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -47,6 +47,7 @@
import android.annotation.UserIdInt;
import android.app.Notification;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
@@ -387,7 +388,7 @@
if (entry == null) {
// A new notification!
- entry = new NotificationEntry(sbn, ranking);
+ entry = new NotificationEntry(sbn, ranking, SystemClock.uptimeMillis());
mNotificationSet.put(sbn.getKey(), entry);
mLogger.logNotifPosted(sbn.getKey());
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 c1ba26d..68ec34e 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
@@ -35,6 +35,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.CurrentTimeMillisLong;
import android.app.Notification;
import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
@@ -93,6 +94,7 @@
private final String mKey;
private StatusBarNotification mSbn;
private Ranking mRanking;
+ private long mCreationTime;
/*
* Bookkeeping members
@@ -171,21 +173,29 @@
private boolean mAllowFgsDismissal;
private int mBucket = BUCKET_ALERTING;
+ /**
+ * @param sbn the StatusBarNotification from system server
+ * @param ranking also from system server
+ * @param creationTime SystemClock.uptimeMillis of when we were created
+ */
public NotificationEntry(
@NonNull StatusBarNotification sbn,
- @NonNull Ranking ranking) {
- this(sbn, ranking, false);
+ @NonNull Ranking ranking,
+ long creationTime) {
+ this(sbn, ranking, false, creationTime);
}
public NotificationEntry(
@NonNull StatusBarNotification sbn,
@NonNull Ranking ranking,
- boolean allowFgsDismissal
+ boolean allowFgsDismissal,
+ long creationTime
) {
super(requireNonNull(Objects.requireNonNull(sbn).getKey()));
requireNonNull(ranking);
+ mCreationTime = creationTime;
mKey = sbn.getKey();
setSbn(sbn);
setRanking(ranking);
@@ -238,6 +248,21 @@
}
/**
+ * A timestamp of SystemClock.uptimeMillis() of when this entry was first created, regardless
+ * of any changes to the data presented. It is set once on creation and will never change, and
+ * allows us to know exactly how long this notification has been alive for in our listener
+ * service. It is entirely unrelated to the information inside of the notification.
+ *
+ * This is different to Notification#when because it persists throughout updates, whereas
+ * system server treats every single call to notify() as a new notification and we handle
+ * updates to NotificationEntry locally.
+ */
+ @CurrentTimeMillisLong
+ public long getCreationTime() {
+ return mCreationTime;
+ }
+
+ /**
* Should only be called by NotificationEntryManager and friends.
* TODO: Make this package-private
*/
diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
new file mode 100644
index 0000000..d65b285
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.graphics.PointF
+import android.os.Handler
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.view.View
+import android.view.ViewConfiguration
+import kotlin.math.hypot
+
+/**
+ * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about
+ * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the
+ * view's initial position.
+ */
+abstract class RelativeTouchListener : View.OnTouchListener {
+
+ /**
+ * Called when an ACTION_DOWN event is received for the given view.
+ *
+ * @return False if the object is not interested in MotionEvents at this time, or true if we
+ * should consume this event and subsequent events, and begin calling [onMove].
+ */
+ abstract fun onDown(v: View, ev: MotionEvent): Boolean
+
+ /**
+ * Called when an ACTION_MOVE event is received for the given view. This signals that the view
+ * is being dragged.
+ *
+ * @param viewInitialX The view's translationX value when this touch gesture started.
+ * @param viewInitialY The view's translationY value when this touch gesture started.
+ * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels.
+ * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels.
+ */
+ abstract fun onMove(
+ v: View,
+ ev: MotionEvent,
+ viewInitialX: Float,
+ viewInitialY: Float,
+ dx: Float,
+ dy: Float
+ )
+
+ /**
+ * Called when an ACTION_UP event is received for the given view. This signals that a drag or
+ * fling gesture has completed.
+ *
+ * @param viewInitialX The view's translationX value when this touch gesture started.
+ * @param viewInitialY The view's translationY value when this touch gesture started.
+ * @param dx Horizontal distance covered, in pixels.
+ * @param dy Vertical distance covered, in pixels.
+ * @param velX The final horizontal velocity of the gesture, in pixels/second.
+ * @param velY The final vertical velocity of the gesture, in pixels/second.
+ */
+ abstract fun onUp(
+ v: View,
+ ev: MotionEvent,
+ viewInitialX: Float,
+ viewInitialY: Float,
+ dx: Float,
+ dy: Float,
+ velX: Float,
+ velY: Float
+ )
+
+ /** The raw coordinates of the last ACTION_DOWN event. */
+ private val touchDown = PointF()
+
+ /** The coordinates of the view, at the time of the last ACTION_DOWN event. */
+ private val viewPositionOnTouchDown = PointF()
+
+ private val velocityTracker = VelocityTracker.obtain()
+
+ private var touchSlop: Int = -1
+ private var movedEnough = false
+
+ private val handler = Handler()
+ private var performedLongClick = false
+
+ @Suppress("UNCHECKED_CAST")
+ override fun onTouch(v: View, ev: MotionEvent): Boolean {
+ addMovement(ev)
+
+ val dx = ev.rawX - touchDown.x
+ val dy = ev.rawY - touchDown.y
+
+ when (ev.action) {
+ MotionEvent.ACTION_DOWN -> {
+ if (!onDown(v, ev)) {
+ return false
+ }
+
+ // Grab the touch slop, it might have changed if the config changed since the
+ // last gesture.
+ touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
+
+ touchDown.set(ev.rawX, ev.rawY)
+ viewPositionOnTouchDown.set(v.translationX, v.translationY)
+
+ performedLongClick = false
+ handler.postDelayed({
+ performedLongClick = v.performLongClick()
+ }, ViewConfiguration.getLongPressTimeout().toLong())
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
+ movedEnough = true
+ handler.removeCallbacksAndMessages(null)
+ }
+
+ if (movedEnough) {
+ onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy)
+ }
+ }
+
+ MotionEvent.ACTION_UP -> {
+ if (movedEnough) {
+ velocityTracker.computeCurrentVelocity(1000 /* units */)
+ onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
+ velocityTracker.xVelocity, velocityTracker.yVelocity)
+ } else if (!performedLongClick) {
+ v.performClick()
+ } else {
+ handler.removeCallbacksAndMessages(null)
+ }
+
+ velocityTracker.clear()
+ movedEnough = false
+ }
+ }
+
+ return true
+ }
+
+ /**
+ * Adds a movement to the velocity tracker using raw screen coordinates.
+ */
+ private fun addMovement(event: MotionEvent) {
+ val deltaX = event.rawX - event.x
+ val deltaY = event.rawY - event.y
+ event.offsetLocation(deltaX, deltaY)
+ velocityTracker.addMovement(event)
+ event.offsetLocation(-deltaX, -deltaY)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index cf1299f..ce032c9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -115,6 +115,7 @@
@Test
public void testShow_dismissedByCallback() throws Exception {
+ doAnswer(answerVoid(Runnable::run)).when(mHandler).post(any(Runnable.class));
doAnswer(invocation -> {
IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1];
callback.onDismiss();
@@ -184,7 +185,7 @@
private void verifyViewDismissed(SurfaceView v) throws Exception {
verify(mParent).removeView(v);
- verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID);
+ verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID, true);
assertThat(mContext.isBound(mComponentName)).isFalse();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 69e933e..45f9437 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.time.FakeSystemClock;
import junit.framework.Assert;
@@ -69,6 +70,7 @@
private ForegroundServiceController mFsc;
private ForegroundServiceNotificationListener mListener;
private NotificationEntryListener mEntryListener;
+ private final FakeSystemClock mClock = new FakeSystemClock();
@Mock private NotificationEntryManager mEntryManager;
@Mock private AppOpsController mAppOpsController;
@Mock private Handler mMainHandler;
@@ -80,9 +82,10 @@
allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
- mFsc = new ForegroundServiceController(mEntryManager, mAppOpsController, mMainHandler);
+ mFsc = new ForegroundServiceController(
+ mEntryManager, mAppOpsController, mMainHandler);
mListener = new ForegroundServiceNotificationListener(
- mContext, mFsc, mEntryManager, mNotifPipeline);
+ mContext, mFsc, mEntryManager, mNotifPipeline, mClock);
ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
ArgumentCaptor.forClass(NotificationEntryListener.class);
verify(mEntryManager).addNotificationEntryListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
index 46a473b..bca8dee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
@@ -17,7 +17,6 @@
package com.android.systemui;
import static com.android.systemui.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS;
-import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -29,6 +28,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -37,12 +37,15 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ForegroundServiceNotificationListenerTest extends SysuiTestCase {
- private ForegroundServiceLifetimeExtender mExtender = new ForegroundServiceLifetimeExtender();
+ private ForegroundServiceLifetimeExtender mExtender;
private NotificationEntry mEntry;
private Notification mNotif;
+ private final FakeSystemClock mClock = new FakeSystemClock();
@Before
public void setup() {
+ mExtender = new ForegroundServiceLifetimeExtender(mClock);
+
mNotif = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
@@ -50,6 +53,7 @@
.build();
mEntry = new NotificationEntryBuilder()
+ .setCreationTime(mClock.uptimeMillis())
.setNotification(mNotif)
.build();
}
@@ -62,27 +66,26 @@
// Extend the lifetime of a FGS notification iff it has not been visible
// for the minimum time
mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
- modifySbn(mEntry)
- .setPostTime(System.currentTimeMillis())
- .build();
+
+ // No time has elapsed, keep showing
assertTrue(mExtender.shouldExtendLifetime(mEntry));
}
@Test
public void testShouldExtendLifetime_shouldNot_foreground() {
mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
- modifySbn(mEntry)
- .setPostTime(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1)
- .build();
+
+ // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1
+ mClock.advanceTime(MIN_FGS_TIME_MS + 1);
assertFalse(mExtender.shouldExtendLifetime(mEntry));
}
@Test
public void testShouldExtendLifetime_shouldNot_notForeground() {
mNotif.flags = 0;
- modifySbn(mEntry)
- .setPostTime(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1)
- .build();
+
+ // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1
+ mClock.advanceTime(MIN_FGS_TIME_MS + 1);
assertFalse(mExtender.shouldExtendLifetime(mEntry));
}
}
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 037f04e..e472de3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -374,7 +374,7 @@
assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
assertTrue(mBubbleController.hasBubbles());
- mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
assertFalse(mNotificationShadeWindowController.getBubblesShowing());
verify(mNotificationEntryManager, times(3)).updateNotifications(any());
assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
@@ -399,7 +399,7 @@
// Expand the stack
BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleController.expandStack();
+ mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
@@ -436,7 +436,7 @@
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleController.expandStack();
+ mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
@@ -448,7 +448,7 @@
mRow2.getEntry()));
// Switch which bubble is expanded
- mBubbleController.selectBubble(mRow.getEntry().getKey());
+ mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
mBubbleData.setExpanded(true);
assertEquals(mRow.getEntry(),
mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry());
@@ -482,7 +482,7 @@
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
// Expand
- mBubbleController.expandStack();
+ mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
@@ -510,7 +510,7 @@
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
// Expand
- mBubbleController.expandStack();
+ mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
@@ -544,7 +544,7 @@
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleController.expandStack();
+ mBubbleData.setExpanded(true);
assertTrue(mSysUiStateBubblesExpanded);
@@ -726,7 +726,7 @@
public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.updateBubble(mRow2.getEntry());
- mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(2)).send();
}
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 545de21..5f4f2ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -321,7 +321,7 @@
assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
assertTrue(mBubbleController.hasBubbles());
- mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
assertFalse(mNotificationShadeWindowController.getBubblesShowing());
verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
@@ -344,7 +344,7 @@
// Expand the stack
BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleController.expandStack();
+ mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
@@ -376,7 +376,7 @@
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleController.expandStack();
+ mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
@@ -385,7 +385,7 @@
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry()));
// Switch which bubble is expanded
- mBubbleController.selectBubble(mRow.getEntry().getKey());
+ mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
mBubbleData.setExpanded(true);
assertEquals(mRow.getEntry(),
mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry());
@@ -416,7 +416,7 @@
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
// Expand
- mBubbleController.expandStack();
+ mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
@@ -442,7 +442,7 @@
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
// Expand
- mBubbleController.expandStack();
+ mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
@@ -474,7 +474,7 @@
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleController.expandStack();
+ mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
@@ -628,7 +628,7 @@
public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.updateBubble(mRow2.getEntry());
- mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(2)).send();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
index 88316f2..bb003ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -125,7 +125,7 @@
loadSubscriberCaptor.value.onSubscribe(Binder(), subscription)
canceller.run()
- verify(subscription).cancel()
+ verify(providers[0]).cancelSubscription(subscription)
}
@Test
@@ -145,7 +145,7 @@
loadSubscriberCaptor.value.onComplete(b)
canceller.run()
- verify(subscription, never()).cancel()
+ verify(providers[0], never()).cancelSubscription(subscription)
}
@Test
@@ -203,7 +203,7 @@
loadSubscriberCaptor.value.onError(b, "")
canceller.run()
- verify(subscription, never()).cancel()
+ verify(providers[0], never()).cancelSubscription(subscription)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index 260f520..d407b8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -67,10 +67,11 @@
val isEnabled = false
val data = SeekBarViewModel.Progress(isEnabled, false, null, null, null)
observer.onChanged(data)
- // THEN seek bar visibility is set to GONE
- assertThat(seekBarView.getVisibility()).isEqualTo(View.GONE)
- assertThat(elapsedTimeView.getVisibility()).isEqualTo(View.GONE)
- assertThat(totalTimeView.getVisibility()).isEqualTo(View.GONE)
+ // THEN seek bar shows just a line with no text
+ assertThat(seekBarView.isEnabled()).isFalse()
+ assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0)
+ assertThat(elapsedTimeView.getText()).isEqualTo("")
+ assertThat(totalTimeView.getText()).isEqualTo("")
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index 43cf83f..81f9546 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -29,6 +29,7 @@
import com.android.internal.logging.InstanceId;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.util.time.FakeSystemClock;
import java.util.ArrayList;
@@ -44,16 +45,22 @@
public class NotificationEntryBuilder {
private final SbnBuilder mSbnBuilder = new SbnBuilder();
private final RankingBuilder mRankingBuilder = new RankingBuilder();
+ private final FakeSystemClock mClock = new FakeSystemClock();
private StatusBarNotification mSbn = null;
/* ListEntry properties */
private GroupEntry mParent;
private int mSection = -1;
+ /* If set, use this creation time instead of mClock.uptimeMillis */
+ private long mCreationTime = -1;
+
public NotificationEntry build() {
StatusBarNotification sbn = mSbn != null ? mSbn : mSbnBuilder.build();
mRankingBuilder.setKey(sbn.getKey());
- final NotificationEntry entry = new NotificationEntry(sbn, mRankingBuilder.build());
+ long creationTime = mCreationTime != -1 ? mCreationTime : mClock.uptimeMillis();
+ final NotificationEntry entry = new NotificationEntry(
+ sbn, mRankingBuilder.build(), mClock.uptimeMillis());
/* ListEntry properties */
entry.setParent(mParent);
@@ -86,6 +93,14 @@
return this;
}
+ /**
+ * Set the creation time
+ */
+ public NotificationEntryBuilder setCreationTime(long creationTime) {
+ mCreationTime = creationTime;
+ return this;
+ }
+
/* Delegated to SbnBuilder */
public NotificationEntryBuilder setPkg(String pkg) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 5b0b668..1a022ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -71,6 +72,7 @@
private int mId;
private NotificationEntry mEntry;
+ private final FakeSystemClock mClock = new FakeSystemClock();
@Before
public void setup() {
@@ -187,7 +189,7 @@
.build();
NotificationEntry entry =
- new NotificationEntry(sbn, ranking);
+ new NotificationEntry(sbn, ranking, mClock.uptimeMillis());
assertEquals(systemGeneratedSmartActions, entry.getSmartActions());
assertEquals(NOTIFICATION_CHANNEL, entry.getChannel());
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index b3a30ab..6695eed 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -33,7 +33,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
@@ -72,8 +71,6 @@
import org.mockito.quality.Strictness;
import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -255,19 +252,16 @@
@Test
public void testRequestLastEntitlementCacheValue() throws Exception {
- final CountDownLatch mCallbacklatch = new CountDownLatch(1);
// 1. Entitlement check is not required.
mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
ResultReceiver receiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
assertEquals(TETHER_ERROR_NO_ERROR, resultCode);
- mCallbacklatch.countDown();
}
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
- callbackTimeoutHelper(mCallbacklatch);
assertEquals(0, mEnMgr.uiProvisionCount);
mEnMgr.reset();
@@ -277,12 +271,10 @@
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
- mCallbacklatch.countDown();
}
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
mLooper.dispatchAll();
- callbackTimeoutHelper(mCallbacklatch);
assertEquals(0, mEnMgr.uiProvisionCount);
mEnMgr.reset();
// 3. No cache value and ui entitlement check is needed.
@@ -291,12 +283,10 @@
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode);
- mCallbacklatch.countDown();
}
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
- callbackTimeoutHelper(mCallbacklatch);
assertEquals(1, mEnMgr.uiProvisionCount);
mEnMgr.reset();
// 4. Cache value is TETHER_ERROR_PROVISIONING_FAILED and don't need to run entitlement
@@ -306,12 +296,10 @@
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode);
- mCallbacklatch.countDown();
}
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
mLooper.dispatchAll();
- callbackTimeoutHelper(mCallbacklatch);
assertEquals(0, mEnMgr.uiProvisionCount);
mEnMgr.reset();
// 5. Cache value is TETHER_ERROR_PROVISIONING_FAILED and ui entitlement check is needed.
@@ -320,12 +308,10 @@
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
assertEquals(TETHER_ERROR_NO_ERROR, resultCode);
- mCallbacklatch.countDown();
}
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
- callbackTimeoutHelper(mCallbacklatch);
assertEquals(1, mEnMgr.uiProvisionCount);
mEnMgr.reset();
// 6. Cache value is TETHER_ERROR_NO_ERROR.
@@ -334,12 +320,10 @@
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
assertEquals(TETHER_ERROR_NO_ERROR, resultCode);
- mCallbacklatch.countDown();
}
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
- callbackTimeoutHelper(mCallbacklatch);
assertEquals(0, mEnMgr.uiProvisionCount);
mEnMgr.reset();
// 7. Test get value for other downstream type.
@@ -347,12 +331,10 @@
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
- mCallbacklatch.countDown();
}
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_USB, receiver, false);
mLooper.dispatchAll();
- callbackTimeoutHelper(mCallbacklatch);
assertEquals(0, mEnMgr.uiProvisionCount);
mEnMgr.reset();
// 8. Test get value for invalid downstream type.
@@ -361,22 +343,14 @@
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
- mCallbacklatch.countDown();
}
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI_P2P, receiver, true);
mLooper.dispatchAll();
- callbackTimeoutHelper(mCallbacklatch);
assertEquals(0, mEnMgr.uiProvisionCount);
mEnMgr.reset();
}
- void callbackTimeoutHelper(final CountDownLatch latch) throws Exception {
- if (!latch.await(1, TimeUnit.SECONDS)) {
- fail("Timout, fail to receive callback");
- }
- }
-
@Test
public void verifyPermissionResult() {
setupForRequiredProvisioning();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index ff59c24..20a11bd 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility;
import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.os.ShellCommand;
import android.os.UserHandle;
@@ -83,7 +84,7 @@
return null;
}
}
- return UserHandle.USER_SYSTEM;
+ return ActivityManager.getCurrentUser();
}
@Override
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index bc45533..8f8990f 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -98,7 +98,7 @@
final long endTime = SystemClock.uptimeMillis();
Slog.d(TAG, "Completed ANR of " + r.mApp.processName + " in "
+ (endTime - startTime) + "ms, latency " + reportLatency
- + (onlyDumpSelf ? "ms" : "ms (expired, only dump ANR app)"));
+ + (onlyDumpSelf ? "ms (expired, only dump ANR app)" : "ms"));
}
mRunning.set(false);
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 962f337..9a910bf 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -569,9 +569,10 @@
// Immediately dispatch notifications to foreground apps that
// are important to the user; all other background observers are
// delayed to avoid stampeding
+ final boolean noDelay = (key.flags & ContentResolver.NOTIFY_NO_DELAY) != 0;
final int procState = LocalServices.getService(ActivityManagerInternal.class)
.getUidProcessState(key.uid);
- if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND || noDelay) {
task.run();
} else {
BackgroundThread.getHandler().postDelayed(task, BACKGROUND_OBSERVER_DELAY);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 1931079..c3413e8 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -98,18 +98,26 @@
public List<MediaRoute2Info> getSystemRoutes() {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+ final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ == PackageManager.PERMISSION_GRANTED;
final long token = Binder.clearCallingIdentity();
try {
Collection<MediaRoute2Info> systemRoutes;
synchronized (mLock) {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
- MediaRoute2ProviderInfo providerInfo =
- userRecord.mHandler.mSystemProvider.getProviderInfo();
- if (providerInfo != null) {
- systemRoutes = providerInfo.getRoutes();
+ if (hasModifyAudioRoutingPermission) {
+ MediaRoute2ProviderInfo providerInfo =
+ userRecord.mHandler.mSystemProvider.getProviderInfo();
+ if (providerInfo != null) {
+ systemRoutes = providerInfo.getRoutes();
+ } else {
+ systemRoutes = Collections.emptyList();
+ }
} else {
- systemRoutes = Collections.emptyList();
+ systemRoutes = new ArrayList<>();
+ systemRoutes.add(userRecord.mHandler.mSystemProvider.getDefaultRoute());
}
}
return new ArrayList<>(systemRoutes);
@@ -122,18 +130,25 @@
public RoutingSessionInfo getSystemSessionInfo() {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+ final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ == PackageManager.PERMISSION_GRANTED;
final long token = Binder.clearCallingIdentity();
try {
RoutingSessionInfo systemSessionInfo = null;
synchronized (mLock) {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
- List<RoutingSessionInfo> sessionInfos =
- userRecord.mHandler.mSystemProvider.getSessionInfos();
- if (sessionInfos != null && !sessionInfos.isEmpty()) {
- systemSessionInfo = sessionInfos.get(0);
+ List<RoutingSessionInfo> sessionInfos;
+ if (hasModifyAudioRoutingPermission) {
+ sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
+ if (sessionInfos != null && !sessionInfos.isEmpty()) {
+ systemSessionInfo = sessionInfos.get(0);
+ } else {
+ Slog.w(TAG, "System provider does not have any session info.");
+ }
} else {
- Slog.w(TAG, "System provider does not have any session info.");
+ systemSessionInfo = userRecord.mHandler.mSystemProvider.getDefaultSessionInfo();
}
}
return systemSessionInfo;
@@ -654,10 +669,20 @@
return;
}
- routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::transferToRouteOnHandler,
- routerRecord.mUserRecord.mHandler,
- DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
+ String defaultRouteId =
+ routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId();
+ if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
+ && !TextUtils.equals(route.getId(), defaultRouteId)) {
+ routerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::notifySessionCreationFailedToRouter,
+ routerRecord.mUserRecord.mHandler,
+ routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID)));
+ } else {
+ routerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::transferToRouteOnHandler,
+ routerRecord.mUserRecord.mHandler,
+ DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
+ }
}
private void setSessionVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
@@ -1185,18 +1210,42 @@
}
}
- List<IMediaRouter2> routers = getRouters();
+ List<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true);
+ List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false);
List<IMediaRouter2Manager> managers = getManagers();
+ List<MediaRoute2Info> defaultRoute = new ArrayList<>();
+ defaultRoute.add(mSystemProvider.getDefaultRoute());
+
if (addedRoutes.size() > 0) {
- notifyRoutesAddedToRouters(routers, addedRoutes);
+ notifyRoutesAddedToRouters(routersWithModifyAudioRoutingPermission, addedRoutes);
+ if (!provider.mIsSystemRouteProvider) {
+ notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
+ addedRoutes);
+ } else if (prevInfo == null) {
+ notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
+ defaultRoute);
+ } // 'else' is handled as changed routes
notifyRoutesAddedToManagers(managers, addedRoutes);
}
if (removedRoutes.size() > 0) {
- notifyRoutesRemovedToRouters(routers, removedRoutes);
+ notifyRoutesRemovedToRouters(routersWithModifyAudioRoutingPermission,
+ removedRoutes);
+ if (!provider.mIsSystemRouteProvider) {
+ notifyRoutesRemovedToRouters(routersWithoutModifyAudioRoutingPermission,
+ removedRoutes);
+ }
notifyRoutesRemovedToManagers(managers, removedRoutes);
}
if (changedRoutes.size() > 0) {
- notifyRoutesChangedToRouters(routers, changedRoutes);
+ notifyRoutesChangedToRouters(routersWithModifyAudioRoutingPermission,
+ changedRoutes);
+ if (!provider.mIsSystemRouteProvider) {
+ notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
+ changedRoutes);
+ } else if (prevInfo != null) {
+ notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
+ defaultRoute);
+ } // 'else' is handled as added routes
notifyRoutesChangedToManagers(managers, changedRoutes);
}
}
@@ -1223,6 +1272,15 @@
toOriginalRequestId(uniqueRequestId));
return;
}
+ if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
+ && !TextUtils.equals(route.getId(),
+ mSystemProvider.getDefaultRoute().getId())) {
+ Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
+ + route);
+ notifySessionCreationFailedToRouter(routerRecord,
+ toOriginalRequestId(uniqueRequestId));
+ return;
+ }
SessionCreationRequest request =
new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
@@ -1444,7 +1502,9 @@
if (service == null) {
return;
}
- notifySessionInfoChangedToRouters(getRouters(), sessionInfo);
+ notifySessionInfoChangedToRouters(getRouters(true), sessionInfo);
+ notifySessionInfoChangedToRouters(getRouters(false),
+ mSystemProvider.getDefaultSessionInfo());
return;
}
@@ -1565,7 +1625,7 @@
}
}
- private List<IMediaRouter2> getRouters() {
+ private List<IMediaRouter2> getAllRouters() {
final List<IMediaRouter2> routers = new ArrayList<>();
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
@@ -1579,6 +1639,23 @@
return routers;
}
+ private List<IMediaRouter2> getRouters(boolean hasModifyAudioRoutingPermission) {
+ final List<IMediaRouter2> routers = new ArrayList<>();
+ MediaRouter2ServiceImpl service = mServiceRef.get();
+ if (service == null) {
+ return routers;
+ }
+ synchronized (service.mLock) {
+ for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
+ if (hasModifyAudioRoutingPermission
+ == routerRecord.mHasModifyAudioRoutingPermission) {
+ routers.add(routerRecord.mRouter);
+ }
+ }
+ }
+ return routers;
+ }
+
private List<IMediaRouter2Manager> getManagers() {
final List<IMediaRouter2Manager> managers = new ArrayList<>();
MediaRouter2ServiceImpl service = mServiceRef.get();
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 5b16d68..6e2feeb 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -72,6 +72,7 @@
// This should be the currently selected route.
MediaRoute2Info mDefaultRoute;
MediaRoute2Info mDeviceRoute;
+ RoutingSessionInfo mDefaultSessionInfo;
final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
@@ -114,6 +115,7 @@
}
});
updateSessionInfosIfNeeded();
+
mContext.registerReceiver(new VolumeChangeReceiver(),
new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
@@ -156,6 +158,10 @@
@Override
public void transferToRoute(long requestId, String sessionId, String routeId) {
+ if (TextUtils.equals(routeId, DEFAULT_ROUTE_ID)) {
+ // The currently selected route is the default route.
+ return;
+ }
if (mBtRouteProvider != null) {
if (TextUtils.equals(routeId, mDeviceRoute.getId())) {
mBtRouteProvider.transferTo(null);
@@ -182,6 +188,10 @@
return mDefaultRoute;
}
+ public RoutingSessionInfo getDefaultSessionInfo() {
+ return mDefaultSessionInfo;
+ }
+
private void updateDeviceRoute(AudioRoutesInfo newRoutes) {
int name = R.string.default_audio_route_name;
if (newRoutes != null) {
@@ -229,8 +239,6 @@
*/
boolean updateSessionInfosIfNeeded() {
synchronized (mLock) {
- // Prevent to execute this method before mBtRouteProvider is created.
- if (mBtRouteProvider == null) return false;
RoutingSessionInfo oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get(
0);
@@ -238,14 +246,19 @@
SYSTEM_SESSION_ID, "" /* clientPackageName */)
.setSystemSession(true);
- MediaRoute2Info selectedRoute = mBtRouteProvider.getSelectedRoute();
- if (selectedRoute == null) {
- selectedRoute = mDeviceRoute;
- } else {
- builder.addTransferableRoute(mDeviceRoute.getId());
+ MediaRoute2Info selectedRoute = mDeviceRoute;
+ if (mBtRouteProvider != null) {
+ MediaRoute2Info selectedBtRoute = mBtRouteProvider.getSelectedRoute();
+ if (selectedBtRoute != null) {
+ selectedRoute = selectedBtRoute;
+ builder.addTransferableRoute(mDeviceRoute.getId());
+ }
}
mSelectedRouteId = selectedRoute.getId();
- mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute).build();
+ mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute)
+ .setSystemRoute(true)
+ .setProviderId(mUniqueId)
+ .build();
builder.addSelectedRoute(mSelectedRouteId);
for (MediaRoute2Info route : mBtRouteProvider.getTransferableRoutes()) {
@@ -258,6 +271,12 @@
} else {
mSessionInfos.clear();
mSessionInfos.add(newSessionInfo);
+ mDefaultSessionInfo = new RoutingSessionInfo.Builder(
+ SYSTEM_SESSION_ID, "" /* clientPackageName */)
+ .setProviderId(mUniqueId)
+ .setSystemSession(true)
+ .addSelectedRoute(DEFAULT_ROUTE_ID)
+ .build();
return true;
}
}
@@ -302,6 +321,9 @@
} else if (mBtRouteProvider != null) {
mBtRouteProvider.setSelectedRouteVolume(newVolume);
}
+ mDefaultRoute = new MediaRoute2Info.Builder(mDefaultRoute)
+ .setVolume(newVolume)
+ .build();
publishProviderState();
}
}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 1e4dc7b..daf4bf2 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -399,7 +399,9 @@
@VisibleForTesting
protected IApexService waitForApexService() {
try {
- return IApexService.Stub.asInterface(Binder.waitForService("apexservice"));
+ // Since apexd is a trusted platform component, synchronized calls are allowable
+ return IApexService.Stub.asInterface(
+ Binder.allowBlocking(Binder.waitForService("apexservice")));
} catch (RemoteException e) {
throw new IllegalStateException("Required service apexservice not available");
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c47d215..c4d0342 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6484,11 +6484,18 @@
final Rect containingBounds = mTmpBounds;
mCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
orientation, orientationRequested, canChangeOrientation);
- resolvedBounds.set(containingAppBounds);
+ resolvedBounds.set(containingBounds);
// The size of floating task is fixed (only swap), so the aspect ratio is already correct.
if (!mCompatDisplayInsets.mIsFloating) {
applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
}
+ // If the bounds are restricted by fixed aspect ratio, the resolved bounds should be put in
+ // the container app bounds. Otherwise the entire container bounds are available.
+ final boolean fillContainer = resolvedBounds.equals(containingBounds);
+ if (!fillContainer) {
+ // The horizontal position should not cover insets.
+ resolvedBounds.left = containingAppBounds.left;
+ }
// Use resolvedBounds to compute other override configurations such as appBounds. The bounds
// are calculated in compat container space. The actual position on screen will be applied
@@ -6557,7 +6564,7 @@
final int offsetX = getHorizontalCenterOffset(
(int) viewportW, (int) (contentW * mSizeCompatScale));
// Above coordinates are in "@" space, now place "*" and "#" to screen space.
- final int screenPosX = parentAppBounds.left + offsetX;
+ final int screenPosX = (fillContainer ? parentBounds.left : parentAppBounds.left) + offsetX;
final int screenPosY = parentBounds.top;
if (screenPosX != 0 || screenPosY != 0) {
if (mSizeCompatBounds != null) {
@@ -7654,8 +7661,6 @@
// Ensure the app bounds won't overlap with insets.
Task.intersectWithInsetsIfFits(outAppBounds, outBounds, mNonDecorInsets[rotation]);
}
- // The horizontal position is centered and it should not cover insets.
- outBounds.left = outAppBounds.left;
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 98d4b55..40243e8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3421,7 +3421,10 @@
private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
// Always update control target. This is needed to handle rotation.
- updateImeControlTarget(target);
+ // We cannot set target as the control target, because mInputMethodTarget can only help
+ // decide the z-order of IME, but cannot control IME. Only the IME target reported from
+ // updateInputMethodTargetWindow can control IME.
+ updateImeControlTarget(mInputMethodControlTarget);
if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8d3a4f2..dea2e96 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -32,6 +32,7 @@
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.content.pm.PackageManager.FEATURE_PC;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.myPid;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -2573,6 +2574,8 @@
String packageName, boolean fromClientToken) {
final boolean callerCanManageAppTokens =
checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()");
+ // WindowContext users usually don't hold MANAGE_APP_TOKEN permission. Check permissions
+ // by checkAddPermission.
if (!callerCanManageAppTokens) {
final int res = mPolicy.checkAddPermission(type, false /* isRoundedCornerOverlay */,
packageName, new int[1]);
@@ -2587,7 +2590,7 @@
synchronized (mGlobalLock) {
if (!callerCanManageAppTokens) {
if (packageName == null || !unprivilegedAppCanCreateTokenWith(
- null /* parentWindow */, callingUid, type, type, null /* tokenForLog */,
+ null /* parentWindow */, callingUid, type, type, binder,
packageName)) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
@@ -2612,7 +2615,7 @@
new WallpaperWindowToken(this, binder, true, dc, callerCanManageAppTokens);
} else {
new WindowToken(this, binder, type, true, dc, callerCanManageAppTokens,
- false /* roundedCornerOverlay */, fromClientToken);
+ callingUid, false /* roundedCornerOverlay */, fromClientToken);
}
}
} finally {
@@ -2635,8 +2638,25 @@
@Override
public void removeWindowToken(IBinder binder, int displayId) {
- if (!checkCallingPermission(MANAGE_APP_TOKENS, "removeWindowToken()")) {
- throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+ final boolean callerCanManageAppTokens =
+ checkCallingPermission(MANAGE_APP_TOKENS, "removeWindowToken()");
+ final WindowToken windowToken;
+ synchronized (mGlobalLock) {
+ windowToken = mRoot.getWindowToken(binder);
+ }
+ if (windowToken == null) {
+ ProtoLog.w(WM_ERROR,
+ "removeWindowToken: Attempted to remove non-existing token: %s", binder);
+ return;
+ }
+ final int callingUid = Binder.getCallingUid();
+
+ // If MANAGE_APP_TOKEN permission is not held(usually from WindowContext), callers can only
+ // remove the window tokens which they added themselves.
+ if (!callerCanManageAppTokens && (windowToken.getOwnerUid() == INVALID_UID
+ || callingUid != windowToken.getOwnerUid())) {
+ throw new SecurityException("removeWindowToken: Requires MANAGE_APP_TOKENS permission"
+ + " to remove token owned by another uid");
}
final long origId = Binder.clearCallingIdentity();
@@ -2649,14 +2669,7 @@
return;
}
- final WindowToken token = dc.removeWindowToken(binder);
- if (token == null) {
- ProtoLog.w(WM_ERROR,
- "removeWindowToken: Attempted to remove non-existing token: %s",
- binder);
- return;
- }
-
+ dc.removeWindowToken(binder);
dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
}
} finally {
@@ -8006,6 +8019,33 @@
}
}
+ /** Set layer tracing flags. */
+ public void setLayerTracingFlags(int flags) {
+ mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.DUMP,
+ "setLayerTracingFlags");
+ long token = Binder.clearCallingIdentity();
+ try {
+ Parcel data = null;
+ try {
+ IBinder sf = ServiceManager.getService("SurfaceFlinger");
+ if (sf != null) {
+ data = Parcel.obtain();
+ data.writeInterfaceToken("android.ui.ISurfaceComposer");
+ data.writeInt(flags);
+ sf.transact(1033 /* LAYER_TRACE_FLAGS_CODE */, data, null, 0 /* flags */);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set layer tracing flags");
+ } finally {
+ if (data != null) {
+ data.recycle();
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
public boolean mirrorDisplay(int displayId, SurfaceControl outSurfaceControl) {
if (!checkCallingPermission(READ_FRAME_BUFFER, "mirrorDisplay()")) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 23091a0..5976b48 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.os.Process.INVALID_UID;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -44,6 +45,7 @@
import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.InsetsState;
@@ -106,6 +108,11 @@
@VisibleForTesting
final boolean mFromClientToken;
+ private DeathRecipient mDeathRecipient;
+ private boolean mBinderDied = false;
+
+ private final int mOwnerUid;
+
/**
* Used to fix the transform of the token to be rotated to a rotation different than it's
* display. The window frames and surfaces corresponding to this token will be layouted and
@@ -165,6 +172,30 @@
}
}
+ private class DeathRecipient implements IBinder.DeathRecipient {
+ private boolean mHasUnlinkToDeath = false;
+
+ @Override
+ public void binderDied() {
+ synchronized (mWmService.mGlobalLock) {
+ mBinderDied = true;
+ removeImmediately();
+ }
+ }
+
+ void linkToDeath() throws RemoteException {
+ token.linkToDeath(DeathRecipient.this, 0);
+ }
+
+ void unlinkToDeath() {
+ if (mHasUnlinkToDeath) {
+ return;
+ }
+ token.unlinkToDeath(DeathRecipient.this, 0);
+ mHasUnlinkToDeath = true;
+ }
+ }
+
/**
* Compares two child window of this token and returns -1 if the first is lesser than the
* second in terms of z-order and 1 otherwise.
@@ -193,23 +224,35 @@
WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) {
- this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens,
+ this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens, INVALID_UID,
roundedCornerOverlay, false /* fromClientToken */);
}
WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
- DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay,
- boolean fromClientToken) {
+ DisplayContent dc, boolean ownerCanManageAppTokens, int ownerUid,
+ boolean roundedCornerOverlay, boolean fromClientToken) {
super(service);
token = _token;
windowType = type;
mPersistOnEmpty = persistOnEmpty;
mOwnerCanManageAppTokens = ownerCanManageAppTokens;
+ mOwnerUid = ownerUid;
mRoundedCornerOverlay = roundedCornerOverlay;
mFromClientToken = fromClientToken;
if (dc != null) {
dc.addWindowToken(token, this);
}
+ if (shouldReportToClient()) {
+ try {
+ mDeathRecipient = new DeathRecipient();
+ mDeathRecipient.linkToDeath();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to add window token with type " + windowType + " on "
+ + "display " + dc.getDisplayId(), e);
+ mDeathRecipient = null;
+ return;
+ }
+ }
}
void removeAllWindowsIfPossible() {
@@ -222,7 +265,7 @@
}
void setExiting() {
- if (mChildren.size() == 0) {
+ if (isEmpty()) {
super.removeImmediately();
return;
}
@@ -340,6 +383,21 @@
// Needs to occur after the token is removed from the display above to avoid attempt at
// duplicate removal of this window container from it's parent.
super.removeImmediately();
+
+ reportWindowTokenRemovedToClient();
+ }
+
+ private void reportWindowTokenRemovedToClient() {
+ if (!shouldReportToClient()) {
+ return;
+ }
+ mDeathRecipient.unlinkToDeath();
+ IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token);
+ try {
+ windowTokenClient.onWindowTokenRemoved();
+ } catch (RemoteException e) {
+ ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client.");
+ }
}
@Override
@@ -361,17 +419,9 @@
}
void reportConfigToWindowTokenClient() {
- if (asActivityRecord() != null) {
- // Activities are updated through ATM callbacks.
+ if (!shouldReportToClient()) {
return;
}
-
- // Unfortunately, this WindowToken is not from WindowContext so it cannot handle
- // its own configuration changes.
- if (!mFromClientToken) {
- return;
- }
-
final Configuration config = getConfiguration();
final int displayId = getDisplayContent().getDisplayId();
if (config.equals(mLastReportedConfig) && displayId == mLastReportedDisplay) {
@@ -383,16 +433,26 @@
mLastReportedDisplay = displayId;
IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token);
- if (windowTokenClient != null) {
- try {
- windowTokenClient.onConfigurationChanged(config, displayId);
- } catch (RemoteException e) {
- ProtoLog.w(WM_ERROR,
- "Could not report config changes to the window token client.");
- }
+ try {
+ windowTokenClient.onConfigurationChanged(config, displayId);
+ } catch (RemoteException e) {
+ ProtoLog.w(WM_ERROR,
+ "Could not report config changes to the window token client.");
}
}
+ /**
+ * @return {@code true} if this {@link WindowToken} is not an {@link ActivityRecord} and
+ * registered from client side.
+ */
+ private boolean shouldReportToClient() {
+ // Only report to client for WindowToken because Activities are updated through ATM
+ // callbacks.
+ return asActivityRecord() == null
+ // Report to {@link android.view.WindowTokenClient} if this token was registered from it.
+ && mFromClientToken && !mBinderDied;
+ }
+
@Override
void assignLayer(SurfaceControl.Transaction t, int layer) {
if (windowType == TYPE_DOCK_DIVIDER) {
@@ -616,4 +676,8 @@
int getWindowLayerFromType() {
return mWmService.mPolicy.getWindowLayerFromTypeLw(windowType, mOwnerCanManageAppTokens);
}
+
+ int getOwnerUid() {
+ return mOwnerUid;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 67e83ba..d1d353d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1836,8 +1836,9 @@
Bundle addSyntheticRestrictions(Bundle restrictions) {
if (disableCamera) {
restrictions.putBoolean(UserManager.DISALLOW_CAMERA, true);
- } else {
- restrictions.remove(UserManager.DISALLOW_CAMERA);
+ }
+ if (requireAutoTime) {
+ restrictions.putBoolean(UserManager.DISALLOW_CONFIG_DATE_TIME, true);
}
return restrictions;
}
@@ -1864,7 +1865,7 @@
Bundle getEffectiveRestrictions() {
return addSyntheticRestrictions(
- removeDeprecatedRestrictions(ensureUserRestrictions()));
+ removeDeprecatedRestrictions(new Bundle(ensureUserRestrictions())));
}
Bundle getLocalUserRestrictions(int adminType) {
@@ -2747,6 +2748,8 @@
// The following policies weren't available to PO, but will be available after migration.
parentAdmin.disableCamera = doAdmin.disableCamera;
+ parentAdmin.requireAutoTime = doAdmin.requireAutoTime;
+
// TODO(b/143516163): Uncomment once corresponding APIs are available via parent instance.
// parentAdmin.disableScreenCapture = doAdmin.disableScreenCapture;
// parentAdmin.accountTypesWithManagementDisabled.addAll(
@@ -7839,16 +7842,21 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final int userHandle = UserHandle.getCallingUserId();
+ boolean requireAutoTimeChanged = false;
synchronized (getLockObject()) {
ActiveAdmin admin = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
if (admin.requireAutoTime != required) {
admin.requireAutoTime = required;
saveSettingsLocked(userHandle);
+ requireAutoTimeChanged = true;
}
}
-
- // TODO: (b/145604635) Add upgrade case
+ // requireAutoTime is now backed by DISALLOW_CONFIG_DATE_TIME restriction, so propagate
+ // updated restrictions to the framework.
+ if (requireAutoTimeChanged) {
+ pushUserRestrictions(userHandle);
+ }
// Turn AUTO_TIME on in settings if it is required
if (required) {
mInjector.binderWithCleanCallingIdentity(
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index d1153e6..92366e5 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -51,7 +51,7 @@
namespace fs = std::filesystem;
constexpr const char* kDataUsageStats = "android.permission.LOADER_USAGE_STATS";
-constexpr const char* kOpUsage = "android:get_usage_stats";
+constexpr const char* kOpUsage = "android:loader_usage_stats";
namespace android::incremental {
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 0635ae1..18ae4b5 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -137,6 +137,7 @@
bool* _aidl_return) {
mId = mountId;
mListener = listener;
+ mServiceConnector = control.service;
*_aidl_return = true;
return binder::Status::ok();
}
@@ -166,9 +167,17 @@
mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_CREATED);
}
+ int32_t setStorageParams(bool enableReadLogs) {
+ int32_t result = -1;
+ EXPECT_NE(mServiceConnector.get(), nullptr);
+ EXPECT_TRUE(mServiceConnector->setStorageParams(enableReadLogs, &result).isOk());
+ return result;
+ }
+
private:
int mId;
sp<IDataLoaderStatusListener> mListener;
+ sp<IIncrementalServiceConnector> mServiceConnector;
sp<IDataLoader> mDataLoader = sp<IDataLoader>(new FakeDataLoader());
};
@@ -453,7 +462,7 @@
mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
IncrementalService::CreateOptions::CreateNew);
ASSERT_GE(storageId, 0);
- ASSERT_GE(mIncrementalService->setStorageParams(storageId, true), 0);
+ ASSERT_GE(mDataLoaderManager->setStorageParams(true), 0);
}
TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChanged) {
@@ -480,7 +489,7 @@
mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
IncrementalService::CreateOptions::CreateNew);
ASSERT_GE(storageId, 0);
- ASSERT_GE(mIncrementalService->setStorageParams(storageId, true), 0);
+ ASSERT_GE(mDataLoaderManager->setStorageParams(true), 0);
ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get());
mAppOpsManager->mStoredCallback->opChanged(0, {});
}
@@ -503,7 +512,7 @@
mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
IncrementalService::CreateOptions::CreateNew);
ASSERT_GE(storageId, 0);
- ASSERT_LT(mIncrementalService->setStorageParams(storageId, true), 0);
+ ASSERT_LT(mDataLoaderManager->setStorageParams(true), 0);
}
TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) {
@@ -526,7 +535,7 @@
mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
IncrementalService::CreateOptions::CreateNew);
ASSERT_GE(storageId, 0);
- ASSERT_LT(mIncrementalService->setStorageParams(storageId, true), 0);
+ ASSERT_LT(mDataLoaderManager->setStorageParams(true), 0);
}
TEST_F(IncrementalServiceTest, testMakeDirectory) {
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
index c225d3f..1aab672 100644
--- a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.systemcaptions;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
@@ -37,6 +39,8 @@
private static final String SERVICE_INTERFACE =
"android.service.systemcaptions.SystemCaptionsManagerService";
+ private static final int MSG_BIND = 1;
+
private final Object mLock = new Object();
private final Context mContext;
@@ -71,18 +75,26 @@
if (mVerbose) {
Slog.v(TAG, "initialize()");
}
- ensureBound();
+ scheduleBind();
}
- void destroy() {
+ /**
+ * Destroys this service.
+ */
+ public void destroy() {
+ mHandler.sendMessage(
+ obtainMessage(RemoteSystemCaptionsManagerService::handleDestroy, this));
+ }
+
+ void handleDestroy() {
if (mVerbose) {
- Slog.v(TAG, "destroy()");
+ Slog.v(TAG, "handleDestroy()");
}
synchronized (mLock) {
if (mDestroyed) {
if (mVerbose) {
- Slog.v(TAG, "destroy(): Already destroyed");
+ Slog.v(TAG, "handleDestroy(): Already destroyed");
}
return;
}
@@ -97,14 +109,24 @@
}
}
- private void ensureBound() {
+ private void scheduleBind() {
+ if (mHandler.hasMessages(MSG_BIND)) {
+ if (mVerbose) Slog.v(TAG, "scheduleBind(): already scheduled");
+ return;
+ }
+ mHandler.sendMessage(
+ obtainMessage(RemoteSystemCaptionsManagerService::handleEnsureBound, this)
+ .setWhat(MSG_BIND));
+ }
+
+ private void handleEnsureBound() {
synchronized (mLock) {
if (mService != null || mBinding) {
return;
}
if (mVerbose) {
- Slog.v(TAG, "ensureBound(): binding");
+ Slog.v(TAG, "handleEnsureBound(): binding");
}
mBinding = true;
diff --git a/services/tests/servicestests/res/raw/comp_policies_primary.xml b/services/tests/servicestests/res/raw/comp_policies_primary.xml
index d30f479..8b7709e 100644
--- a/services/tests/servicestests/res/raw/comp_policies_primary.xml
+++ b/services/tests/servicestests/res/raw/comp_policies_primary.xml
@@ -3,6 +3,7 @@
<admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
<policies flags="991"/>
<password-history-length value="33" />
+ <require_auto_time value="true" />
<user-restrictions no_bluetooth="true" />
</admin>
</policies>
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index de2addf..c9bd01a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -385,6 +385,15 @@
assertFalse("User restriction was put into non-parent PO instance",
dpm.getUserRestrictions(admin1).containsKey(UserManager.DISALLOW_BLUETOOTH));
+ assertTrue("User restriction wasn't migrated to PO parent instance",
+ dpms.getProfileOwnerAdminLocked(COPE_PROFILE_USER_ID)
+ .getParentActiveAdmin()
+ .getEffectiveRestrictions()
+ .containsKey(UserManager.DISALLOW_CONFIG_DATE_TIME));
+ assertFalse("User restriction was put into non-parent PO instance",
+ dpms.getProfileOwnerAdminLocked(COPE_PROFILE_USER_ID)
+ .getEffectiveRestrictions()
+ .containsKey(UserManager.DISALLOW_CONFIG_DATE_TIME));
// TODO(b/143516163): verify more policies.
});
}
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 d780370..fe224ce 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2032,13 +2032,17 @@
eq(false));
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(UserManager.DISALLOW_CAMERA),
- parentDpm.getUserRestrictions(admin1)
+ dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+ .getParentActiveAdmin()
+ .getEffectiveRestrictions()
);
parentDpm.setCameraDisabled(admin1, false);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(),
- parentDpm.getUserRestrictions(admin1)
+ dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+ .getParentActiveAdmin()
+ .getEffectiveRestrictions()
);
reset(getServices().userManagerInternal);
}
@@ -2053,7 +2057,9 @@
parentDpm.clearUserRestriction(admin1, restriction);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(),
- parentDpm.getUserRestrictions(admin1)
+ dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+ .getParentActiveAdmin()
+ .getEffectiveRestrictions()
);
}
@@ -2088,11 +2094,7 @@
private void assertNoDeviceOwnerRestrictions() {
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(),
- getDeviceOwner().ensureUserRestrictions()
- );
- DpmTestUtils.assertRestrictions(
- DpmTestUtils.newRestrictions(),
- dpm.getUserRestrictions(admin1)
+ getDeviceOwner().getEffectiveRestrictions()
);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6734694..e47792f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -243,6 +243,26 @@
}
@Test
+ public void testAspectRatioMatchParentBoundsAndImeAttachable() {
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2000)
+ .setSystemDecorations(true).build());
+ prepareUnresizable(2f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+ assertFitted();
+
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+ mActivity.mDisplayContent.mInputMethodTarget = addWindowToActivity(mActivity);
+ // Because the aspect ratio of display doesn't exceed the max aspect ratio of activity.
+ // The activity should still fill its parent container and IME can attach to the activity.
+ assertTrue(mActivity.matchParentBounds());
+ assertTrue(mActivity.mDisplayContent.isImeAttachedToApp());
+
+ final Rect letterboxInnerBounds = new Rect();
+ mActivity.getLetterboxInnerBounds(letterboxInnerBounds);
+ // The activity should not have letterbox.
+ assertTrue(letterboxInnerBounds.isEmpty());
+ }
+
+ @Test
public void testMoveToDifferentOrientDisplay() {
setUpDisplaySizeWithApp(1000, 2500);
prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 57c7504..d2a2732 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -139,6 +139,7 @@
spyOn(displayPolicy);
if (mSystemDecorations) {
doReturn(true).when(newDisplay).supportsSystemDecorations();
+ doReturn(true).when(displayPolicy).hasNavigationBar();
} else {
doReturn(false).when(displayPolicy).hasNavigationBar();
doReturn(false).when(displayPolicy).hasStatusBar();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index da4bde5..79b9ae1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -16,13 +16,18 @@
package com.android.server.wm;
+import static android.os.Process.INVALID_UID;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.content.pm.PackageManager;
import android.os.IBinder;
@@ -85,4 +90,19 @@
assertFalse(windowToken.mRoundedCornerOverlay);
assertTrue(windowToken.mFromClientToken);
}
+
+ @Test(expected = SecurityException.class)
+ public void testRemoveWindowToken_ownerUidNotMatch_throwException() {
+ IBinder token = mock(IBinder.class);
+ mWm.addWindowTokenWithOptions(token, TYPE_TOAST, mDisplayContent.getDisplayId(),
+ null /* options */, null /* options */);
+
+ spyOn(mWm);
+ when(mWm.checkCallingPermission(anyString(), anyString())).thenReturn(false);
+ WindowToken windowToken = mWm.mRoot.getWindowToken(token);
+ spyOn(windowToken);
+ when(windowToken.getOwnerUid()).thenReturn(INVALID_UID);
+
+ mWm.removeWindowToken(token, mDisplayContent.getDisplayId());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 7a347cb..76479cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.os.Process.INVALID_UID;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -152,7 +153,7 @@
token = new WindowToken(mDisplayContent.mWmService, mock(IBinder.class), TYPE_TOAST,
true /* persistOnEmpty */, mDisplayContent, true /* ownerCanManageAppTokens */,
- true /* roundedCornerOverlay */, true /* fromClientToken */);
+ INVALID_UID, true /* roundedCornerOverlay */, true /* fromClientToken */);
assertTrue(token.mRoundedCornerOverlay);
assertTrue(token.mFromClientToken);
}
@@ -166,7 +167,7 @@
public void testSurfaceCreatedForWindowToken() {
final WindowToken fromClientToken = new WindowToken(mDisplayContent.mWmService,
mock(IBinder.class), TYPE_APPLICATION_OVERLAY, true /* persistOnEmpty */,
- mDisplayContent, true /* ownerCanManageAppTokens */,
+ mDisplayContent, true /* ownerCanManageAppTokens */, INVALID_UID,
true /* roundedCornerOverlay */, true /* fromClientToken */);
assertNull(fromClientToken.mSurfaceControl);
@@ -175,7 +176,7 @@
final WindowToken nonClientToken = new WindowToken(mDisplayContent.mWmService,
mock(IBinder.class), TYPE_TOAST, true /* persistOnEmpty */, mDisplayContent,
- true /* ownerCanManageAppTokens */, true /* roundedCornerOverlay */,
+ true /* ownerCanManageAppTokens */, INVALID_UID, true /* roundedCornerOverlay */,
false /* fromClientToken */);
assertNotNull(nonClientToken.mSurfaceControl);
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d6cdaa6..f623649 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -147,6 +147,10 @@
* information unless it has the appropriate permissions declared in
* its manifest file. Where permissions apply, they are noted in the
* the methods through which you access the protected information.
+ *
+ * <p>TelephonyManager is intended for use on devices that implement
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices
+ * that do not implement this feature, the behavior is not reliable.
*/
@SystemService(Context.TELEPHONY_SERVICE)
public class TelephonyManager {
@@ -5842,6 +5846,10 @@
* {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
* for each active subscription.
*
+ * <p>This method returns valid data for devices with
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices
+ * that do not implement this feature, the behavior is not reliable.
+ *
* @param executor the executor on which callback will be invoked.
* @param callback a callback to receive CellInfo.
*/
@@ -5888,6 +5896,10 @@
* {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
* for each active subscription.
*
+ * <p>This method returns valid data for devices with
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices
+ * that do not implement this feature, the behavior is not reliable.
+ *
* @param workSource the requestor to whom the power consumption for this should be attributed.
* @param executor the executor on which callback will be invoked.
* @param callback a callback to receive CellInfo.
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 4c2a984..737665f 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -17,6 +17,7 @@
package com.android.framework.permission.tests;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.os.Binder;
import android.os.RemoteException;
@@ -27,6 +28,8 @@
import junit.framework.TestCase;
+import org.junit.Test;
+
/**
* TODO: Remove this. This is only a placeholder, need to implement this.
*/
@@ -53,7 +56,7 @@
}
try {
- mWm.addWindowToken(null, 0, DEFAULT_DISPLAY);
+ mWm.addWindowToken(null, TYPE_APPLICATION, DEFAULT_DISPLAY);
fail("IWindowManager.addWindowToken did not throw SecurityException as"
+ " expected");
} catch (SecurityException e) {
@@ -63,16 +66,6 @@
}
try {
- mWm.removeWindowToken(null, DEFAULT_DISPLAY);
- fail("IWindowManager.removeWindowToken did not throw SecurityException as"
- + " expected");
- } catch (SecurityException e) {
- // expected
- } catch (RemoteException e) {
- fail("Unexpected remote exception");
- }
-
- try {
mWm.prepareAppTransition(0, false);
fail("IWindowManager.prepareAppTransition did not throw SecurityException as"
+ " expected");
@@ -182,4 +175,29 @@
fail("Unexpected remote exception");
}
}
+
+ @Test
+ public void testADD_WINDOW_TOKEN_WITH_OPTIONS() {
+ // Verify if addWindowTokenWithOptions throw SecurityException for privileged window type.
+ try {
+ mWm.addWindowTokenWithOptions(null, TYPE_APPLICATION, DEFAULT_DISPLAY, null, "");
+ fail("IWindowManager.addWindowTokenWithOptions did not throw SecurityException as"
+ + " expected");
+ } catch (SecurityException e) {
+ // expected
+ } catch (RemoteException e) {
+ fail("Unexpected remote exception");
+ }
+
+ // Verify if addWindowTokenWithOptions throw SecurityException for null packageName.
+ try {
+ mWm.addWindowTokenWithOptions(null, TYPE_APPLICATION, DEFAULT_DISPLAY, null, null);
+ fail("IWindowManager.addWindowTokenWithOptions did not throw SecurityException as"
+ + " expected");
+ } catch (SecurityException e) {
+ // expected
+ } catch (RemoteException e) {
+ fail("Unexpected remote exception");
+ }
+ }
}
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index 3026e0b..0dd45ba 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -47,7 +47,8 @@
"android.view.InsetsSourceConsumerTest",
"android.view.InsetsStateTest",
"android.view.WindowMetricsTest",
- "android.view.PendingInsetsControllerTest"
+ "android.view.PendingInsetsControllerTest",
+ "android.app.WindowContextTest"
};
public FrameworksTestsFilter(Bundle testArgs) {
diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp
index 23a0f72..5fe9498 100644
--- a/tools/stats_log_api_gen/atoms_info_writer.cpp
+++ b/tools/stats_log_api_gen/atoms_info_writer.cpp
@@ -42,7 +42,6 @@
fprintf(out,
" const static std::set<int> "
"kTruncatingTimestampAtomBlackList;\n");
- fprintf(out, " const static std::map<int, int> kAtomsWithUidField;\n");
fprintf(out, " const static std::set<int> kAtomsWithAttributionChain;\n");
fprintf(out,
" const static std::map<int, StateAtomFieldOptions> "
@@ -101,28 +100,6 @@
fprintf(out, "};\n");
fprintf(out, "\n");
- fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
- fprintf(out, " std::map<int, int> uidField;\n");
- for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end();
- atomIt++) {
- if ((*atomIt)->uidField == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding uid field for atom "
- "(%d)%s\n",
- (*atomIt)->code, (*atomIt)->name.c_str());
- fprintf(out, " uidField[%d /* %s */] = %d;\n", (*atomIt)->code,
- make_constant_name((*atomIt)->name).c_str(), (*atomIt)->uidField);
- }
-
- fprintf(out, " return uidField;\n");
- fprintf(out, "};\n");
-
- fprintf(out,
- "const std::map<int, int> AtomsInfo::kAtomsWithUidField = "
- "getAtomUidField();\n");
-
fprintf(out,
"static std::map<int, StateAtomFieldOptions> "
"getStateAtomFieldOptions() {\n");
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
index eeb006e..cf11f43 100644
--- a/wifi/jarjar-rules.txt
+++ b/wifi/jarjar-rules.txt
@@ -53,6 +53,9 @@
rule com.android.internal.messages.SystemMessageProto* com.android.server.x.wifi.messages.SystemMessageProto@1
# Use our statically linked PlatformProperties library
rule android.sysprop.** com.android.server.x.wifi.sysprop.@1
+# Use our statically linked HIDL stubs
+rule android.hardware.** com.android.server.x.wifi.hardware.@1
+rule android.hidl.** com.android.server.x.wifi.hidl.@1
# used by both framework-wifi and wifi-service
rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1