Merge "Upgrade case for setAutoTimeRequired" into rvc-dev
diff --git a/Android.bp b/Android.bp
index 874d76f..d4ca706 100644
--- a/Android.bp
+++ b/Android.bp
@@ -911,6 +911,7 @@
filegroup {
name: "incremental_aidl",
srcs: [
+ "core/java/android/os/incremental/IIncrementalServiceConnector.aidl",
"core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
],
path: "core/java",
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/apex/statsd/Android.bp b/apex/statsd/Android.bp
index 32e13e3..15d7495 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -67,7 +67,6 @@
"liblog", // Has a stable abi - should not be copied into apex.
"libstatssocket",
],
- //TODO: is libc++_static correct?
stl: "libc++_static",
cflags: [
"-Wall",
diff --git a/apex/statsd/tests/libstatspull/Android.bp b/apex/statsd/tests/libstatspull/Android.bp
index 2d64f19..0df96e1 100644
--- a/apex/statsd/tests/libstatspull/Android.bp
+++ b/apex/statsd/tests/libstatspull/Android.bp
@@ -32,7 +32,7 @@
"protos/**/*.proto",
],
test_suites: [
- "general-tests",
+ "device-tests",
],
platform_apis: true,
privileged: true,
diff --git a/api/test-current.txt b/api/test-current.txt
index 5de6c2a..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";
@@ -4860,7 +4861,9 @@
}
public final class SurfaceControl implements android.os.Parcelable {
+ ctor public SurfaceControl(@NonNull android.view.SurfaceControl);
method public static long acquireFrameRateFlexibilityToken();
+ method public boolean isSameSurface(@NonNull android.view.SurfaceControl);
method public static void releaseFrameRateFlexibilityToken(long);
}
@@ -5213,10 +5216,10 @@
method public void onDisplayAreaAppeared(@NonNull android.window.WindowContainerToken);
method public void onDisplayAreaVanished(@NonNull android.window.WindowContainerToken);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void registerOrganizer(int);
+ field public static final int FEATURE_DEFAULT_TASK_CONTAINER = 1; // 0x1
field public static final int FEATURE_ROOT = 0; // 0x0
field public static final int FEATURE_SYSTEM_FIRST = 0; // 0x0
field public static final int FEATURE_SYSTEM_LAST = 10000; // 0x2710
- field public static final int FEATURE_TASK_CONTAINER = 1; // 0x1
field public static final int FEATURE_UNDEFINED = -1; // 0xffffffff
field public static final int FEATURE_VENDOR_FIRST = 10001; // 0x2711
field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index d3d7e1d..65061d0 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -113,17 +113,18 @@
static_libs: [
"libbase",
"libcutils",
+ "libgtest_prod",
"libprotoutil",
"libstatsmetadata",
"libstatslog_statsd",
"libsysutils",
"libutils",
+ "statsd-aidl-ndk_platform",
],
shared_libs: [
"libbinder_ndk",
"libincident",
"liblog",
- "statsd-aidl-ndk_platform",
],
}
@@ -268,10 +269,11 @@
proto: {
type: "lite",
+ static: true,
},
+ stl: "libc++_static",
shared_libs: [
- "libgtest_prod",
"libstatssocket",
],
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 bd15264..2a1716e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -418,7 +418,8 @@
AppStandbyBucketChanged app_standby_bucket_changed = 258 [(module) = "framework"];
SharesheetStarted sharesheet_started = 259 [(module) = "framework"];
RankingSelected ranking_selected = 260 [(module) = "framework"];
- TvSettingsUIInteracted tvsettings_ui_interacted = 261;
+ 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/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 6d9c644..bbae3fe 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -43,20 +43,23 @@
using std::unique_ptr;
struct ConfigReceiverDeathCookie {
- ConfigReceiverDeathCookie(sp<ConfigManager> configManager, const ConfigKey& configKey,
- const shared_ptr<IPendingIntentRef>& pir):
- mConfigManager(configManager),
- mConfigKey(configKey),
- mPir(pir) {}
+ ConfigReceiverDeathCookie(const wp<ConfigManager>& configManager, const ConfigKey& configKey,
+ const shared_ptr<IPendingIntentRef>& pir) :
+ mConfigManager(configManager), mConfigKey(configKey), mPir(pir) {
+ }
- sp<ConfigManager> mConfigManager;
+ wp<ConfigManager> mConfigManager;
ConfigKey mConfigKey;
shared_ptr<IPendingIntentRef> mPir;
};
void ConfigManager::configReceiverDied(void* cookie) {
auto cookie_ = static_cast<ConfigReceiverDeathCookie*>(cookie);
- sp<ConfigManager>& thiz = cookie_->mConfigManager;
+ sp<ConfigManager> thiz = cookie_->mConfigManager.promote();
+ if (!thiz) {
+ return;
+ }
+
ConfigKey& configKey = cookie_->mConfigKey;
shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
@@ -74,20 +77,23 @@
}
struct ActiveConfigChangedReceiverDeathCookie {
- ActiveConfigChangedReceiverDeathCookie(sp<ConfigManager> configManager, const int uid,
- const shared_ptr<IPendingIntentRef>& pir):
- mConfigManager(configManager),
- mUid(uid),
- mPir(pir) {}
+ ActiveConfigChangedReceiverDeathCookie(const wp<ConfigManager>& configManager, const int uid,
+ const shared_ptr<IPendingIntentRef>& pir) :
+ mConfigManager(configManager), mUid(uid), mPir(pir) {
+ }
- sp<ConfigManager> mConfigManager;
+ wp<ConfigManager> mConfigManager;
int mUid;
shared_ptr<IPendingIntentRef> mPir;
};
void ConfigManager::activeConfigChangedReceiverDied(void* cookie) {
auto cookie_ = static_cast<ActiveConfigChangedReceiverDeathCookie*>(cookie);
- sp<ConfigManager>& thiz = cookie_->mConfigManager;
+ sp<ConfigManager> thiz = cookie_->mConfigManager.promote();
+ if (!thiz) {
+ return;
+ }
+
int uid = cookie_->mUid;
shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 79a7e8d..ebe9610 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -44,19 +44,23 @@
// Stores the puller as a wp to avoid holding a reference in case it is unregistered and
// pullAtomCallbackDied is never called.
struct PullAtomCallbackDeathCookie {
- PullAtomCallbackDeathCookie(sp<StatsPullerManager> pullerManager, const PullerKey& pullerKey,
- const wp<StatsPuller>& puller)
- : mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
+ PullAtomCallbackDeathCookie(const wp<StatsPullerManager>& pullerManager,
+ const PullerKey& pullerKey, const wp<StatsPuller>& puller) :
+ mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
}
- sp<StatsPullerManager> mPullerManager;
+ wp<StatsPullerManager> mPullerManager;
PullerKey mPullerKey;
wp<StatsPuller> mPuller;
};
void StatsPullerManager::pullAtomCallbackDied(void* cookie) {
PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie);
- sp<StatsPullerManager>& thiz = cookie_->mPullerManager;
+ sp<StatsPullerManager> thiz = cookie_->mPullerManager.promote();
+ if (!thiz) {
+ return;
+ }
+
const PullerKey& pullerKey = cookie_->mPullerKey;
wp<StatsPuller> puller = cookie_->mPuller;
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/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
index 6aff9ef..4b9bac1 100644
--- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
@@ -190,13 +190,13 @@
int32_t uid = 123;
values.push_back(value);
- StatsPullerManager pullerManager;
- pullerManager.RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs,
- vector<int32_t>(), cb);
+ sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+ pullerManager->RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs,
+ vector<int32_t>(), cb);
vector<shared_ptr<LogEvent>> dataHolder;
int64_t startTimeNs = getElapsedRealtimeNs();
// Returns false, since StatsPuller code will evaluate the timeout.
- EXPECT_FALSE(pullerManager.Pull(pullTagId, {uid}, &dataHolder));
+ EXPECT_FALSE(pullerManager->Pull(pullTagId, {uid}, &dataHolder));
int64_t endTimeNs = getElapsedRealtimeNs();
int64_t actualPullDurationNs = endTimeNs - startTimeNs;
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/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index 2a7cfd3..d5da0b4 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -68,6 +68,7 @@
};
private static final String[] DEFAULT_PULL_SOURCES = {
"AID_SYSTEM",
+ "AID_RADIO"
};
private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName());
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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 91a8572..e599a5c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1658,6 +1658,9 @@
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
final int[] gates = ctx.mServiceInitializationStateArray;
+ boolean interrupted = false;
+
+ T ret = null;
for (;;) {
boolean doInitialize = false;
@@ -1665,7 +1668,8 @@
// Return it if we already have a cached instance.
T service = (T) cache[mCacheIndex];
if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
- return service;
+ ret = service;
+ break; // exit the for (;;)
}
// If we get here, there's no cached instance.
@@ -1708,24 +1712,33 @@
cache.notifyAll();
}
}
- return service;
+ ret = service;
+ break; // exit the for (;;)
}
// The other threads will wait for the first thread to call notifyAll(),
// and go back to the top and retry.
synchronized (cache) {
+ // Repeat until the state becomes STATE_READY or STATE_NOT_FOUND.
+ // We can't respond to interrupts here; just like we can't in the "doInitialize"
+ // path, so we remember the interrupt state here and re-interrupt later.
while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
try {
+ // Clear the interrupt state.
+ interrupted |= Thread.interrupted();
cache.wait();
} catch (InterruptedException e) {
// This shouldn't normally happen, but if someone interrupts the
// thread, it will.
- Slog.wtf(TAG, "getService() interrupted");
- Thread.currentThread().interrupt();
- return null;
+ Slog.w(TAG, "getService() interrupted");
+ interrupted = true;
}
}
}
}
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ return ret;
}
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
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/content/pm/FileSystemControlParcel.aidl b/core/java/android/content/pm/FileSystemControlParcel.aidl
index f00feae..92df16c 100644
--- a/core/java/android/content/pm/FileSystemControlParcel.aidl
+++ b/core/java/android/content/pm/FileSystemControlParcel.aidl
@@ -17,6 +17,7 @@
package android.content.pm;
import android.content.pm.IPackageInstallerSessionFileSystemConnector;
+import android.os.incremental.IIncrementalServiceConnector;
import android.os.incremental.IncrementalFileSystemControlParcel;
/**
@@ -26,6 +27,8 @@
parcelable FileSystemControlParcel {
// Incremental FS control descriptors.
@nullable IncrementalFileSystemControlParcel incremental;
+ // Incremental FS service.
+ @nullable IIncrementalServiceConnector service;
// Callback-based installation connector.
@nullable IPackageInstallerSessionFileSystemConnector callback;
}
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/os/Process.java b/core/java/android/os/Process.java
index b7b3c4f..5d2c9d1 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -607,6 +607,9 @@
* started.
* @param pkgDataInfoMap Map from related package names to private data directory
* volume UUID and inode number.
+ * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory
+ * volume UUID and inode number.
+ * @param bindMountAppsData whether zygote needs to mount CE and DE data.
* @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
* @param zygoteArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
@@ -631,13 +634,17 @@
@Nullable long[] disabledCompatChanges,
@Nullable Map<String, Pair<String, Long>>
pkgDataInfoMap,
+ @Nullable Map<String, Pair<String, Long>>
+ whitelistedDataInfoMap,
+ boolean bindMountAppsData,
boolean bindMountAppStorageDirs,
@Nullable String[] zygoteArgs) {
return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
zygotePolicyFlags, isTopApp, disabledCompatChanges,
- pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs);
+ pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
+ bindMountAppStorageDirs, zygoteArgs);
}
/** @hide */
@@ -661,7 +668,8 @@
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
/*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
- disabledCompatChanges, /* pkgDataInfoMap */ null, false, zygoteArgs);
+ disabledCompatChanges, /* pkgDataInfoMap */ null,
+ /* whitelistedDataInfoMap */ null, false, false, zygoteArgs);
}
/**
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 5f3f14f..a4c99c0 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -333,6 +333,9 @@
* started.
* @param pkgDataInfoMap Map from related package names to private data directory
* volume UUID and inode number.
+ * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory
+ * volume UUID and inode number.
+ * @param bindMountAppsData whether zygote needs to mount CE and DE data.
* @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
*
* @param zygoteArgs Additional arguments to supply to the Zygote process.
@@ -355,6 +358,9 @@
@Nullable long[] disabledCompatChanges,
@Nullable Map<String, Pair<String, Long>>
pkgDataInfoMap,
+ @Nullable Map<String, Pair<String, Long>>
+ whitelistedDataInfoMap,
+ boolean bindMountAppsData,
boolean bindMountAppStorageDirs,
@Nullable String[] zygoteArgs) {
// TODO (chriswailes): Is there a better place to check this value?
@@ -367,7 +373,8 @@
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,
- pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs);
+ pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
+ bindMountAppStorageDirs, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -608,6 +615,9 @@
* @param disabledCompatChanges a list of disabled compat changes for the process being started.
* @param pkgDataInfoMap Map from related package names to private data directory volume UUID
* and inode number.
+ * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory
+ * volume UUID and inode number.
+ * @param bindMountAppsData whether zygote needs to mount CE and DE data.
* @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
* @param extraArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
@@ -631,6 +641,9 @@
@Nullable long[] disabledCompatChanges,
@Nullable Map<String, Pair<String, Long>>
pkgDataInfoMap,
+ @Nullable Map<String, Pair<String, Long>>
+ whitelistedDataInfoMap,
+ boolean bindMountAppsData,
boolean bindMountAppStorageDirs,
@Nullable String[] extraArgs)
throws ZygoteStartFailedEx {
@@ -728,11 +741,33 @@
}
argsForZygote.add(sb.toString());
}
+ if (whitelistedDataInfoMap != null && whitelistedDataInfoMap.size() > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Zygote.WHITELISTED_DATA_INFO_MAP);
+ sb.append("=");
+ boolean started = false;
+ for (Map.Entry<String, Pair<String, Long>> entry : whitelistedDataInfoMap.entrySet()) {
+ if (started) {
+ sb.append(',');
+ }
+ started = true;
+ sb.append(entry.getKey());
+ sb.append(',');
+ sb.append(entry.getValue().first);
+ sb.append(',');
+ sb.append(entry.getValue().second);
+ }
+ argsForZygote.add(sb.toString());
+ }
if (bindMountAppStorageDirs) {
argsForZygote.add(Zygote.BIND_MOUNT_APP_STORAGE_DIRS);
}
+ if (bindMountAppsData) {
+ argsForZygote.add(Zygote.BIND_MOUNT_APP_DATA_DIRS);
+ }
+
if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
StringBuilder sb = new StringBuilder();
sb.append("--disabled-compat-changes=");
@@ -1291,6 +1326,7 @@
true /* startChildZygote */, null /* packageName */,
ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */,
null /* disabledCompatChanges */, null /* pkgDataInfoMap */,
+ null /* whitelistedDataInfoMap */, false /* bindMountAppsData*/,
/* bindMountAppStorageDirs */ false, extraArgs);
} catch (ZygoteStartFailedEx ex) {
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index d8308c7..2dbaea8 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -38,13 +38,6 @@
int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
/**
- * Changes storage params. Returns 0 on success, and -errno on failure.
- * Use enableReadLogs to switch pages read logs reporting on and off.
- * Returns 0 on success, and - errno on failure: permission check or remount.
- */
- int setStorageParams(int storageId, boolean enableReadLogs);
-
- /**
* Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
*/
const int BIND_TEMPORARY = 0;
diff --git a/core/java/android/os/incremental/IIncrementalServiceConnector.aidl b/core/java/android/os/incremental/IIncrementalServiceConnector.aidl
new file mode 100644
index 0000000..5800ecf
--- /dev/null
+++ b/core/java/android/os/incremental/IIncrementalServiceConnector.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.incremental;
+
+/** @hide */
+interface IIncrementalServiceConnector {
+ /**
+ * Changes storage params. Returns 0 on success, and -errno on failure.
+ * Use enableReadLogs to switch pages read logs reporting on and off.
+ * Returns 0 on success, and - errno on failure: permission check or remount.
+ */
+ int setStorageParams(boolean enableReadLogs);
+}
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 5f01408..35518db 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -19,13 +19,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.DataLoaderParams;
import android.content.pm.IDataLoaderStatusListener;
import android.os.RemoteException;
-import android.system.ErrnoException;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -321,23 +319,6 @@
return nativeUnsafeGetFileSignature(path);
}
- /**
- * Sets storage parameters.
- *
- * @param enableReadLogs - enables or disables read logs. Caller has to have a permission.
- */
- @RequiresPermission(android.Manifest.permission.LOADER_USAGE_STATS)
- public void setStorageParams(int storageId, boolean enableReadLogs) throws ErrnoException {
- try {
- int res = mService.setStorageParams(storageId, enableReadLogs);
- if (res < 0) {
- throw new ErrnoException("setStorageParams", -res);
- }
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
-
/* Native methods */
private static native boolean nativeIsEnabled();
private static native boolean nativeIsIncrementalPath(@NonNull String path);
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index 05877a5..c047dc0 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -21,7 +21,6 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.Service;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.DataLoaderParams;
import android.content.pm.DataLoaderParamsParcel;
@@ -32,8 +31,6 @@
import android.content.pm.InstallationFileParcel;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
-import android.os.incremental.IncrementalManager;
-import android.system.ErrnoException;
import android.util.ExceptionUtils;
import android.util.Slog;
@@ -211,25 +208,6 @@
private final long mNativeInstance;
}
- /* Used by native FileSystemConnector. */
- private boolean setStorageParams(int storageId, boolean enableReadLogs) {
- IncrementalManager incrementalManager = (IncrementalManager) getSystemService(
- Context.INCREMENTAL_SERVICE);
- if (incrementalManager == null) {
- Slog.e(TAG, "Failed to obtain incrementalManager: " + storageId);
- return false;
- }
- try {
- // This has to be done directly in incrementalManager as the storage
- // might be missing still.
- incrementalManager.setStorageParams(storageId, enableReadLogs);
- } catch (ErrnoException e) {
- Slog.e(TAG, "Failed to set params for storage: " + storageId, e);
- return false;
- }
- return true;
- }
-
/* Native methods */
private native boolean nativeCreateDataLoader(int storageId,
@NonNull FileSystemControlParcel control,
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 84ac90b..5648adf 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);
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 69bab4d..6cb9374 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -91,6 +91,8 @@
if (mSourceControl == control) {
return;
}
+ SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null;
+
final InsetsSourceControl lastControl = mSourceControl;
mSourceControl = control;
@@ -116,6 +118,12 @@
// However make sure that the leash visibility is still up to date.
if (applyLocalVisibilityOverride()) {
mController.notifyVisibilityChanged();
+ }
+
+ // If we have a new leash, make sure visibility is up-to-date, even though we
+ // didn't want to run an animation above.
+ SurfaceControl newLeash = mSourceControl.getLeash();
+ if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) {
applyHiddenToControl();
}
}
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index f3ec65f..e001b66 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -44,8 +44,7 @@
public InsetsSourceControl(InsetsSourceControl other) {
mType = other.mType;
if (other.mLeash != null) {
- mLeash = new SurfaceControl();
- mLeash.copyFrom(other.mLeash);
+ mLeash = new SurfaceControl(other.mLeash);
} else {
mLeash = null;
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b5f9df7..1086774 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -216,6 +216,7 @@
private static native void nativeSetFrameRate(
long transactionObj, long nativeObject, float frameRate, int compatibility);
+ private static native long nativeGetHandle(long nativeObject);
private static native long nativeAcquireFrameRateFlexibilityToken();
private static native void nativeReleaseFrameRateFlexibilityToken(long token);
@@ -226,6 +227,7 @@
* @hide
*/
public long mNativeObject;
+ private long mNativeHandle;
// TODO: Move this to native.
private final Object mSizeLock = new Object();
@@ -428,12 +430,13 @@
mCloseGuard.open("release");
}
mNativeObject = nativeObject;
+ mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0;
}
/**
* @hide
*/
- public void copyFrom(SurfaceControl other) {
+ public void copyFrom(@NonNull SurfaceControl other) {
mName = other.mName;
mWidth = other.mWidth;
mHeight = other.mHeight;
@@ -853,23 +856,19 @@
throw new OutOfResourcesException(
"Couldn't allocate SurfaceControl native object");
}
-
+ mNativeHandle = nativeGetHandle(mNativeObject);
mCloseGuard.open("release");
}
- /** This is a transfer constructor, useful for transferring a live SurfaceControl native
- * object to another Java wrapper which could have some different behavior, e.g.
- * event logging.
+ /**
+ * Copy constructor. Creates a new native object pointing to the same surface as {@code other}.
+ *
+ * @param other The object to copy the surface from.
* @hide
*/
- public SurfaceControl(SurfaceControl other) {
- mName = other.mName;
- mWidth = other.mWidth;
- mHeight = other.mHeight;
- mNativeObject = other.mNativeObject;
- other.mCloseGuard.close();
- other.mNativeObject = 0;
- mCloseGuard.open("release");
+ @TestApi
+ public SurfaceControl(@NonNull SurfaceControl other) {
+ copyFrom(other);
}
private SurfaceControl(Parcel in) {
@@ -921,6 +920,18 @@
}
/**
+ * Checks whether two {@link SurfaceControl} objects represent the same surface.
+ *
+ * @param other The other object to check
+ * @return {@code true} if these two {@link SurfaceControl} objects represent the same surface.
+ * @hide
+ */
+ @TestApi
+ public boolean isSameSurface(@NonNull SurfaceControl other) {
+ return other.mNativeHandle == mNativeHandle;
+ }
+
+ /**
* Write to a protocol buffer output stream. Protocol buffer message definition is at {@link
* android.view.SurfaceControlProto}.
*
@@ -977,6 +988,7 @@
if (mNativeObject != 0) {
nativeRelease(mNativeObject);
mNativeObject = 0;
+ mNativeHandle = 0;
mCloseGuard.close();
}
}
@@ -2876,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/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index eee222b..6ae70b7 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -33,8 +33,8 @@
public static final int FEATURE_SYSTEM_FIRST = 0;
// The Root display area on a display
public static final int FEATURE_ROOT = FEATURE_SYSTEM_FIRST;
- // Display area hosting the task container.
- public static final int FEATURE_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1;
+ // Display area hosting the default task container.
+ public static final int FEATURE_DEFAULT_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1;
// Display area hosting non-activity window tokens.
public static final int FEATURE_WINDOW_TOKENS = FEATURE_SYSTEM_FIRST + 2;
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index ff03f1a..505a05e 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -205,9 +205,15 @@
/** List of packages with the same uid, and its app data info: volume uuid and inode. */
public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";
+ /** List of whitelisted packages and its app data info: volume uuid and inode. */
+ public static final String WHITELISTED_DATA_INFO_MAP = "--whitelisted-data-info-map";
+
/** Bind mount app storage dirs to lower fs not via fuse */
public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs";
+ /** Bind mount app storage dirs to lower fs not via fuse */
+ public static final String BIND_MOUNT_APP_DATA_DIRS = "--bind-mount-data-dirs";
+
/**
* An extraArg passed when a zygote process is forking a child-zygote, specifying a name
* in the abstract socket namespace. This socket name is what the new child zygote
@@ -313,6 +319,8 @@
* @param isTopApp true if the process is for top (high priority) application.
* @param pkgDataInfoList A list that stores related packages and its app data
* info: volume uuid and inode.
+ * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for whitelisted apps.
+ * @param bindMountAppDataDirs True if the zygote needs to mount data dirs.
* @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs.
*
* @return 0 if this is the child, pid of the child
@@ -321,13 +329,15 @@
static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- boolean isTopApp, String[] pkgDataInfoList, boolean bindMountAppStorageDirs) {
+ boolean isTopApp, String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+ boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
ZygoteHooks.preFork();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
- pkgDataInfoList, bindMountAppStorageDirs);
+ pkgDataInfoList, whitelistedDataInfoList, bindMountAppDataDirs,
+ bindMountAppStorageDirs);
if (pid == 0) {
// Note that this event ends at the end of handleChildProc,
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -344,6 +354,7 @@
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
String appDataDir, boolean isTopApp, String[] pkgDataInfoList,
+ String[] whitelistedDataInfoList, boolean bindMountAppDataDirs,
boolean bindMountAppStorageDirs);
/**
@@ -371,15 +382,19 @@
* volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name,
* app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid,
* app_b_ce_inode, ...];
+ * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for whitelisted apps.
+ * @param bindMountAppDataDirs True if the zygote needs to mount data dirs.
* @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs.
*/
private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName,
boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
- String[] pkgDataInfoList, boolean bindMountAppStorageDirs) {
+ String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+ boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
- pkgDataInfoList, bindMountAppStorageDirs);
+ pkgDataInfoList, whitelistedDataInfoList,
+ bindMountAppDataDirs, bindMountAppStorageDirs);
// Note that this event ends at the end of handleChildProc.
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -399,7 +414,8 @@
private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
- String[] pkgDataInfoList, boolean bindMountAppStorageDirs);
+ String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+ boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs);
/**
* Called to do any initialization before starting an application.
@@ -724,7 +740,8 @@
args.mRuntimeFlags, rlimits, args.mMountExternal,
args.mSeInfo, args.mNiceName, args.mStartChildZygote,
args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
- args.mPkgDataInfoList, args.mBindMountAppStorageDirs);
+ args.mPkgDataInfoList, args.mWhitelistedDataInfoList,
+ args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -1060,4 +1077,11 @@
*/
@FastNative
public static native int nativeParseSigChld(byte[] in, int length, int[] out);
+
+ /**
+ * Returns whether the kernel supports tagged pointers. Present in the
+ * Android Common Kernel from 4.14 and up. By default, you should prefer
+ * fully-feature Memory Tagging, rather than the static Tagged Pointers.
+ */
+ public static native boolean nativeSupportsTaggedPointers();
}
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 1a63765..94c1f71 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -227,11 +227,22 @@
String[] mPkgDataInfoList;
/**
+ * A list that stores all whitelisted app data info: volume uuid and inode.
+ * Null if it does need to do app data isolation.
+ */
+ String[] mWhitelistedDataInfoList;
+
+ /**
* @see Zygote#BIND_MOUNT_APP_STORAGE_DIRS
*/
boolean mBindMountAppStorageDirs;
/**
+ * @see Zygote#BIND_MOUNT_APP_DATA_DIRS
+ */
+ boolean mBindMountAppDataDirs;
+
+ /**
* Constructs instance and parses args
*
* @param args zygote command-line args
@@ -452,8 +463,12 @@
}
} else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) {
mPkgDataInfoList = getAssignmentList(arg);
+ } else if (arg.startsWith(Zygote.WHITELISTED_DATA_INFO_MAP)) {
+ mWhitelistedDataInfoList = getAssignmentList(arg);
} else if (arg.equals(Zygote.BIND_MOUNT_APP_STORAGE_DIRS)) {
mBindMountAppStorageDirs = true;
+ } else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) {
+ mBindMountAppDataDirs = true;
} else {
break;
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index bc8dfd4..e6a3029 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -258,7 +258,8 @@
parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp,
- parsedArgs.mPkgDataInfoList, parsedArgs.mBindMountAppStorageDirs);
+ parsedArgs.mPkgDataInfoList, parsedArgs.mWhitelistedDataInfoList,
+ parsedArgs.mBindMountAppDataDirs, parsedArgs.mBindMountAppStorageDirs);
try {
if (pid == 0) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index ec1f516..c2b13c9 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -757,9 +757,11 @@
Zygote.applyDebuggerSystemProperty(parsedArgs);
Zygote.applyInvokeWithSystemProperty(parsedArgs);
- /* Enable pointer tagging in the system server unconditionally. Hardware support for
- * this is present in all ARMv8 CPUs; this flag has no effect on other platforms. */
- parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+ if (Zygote.nativeSupportsTaggedPointers()) {
+ /* Enable pointer tagging in the system server. Hardware support for this is present
+ * in all ARMv8 CPUs. */
+ parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+ }
/* Enable gwp-asan on the system server with a small probability. This is the same
* policy as applied to native processes and system apps. */
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/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b11c4c9..1cfa12d 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1430,6 +1430,12 @@
client->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, lightRadius);
}
+
+static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) {
+ SurfaceControl *surfaceControl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ return reinterpret_cast<jlong>(surfaceControl->getHandle().get());
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod sSurfaceControlMethods[] = {
@@ -1606,6 +1612,8 @@
(void*)nativeMirrorSurface },
{"nativeSetGlobalShadowSettings", "([F[FFFF)V",
(void*)nativeSetGlobalShadowSettings },
+ {"nativeGetHandle", "(J)J",
+ (void*)nativeGetHandle },
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index ea3c0fa..4b30359 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -110,7 +110,6 @@
using android::base::StringPrintf;
using android::base::WriteStringToFile;
using android::base::GetBoolProperty;
-using android::base::GetProperty;
#define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \
append(StringPrintf(__VA_ARGS__))
@@ -170,18 +169,6 @@
static constexpr int DEFAULT_DATA_DIR_PERMISSION = 0751;
-/**
- * Property to control if app data isolation is enabled.
- */
-static const std::string ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
- "persist.zygote.app_data_isolation";
-
-/**
- * Property to enable app data isolation for sdcard obb or data in vold.
- */
-static const std::string ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY =
- "persist.sys.vold_app_data_isolation_enabled";
-
static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000;
static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF;
@@ -1319,20 +1306,13 @@
* be decrypted after storage is decrypted.
*
*/
-static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
- uid_t uid, const char* process_name, jstring managed_nice_name,
- fail_fn_t fail_fn) {
+static void isolateAppData(JNIEnv* env, const std::vector<std::string>& merged_data_info_list,
+ uid_t uid, const char* process_name,
+ jstring managed_nice_name, fail_fn_t fail_fn) {
const userid_t userId = multiuser_get_user_id(uid);
- auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
-
- int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
- // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
- if ((size % 3) != 0) {
- fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size));
- }
- ensureInAppMountNamespace(fail_fn);
+ int size = merged_data_info_list.size();
// Mount tmpfs on all possible data directories, so app no longer see the original apps data.
char internalCePath[PATH_MAX];
@@ -1377,14 +1357,10 @@
bool legacySymlinkCreated = false;
for (int i = 0; i < size; i += 3) {
- jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
- std::string packageName = extract_fn(package_str).value();
+ std::string const & packageName = merged_data_info_list[i];
+ std::string const & volUuid = merged_data_info_list[i + 1];
+ std::string const & inode = merged_data_info_list[i + 2];
- jstring vol_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 1));
- std::string volUuid = extract_fn(vol_str).value();
-
- jstring inode_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 2));
- std::string inode = extract_fn(inode_str).value();
std::string::size_type sz;
long long ceDataInode = std::stoll(inode, &sz);
@@ -1482,6 +1458,48 @@
freecon(dataDataContext);
}
+static void insertPackagesToMergedList(JNIEnv* env,
+ std::vector<std::string>& merged_data_info_list,
+ jobjectArray data_info_list, const char* process_name,
+ jstring managed_nice_name, fail_fn_t fail_fn) {
+
+ auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
+
+ int size = (data_info_list != nullptr) ? env->GetArrayLength(data_info_list) : 0;
+ // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
+ if ((size % 3) != 0) {
+ fail_fn(CREATE_ERROR("Wrong data_info_list size %d", size));
+ }
+
+ for (int i = 0; i < size; i += 3) {
+ jstring package_str = (jstring) (env->GetObjectArrayElement(data_info_list, i));
+ std::string packageName = extract_fn(package_str).value();
+ merged_data_info_list.push_back(packageName);
+
+ jstring vol_str = (jstring) (env->GetObjectArrayElement(data_info_list, i + 1));
+ std::string volUuid = extract_fn(vol_str).value();
+ merged_data_info_list.push_back(volUuid);
+
+ jstring inode_str = (jstring) (env->GetObjectArrayElement(data_info_list, i + 2));
+ std::string inode = extract_fn(inode_str).value();
+ merged_data_info_list.push_back(inode);
+ }
+}
+
+static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
+ jobjectArray whitelisted_data_info_list, uid_t uid, const char* process_name,
+ jstring managed_nice_name, fail_fn_t fail_fn) {
+
+ ensureInAppMountNamespace(fail_fn);
+ std::vector<std::string> merged_data_info_list;
+ insertPackagesToMergedList(env, merged_data_info_list, pkg_data_info_list,
+ process_name, managed_nice_name, fail_fn);
+ insertPackagesToMergedList(env, merged_data_info_list, whitelisted_data_info_list,
+ process_name, managed_nice_name, fail_fn);
+
+ isolateAppData(env, merged_data_info_list, uid, process_name, managed_nice_name, fail_fn);
+}
+
/**
* Like isolateAppData(), isolate jit profile directories, so apps don't see what
* other apps are installed by reading content inside /data/misc/profiles/cur.
@@ -1594,7 +1612,9 @@
jstring managed_nice_name, bool is_system_server,
bool is_child_zygote, jstring managed_instruction_set,
jstring managed_app_data_dir, bool is_top_app,
- jobjectArray pkg_data_info_list, bool mount_storage_dirs) {
+ jobjectArray pkg_data_info_list,
+ jobjectArray whitelisted_data_info_list,
+ bool mount_data_dirs, bool mount_storage_dirs) {
const char* process_name = is_system_server ? "system_server" : "zygote";
auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
@@ -1628,9 +1648,9 @@
// give a null in same_uid_pkgs and private_volumes so they don't need app data isolation.
// Isolated process / webview / app zygote should be gated by SELinux and file permission
// so they can't even traverse CE / DE directories.
- if (pkg_data_info_list != nullptr
- && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
- isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
+ if (mount_data_dirs) {
+ isolateAppData(env, pkg_data_info_list, whitelisted_data_info_list,
+ uid, process_name, managed_nice_name, fail_fn);
isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
}
if ((mount_external != MOUNT_EXTERNAL_INSTALLER) && mount_storage_dirs) {
@@ -2003,7 +2023,8 @@
jint mount_external, jstring se_info, jstring nice_name,
jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
- jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
+ jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list,
+ jboolean mount_data_dirs, jboolean mount_storage_dirs) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
if (UNLIKELY(managed_fds_to_close == nullptr)) {
@@ -2041,6 +2062,8 @@
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
is_top_app == JNI_TRUE, pkg_data_info_list,
+ whitelisted_data_info_list,
+ mount_data_dirs == JNI_TRUE,
mount_storage_dirs == JNI_TRUE);
}
return pid;
@@ -2076,7 +2099,8 @@
permitted_capabilities, effective_capabilities,
MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
false, nullptr, nullptr, /* is_top_app= */ false,
- /* pkg_data_info_list */ nullptr, false);
+ /* pkg_data_info_list */ nullptr,
+ /* whitelisted_data_info_list */ nullptr, false, false);
} else if (pid > 0) {
// The zygote process checks whether the child process has died or not.
ALOGI("System server process %d has been created", pid);
@@ -2206,15 +2230,16 @@
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
- jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
+ jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list,
+ jboolean mount_data_dirs, jboolean mount_storage_dirs) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
capabilities, capabilities,
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE, pkg_data_info_list,
- mount_storage_dirs == JNI_TRUE);
+ is_top_app == JNI_TRUE, pkg_data_info_list, whitelisted_data_info_list,
+ mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE);
}
/**
@@ -2405,10 +2430,19 @@
return -1;
}
+static jboolean com_android_internal_os_Zygote_nativeSupportsTaggedPointers(JNIEnv* env, jclass) {
+#ifdef __aarch64__
+ int res = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+ return res >= 0 && res & PR_TAGGED_ADDR_ENABLE;
+#else
+ return false;
+#endif
+}
+
static const JNINativeMethod gMethods[] = {
{"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/"
- "String;Z[Ljava/lang/String;Z)I",
+ "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I",
(void*)com_android_internal_os_Zygote_nativeForkAndSpecialize},
{"nativeForkSystemServer", "(II[II[[IJJ)I",
(void*)com_android_internal_os_Zygote_nativeForkSystemServer},
@@ -2421,7 +2455,7 @@
{"nativeForkUsap", "(II[IZ)I", (void*)com_android_internal_os_Zygote_nativeForkUsap},
{"nativeSpecializeAppProcess",
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/"
- "String;Z[Ljava/lang/String;Z)V",
+ "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V",
(void*)com_android_internal_os_Zygote_nativeSpecializeAppProcess},
{"nativeInitNativeState", "(Z)V",
(void*)com_android_internal_os_Zygote_nativeInitNativeState},
@@ -2440,6 +2474,8 @@
(void*)com_android_internal_os_Zygote_nativeBoostUsapPriority},
{"nativeParseSigChld", "([BI[I)I",
(void*)com_android_internal_os_Zygote_nativeParseSigChld},
+ {"nativeSupportsTaggedPointers", "()Z",
+ (void*)com_android_internal_os_Zygote_nativeSupportsTaggedPointers},
};
int register_com_android_internal_os_Zygote(JNIEnv* env) {
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/proto/android/stats/dnsresolver/dns_resolver.proto b/core/proto/android/stats/dnsresolver/dns_resolver.proto
index 76f8f0f..61b9b25 100644
--- a/core/proto/android/stats/dnsresolver/dns_resolver.proto
+++ b/core/proto/android/stats/dnsresolver/dns_resolver.proto
@@ -62,6 +62,13 @@
NS_R_NOTAUTH = 9; // Not authoritative for zone
NS_R_NOTZONE = 10; // Zone of record different from zone section
NS_R_MAX = 11;
+ // Define rcode=12~15(UNASSIGNED) in rcode enum type.
+ // Some DNS Servers might return undefined code to devices.
+ // Without the enum definition, that would be noise for our dashboard.
+ NS_R_UNASSIGNED12 = 12; // Unassigned
+ NS_R_UNASSIGNED13 = 13; // Unassigned
+ NS_R_UNASSIGNED14 = 14; // Unassigned
+ NS_R_UNASSIGNED15 = 15; // Unassigned
// The following are EDNS extended rcodes
NS_R_BADVERS = 16;
// The following are TSIG errors
@@ -170,12 +177,22 @@
NT_BLUETOOTH = 3;
// Indicates this network uses an Ethernet transport.
NT_ETHERNET = 4;
- // Indicates this network uses a VPN transport.
- NT_VPN = 5;
+ // Indicates this network uses a VPN transport, now deprecated.
+ NT_VPN = 5 [deprecated=true];
// Indicates this network uses a Wi-Fi Aware transport.
NT_WIFI_AWARE = 6;
// Indicates this network uses a LoWPAN transport.
NT_LOWPAN = 7;
+ // Indicates this network uses a Cellular+VPN transport.
+ NT_CELLULAR_VPN = 8;
+ // Indicates this network uses a Wi-Fi+VPN transport.
+ NT_WIFI_VPN = 9;
+ // Indicates this network uses a Bluetooth+VPN transport.
+ NT_BLUETOOTH_VPN = 10;
+ // Indicates this network uses an Ethernet+VPN transport.
+ NT_ETHERNET_VPN = 11;
+ // Indicates this network uses a Wi-Fi+Cellular+VPN transport.
+ NT_WIFI_CELLULAR_VPN = 12;
}
enum CacheStatus{
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/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index cbb379b..d432dda 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -696,8 +696,7 @@
// Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
// attempt to release mLeash directly.
- SurfaceControl copy = new SurfaceControl();
- copy.copyFrom(mLeash);
+ SurfaceControl copy = new SurfaceControl(mLeash);
return new InsetsSourceControl(type, copy, new Point());
}
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/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 59bdf3d..0389639 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -137,6 +137,7 @@
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
<permission name="android.permission.PACKAGE_USAGE_STATS" />
+ <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
</privapp-permissions>
<privapp-permissions package="com.android.phone">
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/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 4150926..75ea0cb 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -47,10 +47,10 @@
Location getLastLocation(in LocationRequest request, String packageName, String featureId);
boolean getCurrentLocation(in LocationRequest request,
in ICancellationSignal cancellationSignal, in ILocationListener listener,
- String packageName, String featureId);
+ String packageName, String featureId, String listenerId);
void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
- in PendingIntent intent, String packageName, String featureId);
+ in PendingIntent intent, String packageName, String featureId, String listenerId);
void removeUpdates(in ILocationListener listener, in PendingIntent intent);
void requestGeofence(in LocationRequest request, in Geofence geofence,
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index fcbd3e5..d1b41df 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -718,7 +718,7 @@
currentLocationRequest.setExpireIn(GET_CURRENT_LOCATION_MAX_TIMEOUT_MS);
}
- GetCurrentLocationTransport listenerTransport = new GetCurrentLocationTransport(executor,
+ GetCurrentLocationTransport transport = new GetCurrentLocationTransport(executor,
consumer);
if (cancellationSignal != null) {
@@ -729,14 +729,15 @@
try {
if (mService.getCurrentLocation(currentLocationRequest, remoteCancellationSignal,
- listenerTransport, mContext.getPackageName(), mContext.getAttributionTag())) {
- listenerTransport.register(mContext.getSystemService(AlarmManager.class),
+ transport, mContext.getPackageName(), mContext.getAttributionTag(),
+ transport.getListenerId())) {
+ transport.register(mContext.getSystemService(AlarmManager.class),
remoteCancellationSignal);
if (cancellationSignal != null) {
- cancellationSignal.setOnCancelListener(listenerTransport::cancel);
+ cancellationSignal.setOnCancelListener(transport::cancel);
}
} else {
- listenerTransport.fail();
+ transport.fail();
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1175,7 +1176,8 @@
boolean registered = false;
try {
mService.requestLocationUpdates(locationRequest, transport, null,
- mContext.getPackageName(), mContext.getAttributionTag());
+ mContext.getPackageName(), mContext.getAttributionTag(),
+ transport.getListenerId());
registered = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1220,7 +1222,7 @@
try {
mService.requestLocationUpdates(locationRequest, null, pendingIntent,
- mContext.getPackageName(), mContext.getAttributionTag());
+ mContext.getPackageName(), mContext.getAttributionTag(), null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2558,6 +2560,10 @@
mRemoteCancellationSignal = null;
}
+ public String getListenerId() {
+ return mConsumer.getClass().getName() + "@" + System.identityHashCode(mConsumer);
+ }
+
public synchronized void register(AlarmManager alarmManager,
ICancellationSignal remoteCancellationSignal) {
if (mConsumer == null) {
@@ -2683,6 +2689,10 @@
return mListener;
}
+ public String getListenerId() {
+ return mListener.getClass().getName() + "@" + System.identityHashCode(mListener);
+ }
+
public void register(@NonNull Executor executor) {
Preconditions.checkArgument(executor != null, "invalid null executor");
mExecutor = executor;
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/bubble_dismiss_target.xml b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
index ca085b6..f5cd727 100644
--- a/packages/SystemUI/res/layout/bubble_dismiss_target.xml
+++ b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
@@ -17,7 +17,7 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
- android:layout_height="@dimen/pip_dismiss_gradient_height"
+ android:layout_height="@dimen/floating_dismiss_gradient_height"
android:layout_gravity="bottom|center_horizontal">
<FrameLayout
diff --git a/packages/SystemUI/res/layout/pip_dismiss_view.xml b/packages/SystemUI/res/layout/pip_dismiss_view.xml
deleted file mode 100644
index 2cc4b22..0000000
--- a/packages/SystemUI/res/layout/pip_dismiss_view.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/pip_dismiss_gradient_height"
- android:alpha="0">
-
- <TextView
- android:id="@+id/pip_dismiss_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|center_horizontal"
- android:text="@string/pip_phone_dismiss_hint"
- android:textColor="#FFFFFFFF"
- android:textSize="14sp"
- android:shadowColor="@android:color/black"
- android:shadowDx="-2"
- android:shadowDy="2"
- android:shadowRadius="0.01" />
-
-</FrameLayout>
\ No newline at end of file
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 9b9fbed..344479f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -955,7 +955,7 @@
<dimen name="recents_quick_scrub_onboarding_margin_start">8dp</dimen>
<!-- The height of the gradient indicating the dismiss edge when moving a PIP. -->
- <dimen name="pip_dismiss_gradient_height">176dp</dimen>
+ <dimen name="floating_dismiss_gradient_height">176dp</dimen>
<!-- The bottom margin of the PIP drag to dismiss info text shown when moving a PIP. -->
<dimen name="pip_dismiss_text_bottom_margin">24dp</dimen>
@@ -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/KeyguardMediaPlayer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
index d154434..af5196f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
@@ -24,6 +24,8 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
@@ -40,6 +42,7 @@
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.media.MediaControllerFactory;
import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
@@ -71,10 +74,11 @@
private KeyguardMediaObserver mObserver;
@Inject
- public KeyguardMediaPlayer(Context context, @Background Executor backgroundExecutor) {
+ public KeyguardMediaPlayer(Context context, MediaControllerFactory factory,
+ @Background Executor backgroundExecutor) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
- mViewModel = new KeyguardMediaViewModel(context);
+ mViewModel = new KeyguardMediaViewModel(context, factory);
}
/** Binds media controls to a view hierarchy. */
@@ -139,14 +143,16 @@
private static final class KeyguardMediaViewModel {
private final Context mContext;
+ private final MediaControllerFactory mMediaControllerFactory;
private final MutableLiveData<KeyguardMedia> mMedia = new MutableLiveData<>();
private final Object mActionsLock = new Object();
private List<PendingIntent> mActions;
private float mAlbumArtRadius;
private int mAlbumArtSize;
- KeyguardMediaViewModel(Context context) {
+ KeyguardMediaViewModel(Context context, MediaControllerFactory factory) {
mContext = context;
+ mMediaControllerFactory = factory;
loadDimens();
}
@@ -162,6 +168,17 @@
public void updateControls(NotificationEntry entry, Icon appIcon,
MediaMetadata mediaMetadata) {
+ // Check the playback state of the media controller. If it is null, then the session was
+ // probably destroyed. Don't update in this case.
+ final MediaSession.Token token = entry.getSbn().getNotification().extras
+ .getParcelable(Notification.EXTRA_MEDIA_SESSION);
+ final MediaController controller = token != null
+ ? mMediaControllerFactory.create(token) : null;
+ if (controller != null && controller.getPlaybackState() == null) {
+ clearControls();
+ return;
+ }
+
// Foreground and Background colors computed from album art
Notification notif = entry.getSbn().getNotification();
int fgColor = notif.color;
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/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 23fa645..5442299 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -120,7 +120,7 @@
private void init(DozeParameters dozeParameters) {
mIsHighEndGfx = ActivityManager.isHighEndGfx();
mDisplayNeedsBlanking = dozeParameters.getDisplayNeedsBlanking();
- mNeedTransition = mIsHighEndGfx && !mDisplayNeedsBlanking;
+ mNeedTransition = false;
// We will preserve EGL context when we are in lock screen or aod
// to avoid janking in following transition, we need to release when back to home.
@@ -137,7 +137,7 @@
mRenderer = getRendererInstance();
getDisplayContext().getDisplay().getDisplayInfo(mDisplayInfo);
setFixedSizeAllowed(true);
- setOffsetNotificationsEnabled(true);
+ setOffsetNotificationsEnabled(mNeedTransition);
updateSurfaceSize();
}
diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java
index 6923079..13d6a9b 100644
--- a/packages/SystemUI/src/com/android/systemui/Interpolators.java
+++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java
@@ -49,6 +49,8 @@
public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
public static final Interpolator HEADS_UP_APPEAR = new HeadsUpAppearInterpolator();
public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
+ public static final Interpolator SHADE_ANIMATION =
+ new PathInterpolator(0.6f, 0.02f, 0.4f, 0.98f);
public static final Interpolator ICON_OVERSHOT_LESS
= new PathInterpolator(0.4f, 0f, 0.2f, 1.1f);
public static final Interpolator PANEL_CLOSE_ACCELERATED
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/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index eff6934..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;
@@ -514,7 +699,7 @@
mDismissTargetContainer = new FrameLayout(context);
mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams(
MATCH_PARENT,
- getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
+ getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
Gravity.BOTTOM));
mDismissTargetContainer.setClipChildren(false);
mDismissTargetContainer.addView(targetView);
@@ -523,7 +708,7 @@
// Start translated down so the target springs up.
targetView.setTranslationY(
- getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height));
+ getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height));
// Save the MagneticTarget instance for the newly set up view - we'll add this to the
// MagnetizedObjects.
@@ -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/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
index d33cd94..8fd840e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
@@ -56,13 +56,12 @@
enabled: Boolean,
offset: Int = 0
): RenderInfo {
- val (fg, bg) = deviceColorMap.getValue(deviceType)
-
- val iconKey = if (offset > 0) {
+ val key = if (offset > 0) {
deviceType * BUCKET_SIZE + offset
} else deviceType
- val iconState = deviceIconMap.getValue(iconKey)
+ val (fg, bg) = deviceColorMap.getValue(key)
+ val iconState = deviceIconMap.getValue(key)
val resourceId = iconState[enabled]
var icon: Drawable?
if (resourceId == APP_ICON_ID) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index c16dce1..3f88f25 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -40,7 +40,7 @@
private int mDockState = DockManager.STATE_NONE;
- public DozeDockHandler(AmbientDisplayConfiguration config, DozeMachine machine,
+ DozeDockHandler(AmbientDisplayConfiguration config, DozeMachine machine,
DockManager dockManager) {
mMachine = machine;
mConfig = config;
@@ -74,8 +74,13 @@
@Override
public void onEvent(int dockState) {
if (DEBUG) Log.d(TAG, "dock event = " + dockState);
- final DozeMachine.State nextState;
+
mDockState = dockState;
+ if (isPulsing()) {
+ return;
+ }
+
+ DozeMachine.State nextState;
switch (mDockState) {
case DockManager.STATE_DOCKED:
nextState = State.DOZE_AOD_DOCKED;
@@ -90,10 +95,15 @@
default:
return;
}
-
mMachine.requestState(nextState);
}
+ private boolean isPulsing() {
+ DozeMachine.State state = mMachine.getState();
+ return state == State.DOZE_REQUEST_PULSE || state == State.DOZE_PULSING
+ || state == State.DOZE_PULSING_BRIGHT;
+ }
+
void register() {
if (mRegistered) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index f7f9afd..18bfd89 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -339,8 +339,8 @@
return State.DOZE;
}
if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING
- || mState == State.DOZE_AOD || mState == State.DOZE)
- && requestedState == State.DOZE_PULSE_DONE) {
+ || mState == State.DOZE_AOD || mState == State.DOZE
+ || mState == State.DOZE_AOD_DOCKED) && requestedState == State.DOZE_PULSE_DONE) {
Log.i(TAG, "Dropping pulse done because current state is already done: " + mState);
return mState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java
new file mode 100644
index 0000000..71bc7c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media;
+
+import android.content.Context;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+
+import javax.inject.Inject;
+
+/**
+ * Testable wrapper around {@link MediaController} constructor.
+ */
+public class MediaControllerFactory {
+
+ private final Context mContext;
+
+ @Inject
+ public MediaControllerFactory(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Creates a new MediaController from a session's token.
+ *
+ * @param token The token for the session. This value must never be null.
+ */
+ public MediaController create(MediaSession.Token token) {
+ return new MediaController(mContext, token);
+ }
+}
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/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 1868536..e24d29f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -231,7 +231,12 @@
/**
* @return {@link Rect} of the destination PiP window bounds.
*/
- Rect getDestinationBounds(float aspectRatio, Rect bounds, Size minimalSize) {
+ Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds,
+ Size minimalSize) {
+ if (!componentName.equals(mLastPipComponentName)) {
+ onResetReentryBoundsUnchecked();
+ mLastPipComponentName = componentName;
+ }
final Rect destinationBounds;
if (bounds == null) {
final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize);
@@ -246,11 +251,7 @@
transformBoundsToAspectRatio(destinationBounds, aspectRatio,
false /* useCurrentMinEdgeSize */);
}
- if (destinationBounds.equals(bounds)) {
- return bounds;
- }
mAspectRatio = aspectRatio;
- onResetReentryBoundsUnchecked();
mLastDestinationBounds.set(destinationBounds);
return destinationBounds;
}
@@ -483,6 +484,7 @@
pw.println(prefix + TAG);
pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction);
+ pw.println(innerPrefix + "mReentrySize=" + mReentrySize);
pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio);
pw.println(innerPrefix + "mMinAspectRatio=" + mMinAspectRatio);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index d9872d7..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
@@ -247,7 +247,7 @@
public void onTaskAppeared(ActivityManager.RunningTaskInfo info) {
Objects.requireNonNull(info, "Requires RunningTaskInfo");
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- getAspectRatioOrDefault(info.pictureInPictureParams),
+ info.topActivity, getAspectRatioOrDefault(info.pictureInPictureParams),
null /* bounds */, getMinimalSize(info.topActivityInfo));
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
mTaskInfo = info;
@@ -303,7 +303,7 @@
return;
}
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- getAspectRatioOrDefault(newParams),
+ info.topActivity, getAspectRatioOrDefault(newParams),
null /* bounds */, getMinimalSize(info.topActivityInfo));
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration,
@@ -335,7 +335,7 @@
}
final Rect newDestinationBounds = mPipBoundsHandler.getDestinationBounds(
- getAspectRatioOrDefault(mTaskInfo.pictureInPictureParams),
+ mTaskInfo.topActivity, getAspectRatioOrDefault(mTaskInfo.pictureInPictureParams),
null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
if (newDestinationBounds.equals(currentDestinationBounds)) return;
if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
deleted file mode 100644
index b725811..0000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2016 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.pip.phone;
-
-import android.content.Context;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.widget.FrameLayout;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-/**
- * Displays the dismiss UI and target for floating objects.
- */
-public class PipDismissViewController {
-
- // This delay controls how long to wait before we show the target when the user first moves
- // the PIP, to prevent the target from animating if the user just wants to fling the PIP
- public static final int SHOW_TARGET_DELAY = 100;
- private static final int SHOW_TARGET_DURATION = 350;
- private static final int HIDE_TARGET_DURATION = 225;
-
- private Context mContext;
- private WindowManager mWindowManager;
- private View mDismissView;
-
- // Used for dismissing a bubble -- bubble should be in the target to be considered a dismiss
- private View mTargetView;
- private int mTargetSlop;
- private Point mWindowSize;
- private int[] mLoc = new int[2];
- private boolean mIntersecting;
- private Vibrator mVibe;
-
- public PipDismissViewController(Context context) {
- mContext = context;
- mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- mVibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- }
-
- /**
- * Creates the dismiss target for showing via {@link #showDismissTarget()}.
- */
- public void createDismissTarget() {
- if (mDismissView == null) {
- // Determine sizes for the view
- final Rect stableInsets = new Rect();
- WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
- mWindowSize = new Point();
- mWindowManager.getDefaultDisplay().getRealSize(mWindowSize);
- final int gradientHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.pip_dismiss_gradient_height);
- final int bottomMargin = mContext.getResources().getDimensionPixelSize(
- R.dimen.pip_dismiss_text_bottom_margin);
- mTargetSlop = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_dismiss_slop);
-
- // Create a new view for the dismiss target
- LayoutInflater inflater = LayoutInflater.from(mContext);
- mDismissView = inflater.inflate(R.layout.pip_dismiss_view, null);
- mDismissView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
- mDismissView.forceHasOverlappingRendering(false);
-
- // Set the gradient background
- Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
- gradient.setAlpha((int) (255 * 0.85f));
- mDismissView.setBackground(gradient);
-
- // Adjust bottom margins of the text
- mTargetView = mDismissView.findViewById(R.id.pip_dismiss_text);
- FrameLayout.LayoutParams tlp = (FrameLayout.LayoutParams) mTargetView.getLayoutParams();
- tlp.bottomMargin = stableInsets.bottom + bottomMargin;
- mTargetView.setLayoutParams(tlp);
-
- // Add the target to the window
- LayoutParams lp = new LayoutParams(
- LayoutParams.MATCH_PARENT, gradientHeight,
- 0, mWindowSize.y - gradientHeight,
- LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | LayoutParams.FLAG_NOT_TOUCHABLE
- | LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
- lp.setTitle("pip-dismiss-overlay");
- lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- lp.setFitInsetsTypes(0 /* types */);
- mWindowManager.addView(mDismissView, lp);
- }
- mDismissView.animate().cancel();
- }
-
-
- /**
- * Updates the dismiss target based on location of the view, only used for bubbles not for PIP.
- *
- * @return whether the view is within the dismiss target.
- */
- public boolean updateTarget(View view) {
- if (mDismissView == null) {
- return false;
- }
- if (mDismissView.getAlpha() > 0) {
- view.getLocationOnScreen(mLoc);
- Rect viewRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + view.getWidth(),
- mLoc[1] + view.getHeight());
- mTargetView.getLocationOnScreen(mLoc);
- Rect targetRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + mTargetView.getWidth(),
- mLoc[1] + mTargetView.getHeight());
- expandRect(targetRect, mTargetSlop);
- boolean intersecting = targetRect.intersect(viewRect);
- if (intersecting != mIntersecting) {
- // TODO: is this the right effect?
- mVibe.vibrate(VibrationEffect.get(intersecting
- ? VibrationEffect.EFFECT_CLICK
- : VibrationEffect.EFFECT_TICK));
- }
- mIntersecting = intersecting;
- return intersecting;
- }
- return false;
- }
-
- /**
- * Shows the dismiss target.
- */
- public void showDismissTarget() {
- mDismissView.animate()
- .alpha(1f)
- .setInterpolator(Interpolators.LINEAR)
- .setStartDelay(SHOW_TARGET_DELAY)
- .setDuration(SHOW_TARGET_DURATION)
- .start();
- }
-
- /**
- * Hides and destroys the dismiss target.
- */
- public void destroyDismissTarget() {
- if (mDismissView != null) {
- mDismissView.animate()
- .alpha(0f)
- .setInterpolator(Interpolators.LINEAR)
- .setStartDelay(0)
- .setDuration(HIDE_TARGET_DURATION)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mWindowManager.removeViewImmediate(mDismissView);
- mDismissView = null;
- }
- })
- .start();
- }
- }
-
- private void expandRect(Rect outRect, int expandAmount) {
- outRect.left = Math.max(0, outRect.left - expandAmount);
- outRect.top = Math.max(0, outRect.top - expandAmount);
- outRect.right = Math.min(mWindowSize.x, outRect.right + expandAmount);
- outRect.bottom = Math.min(mWindowSize.y, outRect.bottom + expandAmount);
- }
-}
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 8397c65..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,16 +16,12 @@
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.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Debug;
import android.os.RemoteException;
@@ -40,6 +36,7 @@
import com.android.systemui.util.FloatingContentCoordinator;
import com.android.systemui.util.animation.FloatProperties;
import com.android.systemui.util.animation.PhysicsAnimator;
+import com.android.systemui.util.magnetictarget.MagnetizedObject;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -61,9 +58,6 @@
/** Friction to use for PIP when it moves via physics fling animations. */
private static final float DEFAULT_FRICTION = 2f;
- // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP
- private static final float DISMISS_OFFSCREEN_FRACTION = 0.3f;
-
private final Context mContext;
private final IActivityTaskManager mActivityTaskManager;
private final PipTaskOrganizer mPipTaskOrganizer;
@@ -103,7 +97,7 @@
/**
* Update listener that resizes the PIP to {@link #mAnimatedBounds}.
*/
- private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener =
+ final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener =
(target, values) -> resizePipUnchecked(mAnimatedBounds);
/** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
@@ -122,6 +116,13 @@
private final Consumer<Rect> mUpdateBoundsCallback = mBounds::set;
+ /**
+ * Whether we're springing to the touch event location (vs. moving it to that position
+ * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
+ * 'stuck' in the target and needs to catch up to the touch location.
+ */
+ private boolean mSpringingToTouch = false;
+
public PipMotionHelper(Context context, IActivityTaskManager activityTaskManager,
PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController,
PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils,
@@ -166,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());
}
/**
@@ -211,9 +204,35 @@
mFloatingContentCoordinator.onContentMoved(this);
}
- cancelAnimations();
- resizePipUnchecked(toBounds);
- mBounds.set(toBounds);
+ if (!mSpringingToTouch) {
+ // If we are moving PIP directly to the touch event locations, cancel any animations and
+ // move PIP to the given bounds.
+ cancelAnimations();
+ resizePipUnchecked(toBounds);
+ mBounds.set(toBounds);
+ } else {
+ // If PIP is 'catching up' after being stuck in the dismiss target, update the animation
+ // to spring towards the new touch location.
+ mAnimatedBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig)
+ .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig)
+ .withEndActions(() -> mSpringingToTouch = false);
+
+ startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */);
+ }
+ }
+
+ /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
+ void setSpringingToTouch(boolean springingToTouch) {
+ if (springingToTouch) {
+ mAnimatedBounds.set(mBounds);
+ }
+
+ mSpringingToTouch = springingToTouch;
+ }
+
+ void prepareForAnimation() {
+ mAnimatedBounds.set(mBounds);
}
/**
@@ -278,24 +297,11 @@
}
/**
- * @return whether the PiP at the current bounds should be dismissed.
- */
- boolean shouldDismissPip() {
- Point displaySize = new Point();
- mContext.getDisplay().getRealSize(displaySize);
- final int y = displaySize.y - mStableInsets.bottom;
- if (mBounds.bottom > y) {
- float offscreenFraction = (float) (mBounds.bottom - y) / mBounds.height();
- return offscreenFraction >= DISMISS_OFFSCREEN_FRACTION;
- }
- return false;
- }
-
- /**
* Flings the PiP to the closest snap target.
*/
void flingToSnapTarget(
- float velocityX, float velocityY, Runnable updateAction, @Nullable Runnable endAction) {
+ float velocityX, float velocityY,
+ @Nullable Runnable updateAction, @Nullable Runnable endAction) {
mAnimatedBounds.set(mBounds);
mAnimatedBoundsPhysicsAnimator
.flingThenSpring(
@@ -303,9 +309,13 @@
true /* flingMustReachMinOrMax */)
.flingThenSpring(
FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig)
- .addUpdateListener((target, values) -> updateAction.run())
.withEndActions(endAction);
+ if (updateAction != null) {
+ mAnimatedBoundsPhysicsAnimator.addUpdateListener(
+ (target, values) -> updateAction.run());
+ }
+
final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right;
final float estimatedFlingYEndValue =
PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY);
@@ -338,16 +348,14 @@
* Animates the dismissal of the PiP off the edge of the screen.
*/
void animateDismiss(float velocityX, float velocityY, @Nullable Runnable updateAction) {
- final float velocity = PointF.length(velocityX, velocityY);
- final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
- final Point dismissEndPoint = getDismissEndPoint(mBounds, velocityX, velocityY, isFling);
-
mAnimatedBounds.set(mBounds);
- // Animate to the dismiss end point, and then dismiss PIP.
+ // Animate off the bottom of the screen, then dismiss PIP.
mAnimatedBoundsPhysicsAnimator
- .spring(FloatProperties.RECT_X, dismissEndPoint.x, velocityX, mSpringConfig)
- .spring(FloatProperties.RECT_Y, dismissEndPoint.y, velocityY, mSpringConfig)
+ .spring(FloatProperties.RECT_Y,
+ mBounds.bottom + mBounds.height(),
+ velocityY,
+ mSpringConfig)
.withEndActions(this::dismissPip);
// If we were provided with an update action, run it whenever there's an update.
@@ -356,7 +364,7 @@
(target, values) -> updateAction.run());
}
- startBoundsAnimator(dismissEndPoint.x /* toX */, dismissEndPoint.y /* toY */);
+ startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */);
}
/**
@@ -408,6 +416,7 @@
private void cancelAnimations() {
mAnimatedBoundsPhysicsAnimator.cancel();
mAnimatingToBounds.setEmpty();
+ mSpringingToTouch = false;
}
/** Set new fling configs whose min/max values respect the given movement bounds. */
@@ -426,7 +435,9 @@
* the 'real' bounds to equal the final animated bounds.
*/
private void startBoundsAnimator(float toX, float toY) {
- cancelAnimations();
+ if (!mSpringingToTouch) {
+ cancelAnimations();
+ }
// Set animatingToBounds directly to avoid allocating a new Rect, but then call
// setAnimatingToBounds to run the normal logic for changing animatingToBounds.
@@ -484,47 +495,29 @@
}
/**
- * @return the coordinates the PIP should animate to based on the direction of velocity when
- * dismissing.
+ * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the
+ * magnetic dismiss target so it can calculate PIP's size and position.
*/
- private Point getDismissEndPoint(Rect pipBounds, float velX, float velY, boolean isFling) {
- Point displaySize = new Point();
- mContext.getDisplay().getRealSize(displaySize);
- final float bottomBound = displaySize.y + pipBounds.height() * .1f;
- if (isFling && velX != 0 && velY != 0) {
- // Line is defined by: y = mx + b, m = slope, b = y-intercept
- // Find the slope
- final float slope = velY / velX;
- // Sub in slope and PiP position to solve for y-intercept: b = y - mx
- final float yIntercept = pipBounds.top - slope * pipBounds.left;
- // Now find the point on this line when y = bottom bound: x = (y - b) / m
- final float x = (bottomBound - yIntercept) / slope;
- return new Point((int) x, (int) bottomBound);
- } else {
- // If it wasn't a fling the velocity on 'up' is not reliable for direction of movement,
- // just animate downwards.
- return new Point(pipBounds.left, (int) bottomBound);
- }
- }
+ MagnetizedObject<Rect> getMagnetizedPip() {
+ return new MagnetizedObject<Rect>(
+ mContext, mAnimatedBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) {
+ @Override
+ public float getWidth(@NonNull Rect animatedPipBounds) {
+ return animatedPipBounds.width();
+ }
- /**
- * @return whether the gesture it towards the dismiss area based on the velocity when
- * dismissing.
- */
- public boolean isGestureToDismissArea(Rect pipBounds, float velX, float velY,
- boolean isFling) {
- Point endpoint = getDismissEndPoint(pipBounds, velX, velY, isFling);
- // Center the point
- endpoint.x += pipBounds.width() / 2;
- endpoint.y += pipBounds.height() / 2;
+ @Override
+ public float getHeight(@NonNull Rect animatedPipBounds) {
+ return animatedPipBounds.height();
+ }
- // The dismiss area is the middle third of the screen, half the PIP's height from the bottom
- Point size = new Point();
- mContext.getDisplay().getRealSize(size);
- final int left = size.x / 3;
- Rect dismissArea = new Rect(left, size.y - (pipBounds.height() / 2), left * 2,
- size.y + pipBounds.height());
- return dismissArea.contains(endpoint.x, endpoint.y);
+ @Override
+ public void getLocationOnScreen(
+ @NonNull Rect animatedPipBounds, @NonNull int[] loc) {
+ loc[0] = animatedPipBounds.left;
+ loc[1] = animatedPipBounds.top;
+ }
+ };
}
public void dump(PrintWriter pw, String prefix) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 7cc2759..bbb4939 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -20,11 +20,13 @@
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+import android.annotation.SuppressLint;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -33,14 +35,23 @@
import android.util.Log;
import android.util.Pair;
import android.util.Size;
+import android.view.Gravity;
import android.view.IPinnedStackController;
import android.view.InputEvent;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.logging.MetricsLoggerWrapper;
@@ -51,7 +62,10 @@
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.DismissCircleView;
import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.systemui.util.animation.PhysicsAnimator;
+import com.android.systemui.util.magnetictarget.MagnetizedObject;
import java.io.PrintWriter;
@@ -62,9 +76,6 @@
public class PipTouchHandler {
private static final String TAG = "PipTouchHandler";
- // Allow the PIP to be flung from anywhere on the screen to the bottom to be dismissed.
- private static final boolean ENABLE_FLING_DISMISS = false;
-
private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 225;
private static final int BOTTOM_OFFSET_BUFFER_DP = 1;
@@ -73,17 +84,45 @@
// Allow PIP to resize to a slightly bigger state upon touch
private final boolean mEnableResize;
private final Context mContext;
+ private final WindowManager mWindowManager;
private final IActivityManager mActivityManager;
private final PipBoundsHandler mPipBoundsHandler;
private PipResizeGestureHandler mPipResizeGestureHandler;
private IPinnedStackController mPinnedStackController;
private final PipMenuActivityController mMenuController;
- private final PipDismissViewController mDismissViewController;
private final PipSnapAlgorithm mSnapAlgorithm;
private final AccessibilityManager mAccessibilityManager;
private boolean mShowPipMenuOnAnimationEnd = false;
+ /**
+ * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
+ * PIP.
+ */
+ private MagnetizedObject<Rect> mMagnetizedPip;
+
+ /**
+ * Container for the dismiss circle, so that it can be animated within the container via
+ * translation rather than within the WindowManager via slow layout animations.
+ */
+ private ViewGroup mTargetViewContainer;
+
+ /** Circle view used to render the dismiss target. */
+ private DismissCircleView mTargetView;
+
+ /**
+ * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
+ */
+ private MagnetizedObject.MagneticTarget mMagneticTarget;
+
+ /** PhysicsAnimator instance for animating the dismiss target in/out. */
+ private PhysicsAnimator<View> mMagneticTargetAnimator;
+
+ /** Default configuration to use for springing the dismiss target in/out. */
+ private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
+ new PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+
// The current movement bounds
private Rect mMovementBounds = new Rect();
// The current resized bounds, changed by user resize.
@@ -104,21 +143,20 @@
private int mDeferResizeToNormalBoundsUntilRotation = -1;
private int mDisplayRotation;
+ /**
+ * Runnable that can be posted delayed to show the target. This needs to be saved as a member
+ * variable so we can pass it to removeCallbacks.
+ */
+ private Runnable mShowTargetAction = this::showDismissTargetMaybe;
+
private Handler mHandler = new Handler();
- private Runnable mShowDismissAffordance = new Runnable() {
- @Override
- public void run() {
- if (mEnableDismissDragToEdge) {
- mDismissViewController.showDismissTarget();
- }
- }
- };
// Behaviour states
private int mMenuState = MENU_STATE_NONE;
private boolean mIsImeShowing;
private int mImeHeight;
private int mImeOffset;
+ private int mDismissAreaHeight;
private boolean mIsShelfShowing;
private int mShelfHeight;
private int mMovementBoundsExtraOffsets;
@@ -168,6 +206,7 @@
}
}
+ @SuppressLint("InflateParams")
public PipTouchHandler(Context context, IActivityManager activityManager,
IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
InputConsumerController inputConsumerController,
@@ -180,9 +219,9 @@
mContext = context;
mActivityManager = activityManager;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mMenuController = menuController;
mMenuController.addListener(new PipMenuListener());
- mDismissViewController = new PipDismissViewController(context);
mSnapAlgorithm = pipSnapAlgorithm;
mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(),
2.5f);
@@ -200,6 +239,7 @@
mExpandedShortestEdgeSize = res.getDimensionPixelSize(
R.dimen.pip_expanded_shortest_edge_size);
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
+ mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
@@ -212,6 +252,56 @@
mFloatingContentCoordinator = floatingContentCoordinator;
mConnection = new PipAccessibilityInteractionConnection(mMotionHelper,
this::onAccessibilityShowMenu, mHandler);
+
+ final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
+ mTargetView = new DismissCircleView(context);
+ final FrameLayout.LayoutParams newParams =
+ new FrameLayout.LayoutParams(targetSize, targetSize);
+ newParams.gravity = Gravity.CENTER;
+ mTargetView.setLayoutParams(newParams);
+
+ mTargetViewContainer = new FrameLayout(context);
+ mTargetViewContainer.setClipChildren(false);
+ mTargetViewContainer.addView(mTargetView);
+
+ mMagnetizedPip = mMotionHelper.getMagnetizedPip();
+ mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
+ mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener);
+ mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ mMotionHelper.prepareForAnimation();
+
+ // Show the dismiss target, in case the initial touch event occurred within the
+ // magnetic field radius.
+ showDismissTargetMaybe();
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velX, float velY, boolean wasFlungOut) {
+ if (wasFlungOut) {
+ mMotionHelper.flingToSnapTarget(velX, velY, null, null);
+ hideDismissTarget();
+ } else {
+ mMotionHelper.setSpringingToTouch(true);
+ }
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ mHandler.post(() -> {
+ mMotionHelper.animateDismiss(0, 0, null);
+ hideDismissTarget();
+ });
+
+
+ MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
+ PipUtils.getTopPipActivity(mContext, mActivityManager));
+ }
+ });
+
+ mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
}
public void setTouchGesture(PipTouchGesture gesture) {
@@ -231,7 +321,8 @@
}
public void onActivityPinned() {
- cleanUpDismissTarget();
+ createDismissTargetMaybe();
+
mShowPipMenuOnAnimationEnd = true;
mPipResizeGestureHandler.onActivityPinned();
mFloatingContentCoordinator.onContentAdded(mMotionHelper);
@@ -264,6 +355,10 @@
public void onConfigurationChanged() {
mMotionHelper.onConfigurationChanged();
mMotionHelper.synchronizePinnedStackBounds();
+
+ // Recreate the dismiss target for the new orientation.
+ cleanUpDismissTarget();
+ createDismissTargetMaybe();
}
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -351,6 +446,74 @@
}
}
+ /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
+ private void createDismissTargetMaybe() {
+ if (!mTargetViewContainer.isAttachedToWindow()) {
+ mHandler.removeCallbacks(mShowTargetAction);
+ mMagneticTargetAnimator.cancel();
+
+ final Point windowSize = new Point();
+ mWindowManager.getDefaultDisplay().getRealSize(windowSize);
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ mDismissAreaHeight,
+ 0, windowSize.y - mDismissAreaHeight,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.setTitle("pip-dismiss-overlay");
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setFitInsetsTypes(0 /* types */);
+
+ mTargetViewContainer.setVisibility(View.INVISIBLE);
+ mWindowManager.addView(mTargetViewContainer, lp);
+ }
+ }
+
+ /** Makes the dismiss target visible and animates it in, if it isn't already visible. */
+ private void showDismissTargetMaybe() {
+ createDismissTargetMaybe();
+
+ if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+
+ mTargetView.setTranslationY(mTargetViewContainer.getHeight());
+ mTargetViewContainer.setVisibility(View.VISIBLE);
+
+ // Set the magnetic field radius to half of PIP's width.
+ mMagneticTarget.setMagneticFieldRadiusPx(mMotionHelper.getBounds().width());
+
+ // Cancel in case we were in the middle of animating it out.
+ mMagneticTargetAnimator.cancel();
+ mMagneticTargetAnimator
+ .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig)
+ .start();
+ }
+ }
+
+ /** Animates the magnetic dismiss target out and then sets it to GONE. */
+ private void hideDismissTarget() {
+ mHandler.removeCallbacks(mShowTargetAction);
+ mMagneticTargetAnimator
+ .spring(DynamicAnimation.TRANSLATION_Y,
+ mTargetViewContainer.getHeight(),
+ mTargetSpringConfig)
+ .withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE))
+ .start();
+ }
+
+ /**
+ * Removes the dismiss target and cancels any pending callbacks to show it.
+ */
+ private void cleanUpDismissTarget() {
+ mHandler.removeCallbacks(mShowTargetAction);
+
+ if (mTargetViewContainer.isAttachedToWindow()) {
+ mWindowManager.removeView(mTargetViewContainer);
+ }
+ }
+
private void onRegistrationChanged(boolean isRegistered) {
mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
? mConnection : null);
@@ -375,8 +538,24 @@
if (mPinnedStackController == null) {
return true;
}
+
MotionEvent ev = (MotionEvent) inputEvent;
+ if (mMagnetizedPip.maybeConsumeMotionEvent(ev)) {
+ // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
+ // to the touch state. Touch state needs a DOWN event in order to later process MOVE
+ // events it'll receive if the object is dragged out of the magnetic field.
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mTouchState.onTouchEvent(ev);
+ }
+
+ // Continue tracking velocity when the object is in the magnetic field, since we want to
+ // respect touch input velocity if the object is dragged out and then flung.
+ mTouchState.addMovementToVelocityTracker(ev);
+
+ return true;
+ }
+
// Update the touch state
mTouchState.onTouchEvent(ev);
@@ -600,17 +779,13 @@
mDelta.set(0f, 0f);
mStartPosition.set(bounds.left, bounds.top);
mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
+ mMotionHelper.setSpringingToTouch(false);
// If the menu is still visible then just poke the menu
// so that it will timeout after the user stops touching it
if (mMenuState != MENU_STATE_NONE) {
mMenuController.pokeMenu();
}
-
- if (mEnableDismissDragToEdge) {
- mDismissViewController.createDismissTarget();
- mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY);
- }
}
@Override
@@ -623,8 +798,10 @@
mSavedSnapFraction = -1f;
if (mEnableDismissDragToEdge) {
- mHandler.removeCallbacks(mShowDismissAffordance);
- mDismissViewController.showDismissTarget();
+ if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+ mHandler.removeCallbacks(mShowTargetAction);
+ showDismissTargetMaybe();
+ }
}
}
@@ -644,10 +821,6 @@
mTmpBounds.offsetTo((int) left, (int) top);
mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
- if (mEnableDismissDragToEdge) {
- updateDismissFraction();
- }
-
final PointF curPos = touchState.getLastTouchPosition();
if (mMovementWithinDismiss) {
// Track if movement remains near the bottom edge to identify swipe to dismiss
@@ -661,9 +834,7 @@
@Override
public boolean onUp(PipTouchState touchState) {
if (mEnableDismissDragToEdge) {
- // Clean up the dismiss target regardless of the touch state in case the touch
- // enabled state changes while the user is interacting
- cleanUpDismissTarget();
+ hideDismissTarget();
}
if (!touchState.isUserInteracting()) {
@@ -671,26 +842,8 @@
}
final PointF vel = touchState.getVelocity();
- final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
final float velocity = PointF.length(vel.x, vel.y);
final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
- final boolean isUpWithinDimiss = ENABLE_FLING_DISMISS
- && touchState.getLastTouchPosition().y >= mMovementBounds.bottom
- && mMotionHelper.isGestureToDismissArea(mMotionHelper.getBounds(), vel.x,
- vel.y, isFling);
- final boolean isFlingToBot = isFling && vel.y > 0 && !isHorizontal
- && (mMovementWithinDismiss || isUpWithinDimiss);
- if (mEnableDismissDragToEdge) {
- // Check if the user dragged or flung the PiP offscreen to dismiss it
- if (mMotionHelper.shouldDismissPip() || isFlingToBot) {
- MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
- PipUtils.getTopPipActivity(mContext, mActivityManager));
- mMotionHelper.animateDismiss(
- vel.x, vel.y,
- PipTouchHandler.this::updateDismissFraction /* updateAction */);
- return true;
- }
- }
if (touchState.isDragging()) {
Runnable endAction = null;
@@ -749,14 +902,6 @@
}
/**
- * Removes the dismiss target and cancels any pending callbacks to show it.
- */
- private void cleanUpDismissTarget() {
- mHandler.removeCallbacks(mShowDismissAffordance);
- mDismissViewController.destroyDismissTarget();
- }
-
- /**
* @return whether the menu will resize as a part of showing the full menu.
*/
private boolean willResizeMenu() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index e3f65ef..dc286c1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -92,7 +92,7 @@
// Initialize the velocity tracker
initOrResetVelocityTracker();
- addMovement(ev);
+ addMovementToVelocityTracker(ev);
mActivePointerId = ev.getPointerId(0);
if (DEBUG) {
@@ -120,7 +120,7 @@
}
// Update the velocity tracker
- addMovement(ev);
+ addMovementToVelocityTracker(ev);
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
@@ -151,7 +151,7 @@
}
// Update the velocity tracker
- addMovement(ev);
+ addMovementToVelocityTracker(ev);
int pointerIndex = ev.getActionIndex();
int pointerId = ev.getPointerId(pointerIndex);
@@ -174,7 +174,7 @@
}
// Update the velocity tracker
- addMovement(ev);
+ addMovementToVelocityTracker(ev);
mVelocityTracker.computeCurrentVelocity(1000,
mViewConfig.getScaledMaximumFlingVelocity());
mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
@@ -318,6 +318,20 @@
return -1;
}
+ void addMovementToVelocityTracker(MotionEvent event) {
+ if (mVelocityTracker == null) {
+ return;
+ }
+
+ // Add movement to velocity tracker using raw screen X and Y coordinates instead
+ // of window coordinates because the window frame may be moving at the same time.
+ float deltaX = event.getRawX() - event.getX();
+ float deltaY = event.getRawY() - event.getY();
+ event.offsetLocation(deltaX, deltaY);
+ mVelocityTracker.addMovement(event);
+ event.offsetLocation(-deltaX, -deltaY);
+ }
+
private void initOrResetVelocityTracker() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
@@ -333,16 +347,6 @@
}
}
- private void addMovement(MotionEvent event) {
- // Add movement to velocity tracker using raw screen X and Y coordinates instead
- // of window coordinates because the window frame may be moving at the same time.
- float deltaX = event.getRawX() - event.getX();
- float deltaY = event.getRawY() - event.getY();
- event.offsetLocation(deltaX, deltaY);
- mVelocityTracker.addMovement(event);
- event.offsetLocation(-deltaX, -deltaY);
- }
-
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
index 9c175bc..8efeef1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
@@ -30,6 +30,14 @@
*/
public class PipControlsView extends LinearLayout {
+ public PipControlsView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(
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/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index d2da262..729f934 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -21,6 +21,7 @@
import android.animation.ValueAnimator
import android.app.WallpaperManager
import android.util.Log
+import android.util.MathUtils
import android.view.Choreographer
import android.view.View
import androidx.annotation.VisibleForTesting
@@ -37,6 +38,7 @@
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.NotificationShadeWindowController
import com.android.systemui.statusbar.phone.PanelExpansionListener
+import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.io.FileDescriptor
import java.io.PrintWriter
@@ -106,6 +108,16 @@
}
/**
+ * Force stop blur effect when necessary.
+ */
+ private var scrimsVisible: Boolean = false
+ set(value) {
+ if (field == value) return
+ field = value
+ scheduleUpdate()
+ }
+
+ /**
* Blur radius of the wake-up animation on this frame.
*/
private var wakeAndUnlockBlurRadius = 0
@@ -141,7 +153,13 @@
if (showingHomeControls) {
globalActionsRadius = 0
}
- val blur = max(shadeRadius.toInt(), globalActionsRadius)
+ var blur = max(shadeRadius.toInt(), globalActionsRadius)
+
+ // Make blur be 0 if it is necessary to stop blur effect.
+ if (scrimsVisible) {
+ blur = 0
+ }
+
blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
try {
wallpaperManager.setWallpaperZoomOut(root.windowToken,
@@ -201,6 +219,10 @@
brightnessMirrorSpring.finishIfRunning()
}
}
+
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased)
+ }
}
init {
@@ -209,6 +231,10 @@
keyguardStateController.addCallback(keyguardStateCallback)
}
statusBarStateController.addCallback(statusBarStateCallback)
+ notificationShadeWindowController.setScrimsVisibilityListener {
+ // Stop blur effect when scrims is opaque to avoid unnecessary GPU composition.
+ visibility -> scrimsVisible = visibility == ScrimController.OPAQUE
+ }
}
/**
@@ -224,8 +250,12 @@
private fun updateShadeBlur() {
var newBlur = 0
- if (statusBarStateController.state == StatusBarState.SHADE) {
- newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
+ val state = statusBarStateController.state
+ if (state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) {
+ val animatedBlur =
+ Interpolators.SHADE_ANIMATION.getInterpolation(
+ MathUtils.constrain(shadeExpansion / 0.15f, 0f, 1f))
+ newBlur = blurUtils.blurRadiusOfRatio(0.35f * animatedBlur + 0.65f * shadeExpansion)
}
shadeSpring.animateTo(newBlur)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index e1e679f..462b42a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -61,6 +61,7 @@
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -92,6 +93,7 @@
private final State mCurrentState = new State();
private OtherwisedCollapsedListener mListener;
private ForcePluginOpenListener mForcePluginOpenListener;
+ private Consumer<Integer> mScrimsVisibilityListener;
private final ArrayList<WeakReference<StatusBarWindowCallback>>
mCallbacks = Lists.newArrayList();
@@ -150,6 +152,16 @@
mCallbacks.add(new WeakReference<StatusBarWindowCallback>(callback));
}
+ /**
+ * Register a listener to monitor scrims visibility
+ * @param listener A listener to monitor scrims visibility
+ */
+ public void setScrimsVisibilityListener(Consumer<Integer> listener) {
+ if (listener != null && mScrimsVisibilityListener != listener) {
+ mScrimsVisibilityListener = listener;
+ }
+ }
+
private boolean shouldEnableKeyguardScreenRotation() {
Resources res = mContext.getResources();
return SystemProperties.getBoolean("lockscreen.rot_override", false)
@@ -477,6 +489,7 @@
public void setScrimsVisibility(int scrimsVisibility) {
mCurrentState.mScrimsVisibility = scrimsVisibility;
apply(mCurrentState);
+ mScrimsVisibilityListener.accept(scrimsVisibility);
}
/**
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/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
index 812a1e4..f27bdbf 100644
--- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
@@ -160,6 +160,18 @@
lateinit var magnetListener: MagnetizedObject.MagnetListener
/**
+ * Optional update listener to provide to the PhysicsAnimator that is used to spring the object
+ * into the target.
+ */
+ var physicsAnimatorUpdateListener: PhysicsAnimator.UpdateListener<T>? = null
+
+ /**
+ * Optional end listener to provide to the PhysicsAnimator that is used to spring the object
+ * into the target.
+ */
+ var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null
+
+ /**
* Sets whether forcefully flinging the object vertically towards a target causes it to be
* attracted to the target and then released immediately, despite never being dragged within the
* magnetic field.
@@ -479,6 +491,14 @@
.spring(yProperty, yProperty.getValue(underlyingObject) + yDiff, velY,
springConfig)
+ if (physicsAnimatorUpdateListener != null) {
+ animator.addUpdateListener(physicsAnimatorUpdateListener!!)
+ }
+
+ if (physicsAnimatorEndListener != null) {
+ animator.addEndListener(physicsAnimatorEndListener!!)
+ }
+
if (after != null) {
animator.withEndActions(after)
}
@@ -560,13 +580,15 @@
private val tempLoc = IntArray(2)
fun updateLocationOnScreen() {
- targetView.getLocationOnScreen(tempLoc)
+ targetView.post {
+ targetView.getLocationOnScreen(tempLoc)
- // Add half of the target size to get the center, and subtract translation since the
- // target could be animating in while we're doing this calculation.
- centerOnScreen.set(
- tempLoc[0] + targetView.width / 2f - targetView.translationX,
- tempLoc[1] + targetView.height / 2f - targetView.translationY)
+ // Add half of the target size to get the center, and subtract translation since the
+ // target could be animating in while we're doing this calculation.
+ centerOnScreen.set(
+ tempLoc[0] + targetView.width / 2f - targetView.translationX,
+ tempLoc[1] + targetView.height / 2f - targetView.translationY)
+ }
}
}
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/keyguard/KeyguardMediaPlayerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
index 072bc44..4bcf917 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
@@ -16,8 +16,12 @@
package com.android.keyguard
+import android.app.Notification
import android.graphics.drawable.Icon
import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -28,7 +32,9 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.media.MediaControllerFactory
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -38,6 +44,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -48,9 +55,12 @@
public class KeyguardMediaPlayerTest : SysuiTestCase() {
private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer
+ @Mock private lateinit var mockMediaFactory: MediaControllerFactory
+ @Mock private lateinit var mockMediaController: MediaController
+ private lateinit var playbackState: PlaybackState
private lateinit var fakeExecutor: FakeExecutor
private lateinit var mediaMetadata: MediaMetadata.Builder
- private lateinit var entry: NotificationEntryBuilder
+ private lateinit var entry: NotificationEntry
@Mock private lateinit var mockView: View
private lateinit var songView: TextView
private lateinit var artistView: TextView
@@ -70,8 +80,16 @@
@Before
public fun setup() {
+ playbackState = PlaybackState.Builder().run {
+ build()
+ }
+ mockMediaController = mock(MediaController::class.java)
+ whenever(mockMediaController.getPlaybackState()).thenReturn(playbackState)
+ mockMediaFactory = mock(MediaControllerFactory::class.java)
+ whenever(mockMediaFactory.create(any())).thenReturn(mockMediaController)
+
fakeExecutor = FakeExecutor(FakeSystemClock())
- keyguardMediaPlayer = KeyguardMediaPlayer(context, fakeExecutor)
+ keyguardMediaPlayer = KeyguardMediaPlayer(context, mockMediaFactory, fakeExecutor)
mockIcon = mock(Icon::class.java)
mockView = mock(View::class.java)
@@ -81,7 +99,9 @@
whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(artistView)
mediaMetadata = MediaMetadata.Builder()
- entry = NotificationEntryBuilder()
+ entry = NotificationEntryBuilder().build()
+ entry.getSbn().getNotification().extras.putParcelable(Notification.EXTRA_MEDIA_SESSION,
+ MediaSession.Token(1, null))
ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
@@ -109,7 +129,7 @@
@Test
public fun testUpdateControls() {
- keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
+ keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
FakeExecutor.exhaustExecutors(fakeExecutor)
verify(mockView).setVisibility(View.VISIBLE)
}
@@ -122,11 +142,22 @@
}
@Test
+ public fun testUpdateControlsNullPlaybackState() {
+ // GIVEN that the playback state is null (ie. the media session was destroyed)
+ whenever(mockMediaController.getPlaybackState()).thenReturn(null)
+ // WHEN updated
+ keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
+ FakeExecutor.exhaustExecutors(fakeExecutor)
+ // THEN the controls are cleared (ie. visibility is set to GONE)
+ verify(mockView).setVisibility(View.GONE)
+ }
+
+ @Test
public fun testSongName() {
val song: String = "Song"
mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song)
- keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
+ keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
assertThat(songView.getText()).isEqualTo(song)
@@ -137,7 +168,7 @@
val artist: String = "Artist"
mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist)
- keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
+ keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
assertThat(artistView.getText()).isEqualTo(artist)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 475023e..5227aaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -160,7 +160,7 @@
LOW_BMP_HEIGHT /* bmpHeight */,
LOW_BMP_WIDTH /* surfaceWidth */,
LOW_BMP_HEIGHT /* surfaceHeight */,
- true /* assertion */);
+ false /* assertion */);
}
@Test
@@ -172,7 +172,7 @@
INVALID_BMP_HEIGHT /* bmpHeight */,
ImageWallpaper.GLEngine.MIN_SURFACE_WIDTH /* surfaceWidth */,
ImageWallpaper.GLEngine.MIN_SURFACE_HEIGHT /* surfaceHeight */,
- true /* assertion */);
+ false /* assertion */);
}
private void verifySurfaceSizeAndAssertTransition(int bmpWidth, int bmpHeight,
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 037f04ec..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/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index c9bb401..9985d21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -20,8 +20,10 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
@@ -56,6 +58,7 @@
mDockManagerFake = spy(new DockManagerFake());
mDockHandler = new DozeDockHandler(mConfig, mMachine, mDockManagerFake);
+ when(mMachine.getState()).thenReturn(State.DOZE_AOD);
doReturn(true).when(mConfig).alwaysOnEnabled(anyInt());
mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
}
@@ -101,4 +104,31 @@
verify(mMachine).requestState(eq(State.DOZE));
}
+
+ @Test
+ public void onEvent_dockedWhilePulsing_wontRequestStateChange() {
+ when(mMachine.getState()).thenReturn(State.DOZE_PULSING);
+
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED);
+
+ verify(mMachine, never()).requestState(any(State.class));
+ }
+
+ @Test
+ public void onEvent_noneWhilePulsing_wontRequestStateChange() {
+ when(mMachine.getState()).thenReturn(State.DOZE_PULSING);
+
+ mDockManagerFake.setDockEvent(DockManager.STATE_NONE);
+
+ verify(mMachine, never()).requestState(any(State.class));
+ }
+
+ @Test
+ public void onEvent_hideWhilePulsing_wontRequestStateChange() {
+ when(mMachine.getState()).thenReturn(State.DOZE_PULSING);
+
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED_HIDE);
+
+ verify(mMachine, never()).requestState(any(State.class));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index c483314..1f07f46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -254,6 +254,17 @@
}
@Test
+ public void testPulseDone_whileDockedAoD_staysDockedAod() {
+ when(mDockManager.isDocked()).thenReturn(true);
+ mMachine.requestState(INITIALIZED);
+ mMachine.requestState(DOZE_AOD_DOCKED);
+
+ mMachine.requestState(DOZE_PULSE_DONE);
+
+ verify(mPartMock, never()).transitionTo(DOZE_AOD_DOCKED, DOZE_PULSE_DONE);
+ }
+
+ @Test
public void testPulseDone_dozeSuppressed_afterDocked_goesToDoze() {
when(mHost.isDozeSuppressed()).thenReturn(true);
when(mDockManager.isDocked()).thenReturn(true);
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/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
index 0bf0f04..425bf88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.pip;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
@@ -56,11 +57,15 @@
private PipBoundsHandler mPipBoundsHandler;
private DisplayInfo mDefaultDisplayInfo;
+ private ComponentName mTestComponentName1;
+ private ComponentName mTestComponentName2;
@Before
public void setUp() throws Exception {
initializeMockResources();
mPipBoundsHandler = new PipBoundsHandler(mContext, new PipSnapAlgorithm(mContext));
+ mTestComponentName1 = new ComponentName(mContext, "component1");
+ mTestComponentName2 = new ComponentName(mContext, "component2");
mPipBoundsHandler.onDisplayInfoChanged(mDefaultDisplayInfo);
}
@@ -121,7 +126,7 @@
};
for (float aspectRatio : aspectRatios) {
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+ mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final float actualAspectRatio =
destinationBounds.width() / (destinationBounds.height() * 1f);
assertEquals("Destination bounds matches the given aspect ratio",
@@ -137,7 +142,7 @@
};
for (float aspectRatio : invalidAspectRatios) {
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+ mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final float actualAspectRatio =
destinationBounds.width() / (destinationBounds.height() * 1f);
assertEquals("Destination bounds fallbacks to default aspect ratio",
@@ -153,7 +158,7 @@
currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left;
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE);
+ mTestComponentName1, aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE);
final float actualAspectRatio =
destinationBounds.width() / (destinationBounds.height() * 1f);
@@ -177,7 +182,7 @@
final float aspectRatio = aspectRatios[i];
final Size minimalSize = minimalSizes[i];
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize);
+ mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize);
assertTrue("Destination bounds is no smaller than minimal requirement",
(destinationBounds.width() == minimalSize.getWidth()
&& destinationBounds.height() >= minimalSize.getHeight())
@@ -198,7 +203,7 @@
final Size minSize = new Size(currentBounds.width() / 2, currentBounds.height() / 2);
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, currentBounds, minSize);
+ mTestComponentName1, aspectRatio, currentBounds, minSize);
assertTrue("Destination bounds ignores minimal size",
destinationBounds.width() > minSize.getWidth()
@@ -206,81 +211,92 @@
}
@Test
+ public void getDestinationBounds_withDifferentComponentName_ignoreLastPosition() {
+ final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+
+ oldPosition.offset(0, -100);
+ mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition);
+
+ final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName2,
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+
+ assertNonBoundsInclusionWithMargin("ignore saved bounds", oldPosition, newPosition);
+ }
+
+ @Test
public void setShelfHeight_offsetBounds() {
final int shelfHeight = 100;
- final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
mPipBoundsHandler.setShelfHeight(true, shelfHeight);
- final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
oldPosition.offset(0, -shelfHeight);
- assertBoundsWithMargin("offsetBounds by shelf", oldPosition, newPosition);
+ assertBoundsInclusionWithMargin("offsetBounds by shelf", oldPosition, newPosition);
}
@Test
public void onImeVisibilityChanged_offsetBounds() {
final int imeHeight = 100;
- final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
mPipBoundsHandler.onImeVisibilityChanged(true, imeHeight);
- final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
oldPosition.offset(0, -imeHeight);
- assertBoundsWithMargin("offsetBounds by IME", oldPosition, newPosition);
+ assertBoundsInclusionWithMargin("offsetBounds by IME", oldPosition, newPosition);
}
@Test
public void onSaveReentryBounds_restoreLastPosition() {
- final ComponentName componentName = new ComponentName(mContext, "component1");
- final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
oldPosition.offset(0, -100);
- mPipBoundsHandler.onSaveReentryBounds(componentName, oldPosition);
+ mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition);
- final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
- assertBoundsWithMargin("restoreLastPosition", oldPosition, newPosition);
+ assertBoundsInclusionWithMargin("restoreLastPosition", oldPosition, newPosition);
}
@Test
public void onResetReentryBounds_useDefaultBounds() {
- final ComponentName componentName = new ComponentName(mContext, "component1");
- final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(
+ final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final Rect newBounds = new Rect(defaultBounds);
newBounds.offset(0, -100);
- mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds);
+ mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds);
- mPipBoundsHandler.onResetReentryBounds(componentName);
- final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(
+ mPipBoundsHandler.onResetReentryBounds(mTestComponentName1);
+ final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
- assertBoundsWithMargin("useDefaultBounds", defaultBounds, actualBounds);
+ assertBoundsInclusionWithMargin("useDefaultBounds", defaultBounds, actualBounds);
}
@Test
public void onResetReentryBounds_componentMismatch_restoreLastPosition() {
- final ComponentName componentName = new ComponentName(mContext, "component1");
- final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(
+ final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final Rect newBounds = new Rect(defaultBounds);
newBounds.offset(0, -100);
- mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds);
+ mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds);
- mPipBoundsHandler.onResetReentryBounds(new ComponentName(mContext, "component2"));
- final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(
+ mPipBoundsHandler.onResetReentryBounds(mTestComponentName2);
+ final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
- assertBoundsWithMargin("restoreLastPosition", newBounds, actualBounds);
+ assertBoundsInclusionWithMargin("restoreLastPosition", newBounds, actualBounds);
}
- private void assertBoundsWithMargin(String from, Rect expected, Rect actual) {
+ private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
final Rect expectedWithMargin = new Rect(expected);
expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
assertTrue(from + ": expect " + expected
@@ -288,4 +304,13 @@
+ " with error margin " + ROUNDING_ERROR_MARGIN,
expectedWithMargin.contains(actual));
}
+
+ private void assertNonBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
+ final Rect expectedWithMargin = new Rect(expected);
+ expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
+ assertFalse(from + ": expect " + expected
+ + " not contains " + actual
+ + " with error margin " + ROUNDING_ERROR_MARGIN,
+ expectedWithMargin.contains(actual));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
index f1672b1..f6b7b74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
@@ -106,6 +106,10 @@
location[1] = targetCenterY - targetSize / 2 // y = 800
}
}.`when`(targetView).getLocationOnScreen(ArgumentMatchers.any())
+ doAnswer { invocation ->
+ (invocation.arguments[0] as Runnable).run()
+ true
+ }.`when`(targetView).post(ArgumentMatchers.any())
`when`(targetView.context).thenReturn(context)
magneticTarget = MagnetizedObject.MagneticTarget(targetView, magneticFieldRadius)
@@ -408,6 +412,10 @@
`when`(secondTargetView.width).thenReturn(targetSize) // width = 200
`when`(secondTargetView.height).thenReturn(targetSize) // height = 200
doAnswer { invocation ->
+ (invocation.arguments[0] as Runnable).run()
+ true
+ }.`when`(secondTargetView).post(ArgumentMatchers.any())
+ doAnswer { invocation ->
(invocation.arguments[0] as IntArray).also { location ->
// Return the top left of the target.
location[0] = secondTargetCenterX - targetSize / 2 // x = 0
diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp
index 1a1c30d..620261b 100644
--- a/packages/Tethering/tests/integration/Android.bp
+++ b/packages/Tethering/tests/integration/Android.bp
@@ -39,4 +39,9 @@
"android.test.base",
"android.test.mock",
],
+ jni_libs: [
+ // For mockito extended
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
}
diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index dbd68ef..b02bb23 100644
--- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -205,7 +205,7 @@
requestWithStaticIpv4(localAddr, clientAddr));
mTetheringEventCallback.awaitInterfaceTethered();
- assertInterfaceHasIpAddress(iface, clientAddr);
+ assertInterfaceHasIpAddress(iface, localAddr);
byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();
diff --git a/packages/Tethering/tests/unit/AndroidManifest.xml b/packages/Tethering/tests/unit/AndroidManifest.xml
index 530bc07..4ff1d37 100644
--- a/packages/Tethering/tests/unit/AndroidManifest.xml
+++ b/packages/Tethering/tests/unit/AndroidManifest.xml
@@ -20,7 +20,16 @@
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
+ <service
+ android:name="com.android.server.connectivity.tethering.MockTetheringService"
+ android:permission="android.permission.TETHER_PRIVILEGED"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.server.connectivity.tethering.TetheringService"/>
+ </intent-filter>
+ </service>
</application>
+
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.networkstack.tethering.tests.unit"
android:label="Tethering service tests">
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/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java
new file mode 100644
index 0000000..355ece9
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.connectivity.tethering;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Intent;
+import android.net.ITetheringConnector;
+import android.os.Binder;
+import android.os.IBinder;
+
+public class MockTetheringService extends TetheringService {
+ private final Tethering mTethering = mock(Tethering.class);
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new MockTetheringConnector(super.onBind(intent));
+ }
+
+ @Override
+ public Tethering makeTethering(TetheringDependencies deps) {
+ return mTethering;
+ }
+
+ public Tethering getTethering() {
+ return mTethering;
+ }
+
+ public class MockTetheringConnector extends Binder {
+ final IBinder mBase;
+ MockTetheringConnector(IBinder base) {
+ mBase = base;
+ }
+
+ public ITetheringConnector getTetheringConnector() {
+ return ITetheringConnector.Stub.asInterface(mBase);
+ }
+
+ public MockTetheringService getService() {
+ return MockTetheringService.this;
+ }
+ }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java
new file mode 100644
index 0000000..d9d3e73
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.net.IIntResultListener;
+import android.net.ITetheringConnector;
+import android.net.ITetheringEventCallback;
+import android.net.TetheringRequestParcel;
+import android.os.ResultReceiver;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.connectivity.tethering.MockTetheringService.MockTetheringConnector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class TetheringServiceTest {
+ private static final String TEST_IFACE_NAME = "test_wlan0";
+ private static final String TEST_CALLER_PKG = "test_pkg";
+ @Mock private ITetheringEventCallback mITetheringEventCallback;
+ @Rule public ServiceTestRule mServiceTestRule;
+ private Tethering mTethering;
+ private Intent mMockServiceIntent;
+ private ITetheringConnector mTetheringConnector;
+
+ private class TestTetheringResult extends IIntResultListener.Stub {
+ private int mResult = -1; // Default value that does not match any result code.
+ @Override
+ public void onResult(final int resultCode) {
+ mResult = resultCode;
+ }
+
+ public void assertResult(final int expected) {
+ assertEquals(expected, mResult);
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mServiceTestRule = new ServiceTestRule();
+ mMockServiceIntent = new Intent(
+ InstrumentationRegistry.getTargetContext(),
+ MockTetheringService.class);
+ final MockTetheringConnector mockConnector =
+ (MockTetheringConnector) mServiceTestRule.bindService(mMockServiceIntent);
+ mTetheringConnector = mockConnector.getTetheringConnector();
+ final MockTetheringService service = mockConnector.getService();
+ mTethering = service.getTethering();
+ verify(mTethering).startStateMachineUpdaters();
+ when(mTethering.hasTetherableConfiguration()).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mServiceTestRule.unbindService();
+ }
+
+ @Test
+ public void testTether() throws Exception {
+ when(mTethering.tether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).tether(TEST_IFACE_NAME);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testUntether() throws Exception {
+ when(mTethering.untether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).untether(TEST_IFACE_NAME);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testSetUsbTethering() throws Exception {
+ when(mTethering.setUsbTethering(true /* enable */)).thenReturn(TETHER_ERROR_NO_ERROR);
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).setUsbTethering(true /* enable */);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testStartTethering() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ final TetheringRequestParcel request = new TetheringRequestParcel();
+ request.tetheringType = TETHERING_WIFI;
+ mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).startTethering(eq(request), eq(result));
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testStopTethering() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).stopTethering(TETHERING_WIFI);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testRequestLatestTetheringEntitlementResult() throws Exception {
+ final ResultReceiver result = new ResultReceiver(null);
+ mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
+ true /* showEntitlementUi */, TEST_CALLER_PKG);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).requestLatestTetheringEntitlementResult(eq(TETHERING_WIFI),
+ eq(result), eq(true) /* showEntitlementUi */);
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testRegisterTetheringEventCallback() throws Exception {
+ mTetheringConnector.registerTetheringEventCallback(mITetheringEventCallback,
+ TEST_CALLER_PKG);
+ verify(mTethering).registerTetheringEventCallback(eq(mITetheringEventCallback));
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testUnregisterTetheringEventCallback() throws Exception {
+ mTetheringConnector.unregisterTetheringEventCallback(mITetheringEventCallback,
+ TEST_CALLER_PKG);
+ verify(mTethering).unregisterTetheringEventCallback(
+ eq(mITetheringEventCallback));
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testStopAllTethering() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).untetherAll();
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testIsTetheringSupported() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index a59c6fd..3a580dd 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -140,7 +140,9 @@
import com.android.testutils.MiscAssertsKt;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -439,6 +441,18 @@
return buildMobileUpstreamState(false, true, true);
}
+ // See FakeSettingsProvider#clearSettingsProvider() that this needs to be called before and
+ // after use.
+ @BeforeClass
+ public static void setupOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 93bffe9..52a82dd 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -36,10 +36,12 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
import libcore.io.IoUtils;
@@ -261,22 +263,7 @@
// And reset to the wallpaper service we should be using
ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
- if (servicePackageExists(wpService)) {
- Slog.i(TAG, "Using wallpaper service " + wpService);
- mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM);
- if (!lockImageStage.exists()) {
- // We have a live wallpaper and no static lock image,
- // allow live wallpaper to show "through" on lock screen.
- mWm.clear(FLAG_LOCK);
- }
- } else {
- // If we've restored a live wallpaper, but the component doesn't exist,
- // we should log it as an error so we can easily identify the problem
- // in reports from users
- if (wpService != null) {
- Slog.e(TAG, "Wallpaper service " + wpService + " isn't available.");
- }
- }
+ updateWallpaperComponent(wpService, !lockImageStage.exists());
} catch (Exception e) {
Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
} finally {
@@ -293,6 +280,28 @@
}
}
+ @VisibleForTesting
+ void updateWallpaperComponent(ComponentName wpService, boolean applyToLock) throws IOException {
+ if (servicePackageExists(wpService)) {
+ Slog.i(TAG, "Using wallpaper service " + wpService);
+ mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM);
+ if (applyToLock) {
+ // We have a live wallpaper and no static lock image,
+ // allow live wallpaper to show "through" on lock screen.
+ mWm.clear(FLAG_LOCK);
+ }
+ } else {
+ // If we've restored a live wallpaper, but the component doesn't exist,
+ // we should log it as an error so we can easily identify the problem
+ // in reports from users
+ if (wpService != null) {
+ applyComponentAtInstall(wpService, applyToLock);
+ Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
+ + " Will try to apply later");
+ }
+ }
+ }
+
private void restoreFromStage(File stage, File info, String hintTag, int which)
throws IOException {
if (stage.exists()) {
@@ -372,7 +381,8 @@
return (value == null) ? defValue : Integer.parseInt(value);
}
- private boolean servicePackageExists(ComponentName comp) {
+ @VisibleForTesting
+ boolean servicePackageExists(ComponentName comp) {
try {
if (comp != null) {
final IPackageManager pm = AppGlobals.getPackageManager();
@@ -401,4 +411,53 @@
throws IOException {
// Intentionally blank
}
+
+ private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock) {
+ PackageMonitor packageMonitor = getWallpaperPackageMonitor(componentName, applyToLock);
+ packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
+ }
+
+ @VisibleForTesting
+ PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock) {
+ return new PackageMonitor() {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ if (!isDeviceInRestore()) {
+ // We don't want to reapply the wallpaper outside a restore.
+ unregister();
+ return;
+ }
+
+ if (componentName.getPackageName().equals(packageName)) {
+ Slog.d(TAG, "Applying component " + componentName);
+ mWm.setWallpaperComponent(componentName);
+ if (applyToLock) {
+ try {
+ mWm.clear(FLAG_LOCK);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e);
+ }
+ }
+ // We're only expecting to restore the wallpaper component once.
+ unregister();
+ }
+ }
+ };
+ }
+
+ @VisibleForTesting
+ boolean isDeviceInRestore() {
+ try {
+ boolean isInSetup = Settings.Secure.getInt(getBaseContext().getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE) == 0;
+ boolean isInDeferredSetup = Settings.Secure.getInt(getBaseContext()
+ .getContentResolver(),
+ Settings.Secure.USER_SETUP_PERSONALIZATION_STATE) ==
+ Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED;
+ return isInSetup || isInDeferredSetup;
+ } catch (Settings.SettingNotFoundException e) {
+ Slog.w(TAG, "Failed to check if the user is in restore: " + e);
+ return false;
+ }
+ }
}
\ No newline at end of file
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
similarity index 65%
rename from packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java
rename to packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 255fdef..4367075 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wallpaperbackup.tests;
+package com.android.wallpaperbackup;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
@@ -26,17 +26,22 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.WallpaperManager;
import android.app.backup.FullBackupDataOutput;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.SharedPreferences;
import android.os.UserHandle;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.content.PackageMonitor;
import com.android.wallpaperbackup.WallpaperBackupAgent;
import com.android.wallpaperbackup.utils.ContextWithServiceOverrides;
@@ -58,6 +63,7 @@
public class WallpaperBackupAgentTest {
private static final String SYSTEM_GENERATION = "system_gen";
private static final String LOCK_GENERATION = "lock_gen";
+ private static final String TEST_WALLPAPER_PACKAGE = "wallpaper_package";
private static final int TEST_SYSTEM_WALLPAPER_ID = 1;
private static final int TEST_LOCK_WALLPAPER_ID = 2;
@@ -66,11 +72,13 @@
@Mock private WallpaperManager mWallpaperManager;
@Mock private SharedPreferences mSharedPreferences;
@Mock private SharedPreferences.Editor mSharedPreferenceEditor;
+ @Mock private Context mMockContext;
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
private ContextWithServiceOverrides mContext;
private IsolatedWallpaperBackupAgent mWallpaperBackupAgent;
+ private ComponentName mWallpaperComponent;
@Before
public void setUp() {
@@ -88,6 +96,8 @@
mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent(mTemporaryFolder.getRoot());
mWallpaperBackupAgent.attach(mContext);
mWallpaperBackupAgent.onCreate();
+
+ mWallpaperComponent = new ComponentName(TEST_WALLPAPER_PACKAGE, "");
}
@Test
@@ -130,6 +140,69 @@
inOrder.verify(mSharedPreferenceEditor).apply();
}
+ @Test
+ public void updateWallpaperComponent_doesApplyLater() throws IOException {
+ mWallpaperBackupAgent.mIsDeviceInRestore = true;
+
+ mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+ /* applyToLock */ true);
+
+ // Imitate wallpaper component installation.
+ mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+ /* uid */0);
+
+ verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
+ verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK));
+ }
+
+ @Test
+ public void updateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen()
+ throws IOException {
+ mWallpaperBackupAgent.mIsDeviceInRestore = true;
+
+ mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+ /* applyToLock */ false);
+
+ // Imitate wallpaper component installation.
+ mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+ /* uid */0);
+
+ verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
+ verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+ }
+
+ @Test
+ public void updateWallpaperComponent_deviceNotInRestore_doesNotApply()
+ throws IOException {
+ mWallpaperBackupAgent.mIsDeviceInRestore = false;
+
+ mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+ /* applyToLock */ true);
+
+ // Imitate wallpaper component installation.
+ mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+ /* uid */0);
+
+ verify(mWallpaperManager, never()).setWallpaperComponent(mWallpaperComponent);
+ verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+ }
+
+ @Test
+ public void updateWallpaperComponent_differentPackageInstalled_doesNotApply()
+ throws IOException {
+ mWallpaperBackupAgent.mIsDeviceInRestore = false;
+
+ mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+ /* applyToLock */ true);
+
+ // Imitate "wrong" wallpaper component installation.
+ mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(/* packageName */"",
+ /* uid */0);
+
+ verify(mWallpaperManager, never()).setWallpaperComponent(mWallpaperComponent);
+ verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+ }
+
private void mockUnbackedUpState() {
mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1))).thenReturn(-1);
@@ -162,6 +235,8 @@
private class IsolatedWallpaperBackupAgent extends WallpaperBackupAgent {
File mWallpaperBaseDirectory;
List<File> mBackedUpFiles = new ArrayList<>();
+ PackageMonitor mWallpaperPackageMonitor;
+ boolean mIsDeviceInRestore = false;
IsolatedWallpaperBackupAgent(File wallpaperBaseDirectory) {
mWallpaperBaseDirectory = wallpaperBaseDirectory;
@@ -181,5 +256,27 @@
public SharedPreferences getSharedPreferences(File file, int mode) {
return mSharedPreferences;
}
+
+ @Override
+ boolean servicePackageExists(ComponentName comp) {
+ return false;
+ }
+
+ @Override
+ boolean isDeviceInRestore() {
+ return mIsDeviceInRestore;
+ }
+
+ @Override
+ PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
+ boolean applyToLock) {
+ mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, applyToLock);
+ return mWallpaperPackageMonitor;
+ }
+
+ @Override
+ public Context getBaseContext() {
+ return mMockContext;
+ }
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 2100c1a..7230b00 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -413,7 +413,12 @@
&& component.getPackageName().equals(packageName))
|| userState.mCrashedServices.removeIf(component -> component != null
&& component.getPackageName().equals(packageName));
- if (reboundAService) {
+ // Reloads the installed services info to make sure the rebound service could
+ // get a new one.
+ userState.mInstalledServices.clear();
+ final boolean configurationChanged =
+ readConfigurationForUserStateLocked(userState);
+ if (reboundAService || configurationChanged) {
onUserStateChangedLocked(userState);
}
migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName);
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/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java
new file mode 100644
index 0000000..3612e09
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+
+/**
+ * Controls the interaction with the IME for the inline suggestion sessions.
+ */
+final class AutofillInlineSessionController {
+ @NonNull
+ private final InputMethodManagerInternal mInputMethodManagerInternal;
+ private final int mUserId;
+ @NonNull
+ private final ComponentName mComponentName;
+ @NonNull
+ private final Object mLock;
+ @NonNull
+ private final Handler mHandler;
+
+ @GuardedBy("mLock")
+ private AutofillInlineSuggestionsRequestSession mSession;
+
+ AutofillInlineSessionController(InputMethodManagerInternal inputMethodManagerInternal,
+ int userId, ComponentName componentName, Handler handler, Object lock) {
+ mInputMethodManagerInternal = inputMethodManagerInternal;
+ mUserId = userId;
+ mComponentName = componentName;
+ mHandler = handler;
+ mLock = lock;
+ }
+
+
+ /**
+ * Requests the IME to create an {@link InlineSuggestionsRequest} for {@code autofillId}.
+ *
+ * @param autofillId the Id of the field for which the request is for.
+ * @param requestConsumer the callback which will be invoked when IME responded or if it times
+ * out waiting for IME response.
+ */
+ @GuardedBy("mLock")
+ void onCreateInlineSuggestionsRequestLocked(@NonNull AutofillId autofillId,
+ @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) {
+ // TODO(b/151123764): rename the method to better reflect what it does.
+ if (mSession != null) {
+ // Send an empty response to IME and destroy the existing session.
+ mSession.onInlineSuggestionsResponseLocked(mSession.getAutofillIdLocked(),
+ new InlineSuggestionsResponse(Collections.EMPTY_LIST));
+ mSession.destroySessionLocked();
+ }
+ // TODO(b/151123764): consider reusing the same AutofillInlineSession object for the
+ // same field.
+ mSession = new AutofillInlineSuggestionsRequestSession(mInputMethodManagerInternal, mUserId,
+ mComponentName, mHandler, mLock, autofillId, requestConsumer, uiExtras);
+ mSession.onCreateInlineSuggestionsRequestLocked();
+
+ }
+
+ /**
+ * Returns the {@link InlineSuggestionsRequest} provided by IME for the last request.
+ *
+ * <p> The caller is responsible for making sure Autofill hears back from IME before calling
+ * this method, using the {@code requestConsumer} provided when calling {@link
+ * #onCreateInlineSuggestionsRequestLocked(AutofillId, Consumer, Bundle)}.
+ */
+ @GuardedBy("mLock")
+ Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() {
+ if (mSession != null) {
+ return mSession.getInlineSuggestionsRequestLocked();
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Requests the IME to hide the current suggestions, if any. Returns true if the message is sent
+ * to the IME.
+ */
+ @GuardedBy("mLock")
+ boolean hideInlineSuggestionsUiLocked(@NonNull AutofillId autofillId) {
+ if (mSession != null) {
+ return mSession.onInlineSuggestionsResponseLocked(autofillId,
+ new InlineSuggestionsResponse(Collections.EMPTY_LIST));
+ }
+ return false;
+ }
+
+ /**
+ * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused
+ * on the {@code autofillId}.
+ *
+ * @return false if there is no session, or if the IME callback is not available in the session.
+ */
+ @GuardedBy("mLock")
+ boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
+ @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
+ // TODO(b/151123764): rename the method to better reflect what it does.
+ if (mSession != null) {
+ return mSession.onInlineSuggestionsResponseLocked(autofillId,
+ inlineSuggestionsResponse);
+ }
+ return false;
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
new file mode 100644
index 0000000..ca230b6
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.autofill.Helper.sDebug;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.internal.view.InlineSuggestionsRequestInfo;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+
+import java.lang.ref.WeakReference;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * Maintains an inline suggestion session with the IME.
+ *
+ * <p> Each session corresponds to one request from the Autofill manager service to create an
+ * {@link InlineSuggestionsRequest}. It's responsible for receiving callbacks from the IME and
+ * sending {@link android.view.inputmethod.InlineSuggestionsResponse} to IME.
+ */
+final class AutofillInlineSuggestionsRequestSession {
+
+ private static final String TAG = AutofillInlineSuggestionsRequestSession.class.getSimpleName();
+ private static final int INLINE_REQUEST_TIMEOUT_MS = 200;
+
+ @NonNull
+ private final InputMethodManagerInternal mInputMethodManagerInternal;
+ private final int mUserId;
+ @NonNull
+ private final ComponentName mComponentName;
+ @NonNull
+ private final Object mLock;
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final Bundle mUiExtras;
+
+ @GuardedBy("mLock")
+ @NonNull
+ private AutofillId mAutofillId;
+ @GuardedBy("mLock")
+ @Nullable
+ private Consumer<InlineSuggestionsRequest> mImeRequestConsumer;
+
+ @GuardedBy("mLock")
+ private boolean mImeRequestReceived;
+ @GuardedBy("mLock")
+ @Nullable
+ private InlineSuggestionsRequest mImeRequest;
+ @GuardedBy("mLock")
+ @Nullable
+ private IInlineSuggestionsResponseCallback mResponseCallback;
+ @GuardedBy("mLock")
+ @Nullable
+ private Runnable mTimeoutCallback;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private AutofillId mImeCurrentFieldId;
+ @GuardedBy("mLock")
+ private boolean mImeInputStarted;
+ @GuardedBy("mLock")
+ private boolean mImeInputViewStarted;
+ @GuardedBy("mLock")
+ @Nullable
+ private InlineSuggestionsResponse mInlineSuggestionsResponse;
+ @GuardedBy("mLock")
+ private boolean mPreviousResponseIsNotEmpty;
+
+ @GuardedBy("mLock")
+ private boolean mDestroyed = false;
+
+ AutofillInlineSuggestionsRequestSession(
+ @NonNull InputMethodManagerInternal inputMethodManagerInternal, int userId,
+ @NonNull ComponentName componentName, @NonNull Handler handler, @NonNull Object lock,
+ @NonNull AutofillId autofillId,
+ @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) {
+ mInputMethodManagerInternal = inputMethodManagerInternal;
+ mUserId = userId;
+ mComponentName = componentName;
+ mHandler = handler;
+ mLock = lock;
+ mUiExtras = uiExtras;
+
+ mAutofillId = autofillId;
+ mImeRequestConsumer = requestConsumer;
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ AutofillId getAutofillIdLocked() {
+ return mAutofillId;
+ }
+
+ /**
+ * Returns the {@link InlineSuggestionsRequest} provided by IME.
+ *
+ * <p> The caller is responsible for making sure Autofill hears back from IME before calling
+ * this method, using the {@link #mImeRequestConsumer}.
+ */
+ @GuardedBy("mLock")
+ Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() {
+ if (mDestroyed) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(mImeRequest);
+ }
+
+ /**
+ * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused
+ * on the {@code autofillId}.
+ *
+ * @return false if the IME callback is not available.
+ */
+ @GuardedBy("mLock")
+ boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
+ @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
+ if (mDestroyed) {
+ return false;
+ }
+ if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked called for:" + autofillId);
+ if (mImeRequest == null || mResponseCallback == null) {
+ return false;
+ }
+ // TODO(b/151123764): each session should only correspond to one field.
+ mAutofillId = autofillId;
+ mInlineSuggestionsResponse = inlineSuggestionsResponse;
+ maybeUpdateResponseToImeLocked();
+ return true;
+ }
+
+ /**
+ * This method must be called when the session is destroyed, to avoid further callbacks from/to
+ * the IME.
+ */
+ @GuardedBy("mLock")
+ void destroySessionLocked() {
+ mDestroyed = true;
+ }
+
+ /**
+ * Requests the IME to create an {@link InlineSuggestionsRequest}.
+ *
+ * <p> This method should only be called once per session.
+ */
+ @GuardedBy("mLock")
+ void onCreateInlineSuggestionsRequestLocked() {
+ if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequestLocked called: " + mAutofillId);
+ if (mDestroyed) {
+ return;
+ }
+ mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(mUserId,
+ new InlineSuggestionsRequestInfo(mComponentName, mAutofillId, mUiExtras),
+ new InlineSuggestionsRequestCallbackImpl(this));
+ mTimeoutCallback = () -> {
+ Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
+ handleOnReceiveImeRequest(null, null);
+ };
+ mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
+ }
+
+ /**
+ * Optionally sends inline response to the IME, depending on the current state.
+ */
+ @GuardedBy("mLock")
+ private void maybeUpdateResponseToImeLocked() {
+ if (sDebug) Log.d(TAG, "maybeUpdateResponseToImeLocked called");
+ if (mDestroyed || mResponseCallback == null) {
+ return;
+ }
+ if (!mImeInputViewStarted && mPreviousResponseIsNotEmpty) {
+ // 1. if previous response is not empty, and IME just become invisible, then send
+ // empty response to make sure existing responses don't stick around on the IME.
+ // Although the inline suggestions should disappear when IME hides which removes them
+ // from the view hierarchy, but we still send an empty response to be extra safe.
+
+ // TODO(b/149945531): clear the existing suggestions when IME is hide, once the bug is
+ // fixed.
+ //if (sDebug) Log.d(TAG, "Send empty inline response");
+ //updateResponseToImeUncheckLocked(new InlineSuggestionsResponse(Collections
+ // .EMPTY_LIST));
+ //mPreviousResponseIsNotEmpty = false;
+ } else if (mImeInputViewStarted && mInlineSuggestionsResponse != null && match(mAutofillId,
+ mImeCurrentFieldId)) {
+ // 2. if IME is visible, and response is not null, send the response
+ boolean isEmptyResponse = mInlineSuggestionsResponse.getInlineSuggestions().isEmpty();
+ if (isEmptyResponse && !mPreviousResponseIsNotEmpty) {
+ // No-op if both the previous response and current response are empty.
+ return;
+ }
+ if (sDebug) {
+ Log.d(TAG, "Send inline response: "
+ + mInlineSuggestionsResponse.getInlineSuggestions().size());
+ }
+ updateResponseToImeUncheckLocked(mInlineSuggestionsResponse);
+ // TODO(b/149945531): don't set the response to null so it's cached, once the bug is
+ // fixed.
+ mInlineSuggestionsResponse = null;
+ mPreviousResponseIsNotEmpty = !isEmptyResponse;
+ }
+ }
+
+ /**
+ * Sends the {@code response} to the IME, assuming all the relevant checks are already done.
+ */
+ @GuardedBy("mLock")
+ private void updateResponseToImeUncheckLocked(InlineSuggestionsResponse response) {
+ if (mDestroyed) {
+ return;
+ }
+ try {
+ mResponseCallback.onInlineSuggestionsResponse(mAutofillId, response);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
+ }
+ }
+
+ /**
+ * Handles the {@code request} and {@code callback} received from the IME.
+ *
+ * <p> Should only invoked in the {@link #mHandler} thread.
+ */
+ private void handleOnReceiveImeRequest(@Nullable InlineSuggestionsRequest request,
+ @Nullable IInlineSuggestionsResponseCallback callback) {
+ synchronized (mLock) {
+ if (mDestroyed || mImeRequestReceived) {
+ return;
+ }
+ mImeRequestReceived = true;
+
+ if (mTimeoutCallback != null) {
+ if (sDebug) Log.d(TAG, "removing timeout callback");
+ mHandler.removeCallbacks(mTimeoutCallback);
+ mTimeoutCallback = null;
+ }
+ if (request != null && callback != null) {
+ mImeRequest = request;
+ mResponseCallback = callback;
+ handleOnReceiveImeStatusUpdated(mAutofillId, true, false);
+ }
+ if (mImeRequestConsumer != null) {
+ // Note that mImeRequest is only set if both request and callback are non-null.
+ mImeRequestConsumer.accept(mImeRequest);
+ mImeRequestConsumer = null;
+ }
+ }
+ }
+
+ /**
+ * Handles the IME status updates received from the IME.
+ *
+ * <p> Should only be invoked in the {@link #mHandler} thread.
+ */
+ private void handleOnReceiveImeStatusUpdated(boolean imeInputStarted,
+ boolean imeInputViewStarted) {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ if (mImeCurrentFieldId != null) {
+ boolean imeInputStartedChanged = (mImeInputStarted != imeInputStarted);
+ boolean imeInputViewStartedChanged = (mImeInputViewStarted != imeInputViewStarted);
+ mImeInputStarted = imeInputStarted;
+ mImeInputViewStarted = imeInputViewStarted;
+ if (imeInputStartedChanged || imeInputViewStartedChanged) {
+ maybeUpdateResponseToImeLocked();
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles the IME status updates received from the IME.
+ *
+ * <p> Should only be invoked in the {@link #mHandler} thread.
+ */
+ private void handleOnReceiveImeStatusUpdated(@Nullable AutofillId imeFieldId,
+ boolean imeInputStarted, boolean imeInputViewStarted) {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ if (imeFieldId != null) {
+ mImeCurrentFieldId = imeFieldId;
+ }
+ handleOnReceiveImeStatusUpdated(imeInputStarted, imeInputViewStarted);
+ }
+ }
+
+ private static final class InlineSuggestionsRequestCallbackImpl extends
+ IInlineSuggestionsRequestCallback.Stub {
+
+ private final WeakReference<AutofillInlineSuggestionsRequestSession> mSession;
+
+ private InlineSuggestionsRequestCallbackImpl(
+ AutofillInlineSuggestionsRequestSession session) {
+ mSession = new WeakReference<>(session);
+ }
+
+ @BinderThread
+ @Override
+ public void onInlineSuggestionsUnsupported() throws RemoteException {
+ if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session,
+ null, null));
+ }
+ }
+
+ @BinderThread
+ @Override
+ public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+ IInlineSuggestionsResponseCallback callback) {
+ if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session,
+ request, callback));
+ }
+ }
+
+ @Override
+ public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
+ if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId);
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+ session, imeFieldId, true, false));
+ }
+ }
+
+ @Override
+ public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
+ if (sDebug) {
+ Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
+ }
+ }
+
+ @BinderThread
+ @Override
+ public void onInputMethodStartInputView() {
+ if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received");
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+ session, true, true));
+ }
+ }
+
+ @BinderThread
+ @Override
+ public void onInputMethodFinishInputView() {
+ if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received");
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+ session, true, false));
+ }
+ }
+
+ @Override
+ public void onInputMethodFinishInput() throws RemoteException {
+ if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received");
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+ session, false, false));
+ }
+ }
+ }
+
+ private static boolean match(@Nullable AutofillId autofillId,
+ @Nullable AutofillId imeClientFieldId) {
+ // The IME doesn't have information about the virtual view id for the child views in the
+ // web view, so we are only comparing the parent view id here. This means that for cases
+ // where there are two input fields in the web view, they will have the same view id
+ // (although different virtual child id), and we will not be able to distinguish them.
+ return autofillId != null && imeClientFieldId != null
+ && autofillId.getViewId() == imeClientFieldId.getViewId();
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
deleted file mode 100644
index e2d5112..0000000
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.autofill;
-
-import static com.android.server.autofill.Helper.sDebug;
-
-import android.annotation.BinderThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Slog;
-import android.view.autofill.AutofillId;
-import android.view.inputmethod.InlineSuggestionsRequest;
-import android.view.inputmethod.InlineSuggestionsResponse;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.view.IInlineSuggestionsRequestCallback;
-import com.android.internal.view.IInlineSuggestionsResponseCallback;
-import com.android.internal.view.InlineSuggestionsRequestInfo;
-import com.android.server.inputmethod.InputMethodManagerInternal;
-
-import java.util.Collections;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
-
-/**
- * Maintains an autofill inline suggestion session that communicates with the IME.
- *
- * <p>
- * The same session may be reused for multiple input fields involved in the same autofill
- * {@link Session}. Therefore, one {@link InlineSuggestionsRequest} and one
- * {@link IInlineSuggestionsResponseCallback} may be used to generate and callback with inline
- * suggestions for different input fields.
- *
- * <p>
- * This class is the sole place in Autofill responsible for directly communicating with the IME. It
- * receives the IME input view start/finish events, with the associated IME field Id. It uses the
- * information to decide when to send the {@link InlineSuggestionsResponse} to IME. As a result,
- * some of the response will be cached locally and only be sent when the IME is ready to show them.
- *
- * <p>
- * See {@link android.inputmethodservice.InlineSuggestionSession} comments for InputMethodService
- * side flow.
- *
- * <p>
- * This class should hold the same lock as {@link Session} as they call into each other.
- */
-final class InlineSuggestionSession {
-
- private static final String TAG = "AfInlineSuggestionSession";
- private static final int INLINE_REQUEST_TIMEOUT_MS = 200;
-
- @NonNull
- private final InputMethodManagerInternal mInputMethodManagerInternal;
- private final int mUserId;
- @NonNull
- private final ComponentName mComponentName;
- @NonNull
- private final Object mLock;
- @NonNull
- private final ImeStatusListener mImeStatusListener;
- @NonNull
- private final Handler mHandler;
-
- /**
- * To avoid the race condition, one should not access {@code mPendingImeResponse} without
- * holding the {@code mLock}. For consuming the existing value, tt's recommended to use
- * {@link #getPendingImeResponse()} to get a copy of the reference to avoid blocking call.
- */
- @GuardedBy("mLock")
- @Nullable
- private CompletableFuture<ImeResponse> mPendingImeResponse;
-
- @GuardedBy("mLock")
- @Nullable
- private AutofillResponse mPendingAutofillResponse;
-
- @GuardedBy("mLock")
- private boolean mIsLastResponseNonEmpty = false;
-
- @Nullable
- @GuardedBy("mLock")
- private AutofillId mImeFieldId = null;
-
- @GuardedBy("mLock")
- private boolean mImeInputViewStarted = false;
-
- InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal,
- int userId, ComponentName componentName, Handler handler, Object lock) {
- mInputMethodManagerInternal = inputMethodManagerInternal;
- mUserId = userId;
- mComponentName = componentName;
- mHandler = handler;
- mLock = lock;
- mImeStatusListener = new ImeStatusListener() {
- @Override
- public void onInputMethodStartInput(AutofillId imeFieldId) {
- synchronized (mLock) {
- mImeFieldId = imeFieldId;
- mImeInputViewStarted = false;
- }
- }
-
- @Override
- public void onInputMethodStartInputView() {
- synchronized (mLock) {
- mImeInputViewStarted = true;
- AutofillResponse pendingAutofillResponse = mPendingAutofillResponse;
- if (pendingAutofillResponse != null
- && pendingAutofillResponse.mAutofillId.equalsIgnoreSession(
- mImeFieldId)) {
- mPendingAutofillResponse = null;
- onInlineSuggestionsResponseLocked(pendingAutofillResponse.mAutofillId,
- pendingAutofillResponse.mResponse);
- }
- }
- }
-
- @Override
- public void onInputMethodFinishInputView() {
- synchronized (mLock) {
- mImeInputViewStarted = false;
- }
- }
-
- @Override
- public void onInputMethodFinishInput() {
- synchronized (mLock) {
- mImeFieldId = null;
- }
- }
- };
- }
-
- public void onCreateInlineSuggestionsRequest(@NonNull AutofillId autofillId,
- @NonNull Consumer<InlineSuggestionsRequest> requestConsumer) {
- if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequest called for " + autofillId);
-
- synchronized (mLock) {
- // Clean up all the state about the previous request.
- hideInlineSuggestionsUi(autofillId);
- mImeFieldId = null;
- mImeInputViewStarted = false;
- if (mPendingImeResponse != null && !mPendingImeResponse.isDone()) {
- mPendingImeResponse.complete(null);
- }
- mPendingImeResponse = new CompletableFuture<>();
- // TODO(b/146454892): pipe the uiExtras from the ExtServices.
- mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
- mUserId,
- new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()),
- new InlineSuggestionsRequestCallbackImpl(autofillId, mPendingImeResponse,
- mImeStatusListener, requestConsumer, mHandler, mLock));
- }
- }
-
- public Optional<InlineSuggestionsRequest> getInlineSuggestionsRequest() {
- final CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse();
- if (pendingImeResponse == null || !pendingImeResponse.isDone()) {
- return Optional.empty();
- }
- return Optional.ofNullable(pendingImeResponse.getNow(null)).map(ImeResponse::getRequest);
- }
-
- public boolean hideInlineSuggestionsUi(@NonNull AutofillId autofillId) {
- synchronized (mLock) {
- if (mIsLastResponseNonEmpty) {
- return onInlineSuggestionsResponseLocked(autofillId,
- new InlineSuggestionsResponse(Collections.EMPTY_LIST));
- }
- return false;
- }
- }
-
- public boolean onInlineSuggestionsResponse(@NonNull AutofillId autofillId,
- @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
- synchronized (mLock) {
- return onInlineSuggestionsResponseLocked(autofillId, inlineSuggestionsResponse);
- }
- }
-
- private boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
- @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
- final CompletableFuture<ImeResponse> completedImsResponse = getPendingImeResponse();
- if (completedImsResponse == null || !completedImsResponse.isDone()) {
- if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked without IMS request");
- return false;
- }
- // There is no need to wait on the CompletableFuture since it should have been completed.
- ImeResponse imeResponse = completedImsResponse.getNow(null);
- if (imeResponse == null) {
- if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked with pending IMS response");
- return false;
- }
-
- // TODO(b/151846600): IME doesn't have access to the virtual id of the webview, so we
- // only compare the view id for now.
- if (!mImeInputViewStarted || mImeFieldId == null
- || autofillId.getViewId() != mImeFieldId.getViewId()) {
- if (sDebug) {
- Log.d(TAG,
- "onInlineSuggestionsResponseLocked not sent because input view is not "
- + "started for " + autofillId);
- }
- mPendingAutofillResponse = new AutofillResponse(autofillId, inlineSuggestionsResponse);
- // TODO(b/149442582): Although we are not sending the response to IME right away, we
- // still return true to indicate that the response may be sent eventually, such that
- // the dropdown UI will not be shown. This may not be the desired behavior in the
- // auto-focus case where IME isn't shown after switching back to an activity. We may
- // revisit this.
- return true;
- }
-
- try {
- imeResponse.mCallback.onInlineSuggestionsResponse(autofillId,
- inlineSuggestionsResponse);
- mIsLastResponseNonEmpty = !inlineSuggestionsResponse.getInlineSuggestions().isEmpty();
- if (sDebug) {
- Log.d(TAG, "Autofill sends inline response to IME: "
- + inlineSuggestionsResponse.getInlineSuggestions().size());
- }
- return true;
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
- return false;
- }
- }
-
- @Nullable
- @GuardedBy("mLock")
- private CompletableFuture<ImeResponse> getPendingImeResponse() {
- synchronized (mLock) {
- return mPendingImeResponse;
- }
- }
-
- private static final class InlineSuggestionsRequestCallbackImpl
- extends IInlineSuggestionsRequestCallback.Stub {
-
- private final Object mLock;
- private final AutofillId mAutofillId;
- @GuardedBy("mLock")
- private final CompletableFuture<ImeResponse> mResponse;
- @GuardedBy("mLock")
- private final Consumer<InlineSuggestionsRequest> mRequestConsumer;
- private final ImeStatusListener mImeStatusListener;
- private final Handler mHandler;
- private final Runnable mTimeoutCallback;
-
- private InlineSuggestionsRequestCallbackImpl(AutofillId autofillId,
- CompletableFuture<ImeResponse> response,
- ImeStatusListener imeStatusListener,
- Consumer<InlineSuggestionsRequest> requestConsumer,
- Handler handler, Object lock) {
- mAutofillId = autofillId;
- mResponse = response;
- mImeStatusListener = imeStatusListener;
- mRequestConsumer = requestConsumer;
- mLock = lock;
-
- mHandler = handler;
- mTimeoutCallback = () -> {
- Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
- completeIfNot(null);
- };
- mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
- }
-
- private void completeIfNot(@Nullable ImeResponse response) {
- synchronized (mLock) {
- if (mResponse.isDone()) {
- return;
- }
- mResponse.complete(response);
- mRequestConsumer.accept(response == null ? null : response.mRequest);
- mHandler.removeCallbacks(mTimeoutCallback);
- }
- }
-
- @BinderThread
- @Override
- public void onInlineSuggestionsUnsupported() throws RemoteException {
- if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
- completeIfNot(null);
- }
-
- @BinderThread
- @Override
- public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
- IInlineSuggestionsResponseCallback callback) {
- if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
- mImeStatusListener.onInputMethodStartInput(mAutofillId);
- if (request != null && callback != null) {
- completeIfNot(new ImeResponse(request, callback));
- } else {
- completeIfNot(null);
- }
- }
-
- @Override
- public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
- if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId);
- mImeStatusListener.onInputMethodStartInput(imeFieldId);
- }
-
- @Override
- public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
- if (sDebug) {
- Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
- }
- // TODO(b/151123764): use this signal to adjust the timeout on Autofill side waiting for
- // IME to show.
- }
-
- @BinderThread
- @Override
- public void onInputMethodStartInputView() {
- if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received");
- mImeStatusListener.onInputMethodStartInputView();
- }
-
- @BinderThread
- @Override
- public void onInputMethodFinishInputView() {
- if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received");
- mImeStatusListener.onInputMethodFinishInputView();
- }
-
- @Override
- public void onInputMethodFinishInput() throws RemoteException {
- if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received");
- mImeStatusListener.onInputMethodFinishInput();
- }
- }
-
- private interface ImeStatusListener {
- void onInputMethodStartInput(AutofillId imeFieldId);
-
- void onInputMethodStartInputView();
-
- void onInputMethodFinishInputView();
-
- void onInputMethodFinishInput();
- }
-
- /**
- * A data class wrapping Autofill responses for the inline suggestion request.
- */
- private static class AutofillResponse {
- @NonNull
- final AutofillId mAutofillId;
-
- @NonNull
- final InlineSuggestionsResponse mResponse;
-
- AutofillResponse(@NonNull AutofillId autofillId,
- @NonNull InlineSuggestionsResponse response) {
- mAutofillId = autofillId;
- mResponse = response;
- }
-
- }
-
- /**
- * A data class wrapping IME responses for the create inline suggestions request.
- */
- private static class ImeResponse {
- @NonNull
- final InlineSuggestionsRequest mRequest;
-
- @NonNull
- final IInlineSuggestionsResponseCallback mCallback;
-
- ImeResponse(@NonNull InlineSuggestionsRequest request,
- @NonNull IInlineSuggestionsResponseCallback callback) {
- mRequest = request;
- mCallback = callback;
- }
-
- InlineSuggestionsRequest getRequest() {
- return mRequest;
- }
- }
-}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 1290569..4ecffd8 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -304,7 +304,7 @@
private boolean mForAugmentedAutofillOnly;
@Nullable
- private final InlineSuggestionSession mInlineSuggestionSession;
+ private final AutofillInlineSessionController mInlineSessionController;
/**
* Receiver of assist data from the app's {@link Activity}.
@@ -720,8 +720,9 @@
Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
if (inlineSuggestionsRequestConsumer != null) {
- mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
- inlineSuggestionsRequestConsumer);
+ // TODO(b/146454892): pipe the uiExtras from the ExtServices.
+ mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId,
+ inlineSuggestionsRequestConsumer, Bundle.EMPTY);
}
} else {
mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false);
@@ -777,8 +778,8 @@
mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
setClientLocked(client);
- mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId,
- componentName, handler, mLock);
+ mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal,
+ userId, componentName, handler, mLock);
mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
@@ -2561,7 +2562,7 @@
if (sVerbose) Slog.v(TAG, "Exiting view " + id);
mUi.hideFillUi(this);
hideAugmentedAutofillLocked(viewState);
- mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId);
+ mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
mCurrentViewId = null;
}
break;
@@ -2779,7 +2780,7 @@
private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
@Nullable String filterText) {
final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest =
- mInlineSuggestionSession.getInlineSuggestionsRequest();
+ mInlineSessionController.getInlineSuggestionsRequestLocked();
if (!inlineSuggestionsRequest.isPresent()) {
Log.w(TAG, "InlineSuggestionsRequest unavailable");
return false;
@@ -2801,7 +2802,8 @@
inlineSuggestionsRequest.get(), response, filterText, mCurrentViewId,
this, () -> {
synchronized (mLock) {
- mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId);
+ mInlineSessionController.hideInlineSuggestionsUiLocked(
+ mCurrentViewId);
}
}, remoteRenderService);
if (inlineSuggestionsResponse == null) {
@@ -2809,7 +2811,7 @@
return false;
}
- return mInlineSuggestionSession.onInlineSuggestionsResponse(mCurrentViewId,
+ return mInlineSessionController.onInlineSuggestionsResponseLocked(mCurrentViewId,
inlineSuggestionsResponse);
}
@@ -3106,8 +3108,13 @@
focusedId,
currentValue, inlineSuggestionsRequest,
/*inlineSuggestionsCallback=*/
- response -> mInlineSuggestionSession.onInlineSuggestionsResponse(
- mCurrentViewId, response),
+ response -> {
+ synchronized (mLock) {
+ return mInlineSessionController
+ .onInlineSuggestionsResponseLocked(
+ mCurrentViewId, response);
+ }
+ },
/*onErrorCallback=*/ () -> {
synchronized (mLock) {
cancelAugmentedAutofillLocked();
@@ -3125,11 +3132,12 @@
&& (mForAugmentedAutofillOnly
|| !isInlineSuggestionsEnabledByAutofillProviderLocked())) {
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
- mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
- /*requestConsumer=*/ requestAugmentedAutofill);
+ // TODO(b/146454892): pipe the uiExtras from the ExtServices.
+ mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId,
+ /*requestConsumer=*/ requestAugmentedAutofill, Bundle.EMPTY);
} else {
requestAugmentedAutofill.accept(
- mInlineSuggestionSession.getInlineSuggestionsRequest().orElse(null));
+ mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null));
}
if (mAugmentedAutofillDestroyer == null) {
mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 192ea72..3c0d880 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -119,8 +119,6 @@
private static final int MESSAGE_DISABLE = 2;
private static final int MESSAGE_HANDLE_ENABLE_DELAYED = 3;
private static final int MESSAGE_HANDLE_DISABLE_DELAYED = 4;
- private static final int MESSAGE_REGISTER_ADAPTER = 20;
- private static final int MESSAGE_UNREGISTER_ADAPTER = 21;
private static final int MESSAGE_REGISTER_STATE_CHANGE_CALLBACK = 30;
private static final int MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK = 31;
private static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40;
@@ -642,10 +640,9 @@
Slog.w(TAG, "Callback is null in registerAdapter");
return null;
}
- Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_ADAPTER);
- msg.obj = callback;
- mHandler.sendMessage(msg);
-
+ synchronized (mCallbacks) {
+ mCallbacks.register(callback);
+ }
return mBluetooth;
}
@@ -655,9 +652,9 @@
return;
}
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_ADAPTER);
- msg.obj = callback;
- mHandler.sendMessage(msg);
+ synchronized (mCallbacks) {
+ mCallbacks.unregister(callback);
+ }
}
public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) {
@@ -1559,18 +1556,20 @@
* Inform BluetoothAdapter instances that Adapter service is up
*/
private void sendBluetoothServiceUpCallback() {
- try {
- int n = mCallbacks.beginBroadcast();
- Slog.d(TAG, "Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
- for (int i = 0; i < n; i++) {
- try {
- mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
+ synchronized (mCallbacks) {
+ try {
+ int n = mCallbacks.beginBroadcast();
+ Slog.d(TAG, "Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
+ }
}
+ } finally {
+ mCallbacks.finishBroadcast();
}
- } finally {
- mCallbacks.finishBroadcast();
}
}
@@ -1578,18 +1577,20 @@
* Inform BluetoothAdapter instances that Adapter service is down
*/
private void sendBluetoothServiceDownCallback() {
- try {
- int n = mCallbacks.beginBroadcast();
- Slog.d(TAG, "Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
- for (int i = 0; i < n; i++) {
- try {
- mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
+ synchronized (mCallbacks) {
+ try {
+ int n = mCallbacks.beginBroadcast();
+ Slog.d(TAG, "Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
+ }
}
+ } finally {
+ mCallbacks.finishBroadcast();
}
- } finally {
- mCallbacks.finishBroadcast();
}
}
@@ -1917,17 +1918,6 @@
mContext.getPackageName());
}
break;
-
- case MESSAGE_REGISTER_ADAPTER: {
- IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj;
- mCallbacks.register(callback);
- break;
- }
- case MESSAGE_UNREGISTER_ADAPTER: {
- IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj;
- mCallbacks.unregister(callback);
- break;
- }
case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: {
IBluetoothStateChangeCallback callback =
(IBluetoothStateChangeCallback) msg.obj;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index a9c3079..7f25de6b 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1836,12 +1836,13 @@
@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
- PendingIntent intent, String packageName, String featureId) {
+ PendingIntent intent, String packageName, String featureId, String listenerId) {
if (request == null) {
request = DEFAULT_LOCATION_REQUEST;
}
- CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId);
+ CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId,
+ listenerId);
identity.enforceLocationPermission();
WorkSource workSource = request.getWorkSource();
@@ -2027,7 +2028,7 @@
@Override
public boolean getCurrentLocation(LocationRequest locationRequest,
ICancellationSignal remoteCancellationSignal, ILocationListener listener,
- String packageName, String featureId) {
+ String packageName, String featureId, String listenerId) {
// side effect of validating locationRequest and packageName
Location lastLocation = getLastLocation(locationRequest, packageName, featureId);
if (lastLocation != null) {
@@ -2052,7 +2053,7 @@
}
}
- requestLocationUpdates(locationRequest, listener, null, packageName, featureId);
+ requestLocationUpdates(locationRequest, listener, null, packageName, featureId, listenerId);
CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
remoteCancellationSignal);
if (cancellationSignal != null) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0671477..9aefc8d 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -4468,9 +4468,8 @@
String.format("/storage/emulated/%d/Android/data/%s/",
userId, pkg);
- int appUid =
- UserHandle.getUid(userId, mPmInternal.getPackage(pkg).getUid());
// Create package obb and data dir if it doesn't exist.
+ int appUid = UserHandle.getUid(userId, mPmInternal.getPackage(pkg).getUid());
File file = new File(packageObbDir);
if (!file.exists()) {
vold.setupAppDir(packageObbDir, appUid);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 061ff42..58972a5 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -123,6 +123,8 @@
"android.hardware.neuralnetworks@1.0::IDevice",
"android.hardware.power.stats@1.0::IPowerStats",
"android.hardware.sensors@1.0::ISensors",
+ "android.hardware.sensors@2.0::ISensors",
+ "android.hardware.sensors@2.1::ISensors",
"android.hardware.vr@1.0::IVr",
"android.system.suspend@1.0::ISystemSuspend"
);
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/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index dbcb3da..2d6ef81 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -231,7 +231,8 @@
final ServiceThread adjusterThread =
new ServiceThread(TAG, TOP_APP_PRIORITY_BOOST, false /* allowIo */);
adjusterThread.start();
- Process.setThreadGroupAndCpuset(adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP);
+ adjusterThread.getThreadHandler().post(() -> Process.setThreadGroupAndCpuset(
+ adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP));
return adjusterThread;
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 595275d..786e9cf 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -97,6 +97,7 @@
import android.system.Os;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.LongSparseArray;
import android.util.Pair;
@@ -137,6 +138,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Activity manager code dealing with processes.
@@ -1846,11 +1848,13 @@
runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
}
- // Enable heap pointer tagging, unless disabled by the app manifest, target sdk level,
- // or the compat feature.
- if (app.info.allowsNativeHeapPointerTagging()
- && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
- runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+ if (Zygote.nativeSupportsTaggedPointers()) {
+ // Enable heap pointer tagging if supported by the kernel, unless disabled by the
+ // app manifest, target sdk level, or compat feature.
+ if (app.info.allowsNativeHeapPointerTagging()
+ && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
+ runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+ }
}
runtimeFlags |= decideGwpAsanLevel(app);
@@ -2127,18 +2131,11 @@
for (String packageName : packages) {
String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
long inode = pmInt.getCeDataInode(packageName, userId);
- if (inode != 0) {
- result.put(packageName, Pair.create(volumeUuid, inode));
+ if (inode == 0) {
+ Slog.w(TAG, packageName + " inode == 0 (b/152760674)");
+ return null;
}
- }
- if (mAppDataIsolationWhitelistedApps != null) {
- for (String packageName : mAppDataIsolationWhitelistedApps) {
- String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
- long inode = pmInt.getCeDataInode(packageName, userId);
- if (inode != 0) {
- result.put(packageName, Pair.create(volumeUuid, inode));
- }
- }
+ result.put(packageName, Pair.create(volumeUuid, inode));
}
return result;
@@ -2160,35 +2157,56 @@
app.setHasForegroundActivities(true);
}
+ final Map<String, Pair<String, Long>> pkgDataInfoMap;
+ final Map<String, Pair<String, Long>> whitelistedAppDataInfoMap;
+ boolean bindMountAppStorageDirs = false;
+ boolean bindMountAppsData = mAppDataIsolationEnabled
+ && UserHandle.isApp(app.uid)
+ && mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info);
+
+ // Get all packages belongs to the same shared uid. sharedPackages is empty array
+ // if it doesn't have shared uid.
+ final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
+ final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage(
+ app.info.packageName, app.userId);
+ final String[] targetPackagesList = sharedPackages.length == 0
+ ? new String[]{app.info.packageName} : sharedPackages;
+
+ pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, targetPackagesList, uid);
+ if (pkgDataInfoMap == null) {
+ // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a
+ // tmp free pass.
+ bindMountAppsData = false;
+ }
+
+ // Remove all packages in pkgDataInfoMap from mAppDataIsolationWhitelistedApps, so
+ // it won't be mounted twice.
+ final Set<String> whitelistedApps = new ArraySet<>(mAppDataIsolationWhitelistedApps);
+ for (String pkg : targetPackagesList) {
+ whitelistedApps.remove(pkg);
+ }
+
+ whitelistedAppDataInfoMap = getPackageAppDataInfoMap(pmInt,
+ whitelistedApps.toArray(new String[0]), uid);
+ if (whitelistedAppDataInfoMap == null) {
+ // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a
+ // tmp free pass.
+ bindMountAppsData = false;
+ }
+
+ int userId = UserHandle.getUserId(uid);
StorageManagerInternal storageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
- final Map<String, Pair<String, Long>> pkgDataInfoMap;
- boolean bindMountAppStorageDirs = false;
-
- if (mAppDataIsolationEnabled && UserHandle.isApp(app.uid)
- && mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info)) {
- // Get all packages belongs to the same shared uid. sharedPackages is empty array
- // if it doesn't have shared uid.
- final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
- final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage(
- app.info.packageName, app.userId);
- pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, sharedPackages.length == 0
- ? new String[]{app.info.packageName} : sharedPackages, uid);
-
- int userId = UserHandle.getUserId(uid);
- if (mVoldAppDataIsolationEnabled
- && !storageManagerInternal.isExternalStorageService(uid)) {
- bindMountAppStorageDirs = true;
- if (!storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(),
- app.processName)) {
- // Cannot prepare Android/app and Android/obb directory,
- // so we won't mount it in zygote.
- app.bindMountPending = true;
- bindMountAppStorageDirs = false;
- }
+ if (mVoldAppDataIsolationEnabled && UserHandle.isApp(app.uid)
+ && !storageManagerInternal.isExternalStorageService(uid)) {
+ bindMountAppStorageDirs = true;
+ if (!storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(),
+ app.processName)) {
+ // Cannot prepare Android/app and Android/obb directory,
+ // so we won't mount it in zygote.
+ app.bindMountPending = true;
+ bindMountAppStorageDirs = false;
}
- } else {
- pkgDataInfoMap = null;
}
final Process.ProcessStartResult startResult;
@@ -2206,7 +2224,8 @@
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
/*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
- app.mDisabledCompatChanges, pkgDataInfoMap, bindMountAppStorageDirs,
+ app.mDisabledCompatChanges, pkgDataInfoMap, whitelistedAppDataInfoMap,
+ bindMountAppsData, bindMountAppStorageDirs,
new String[]{PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
@@ -2214,7 +2233,7 @@
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
isTopApp, app.mDisabledCompatChanges, pkgDataInfoMap,
- bindMountAppStorageDirs,
+ whitelistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
new String[]{PROC_START_SEQ_IDENT + app.startSeq});
}
checkSlow(startTime, "startProcess: returned from zygote!");
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 4431abe..808f8c21 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -558,8 +558,10 @@
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
statsModality(), BiometricsProtoEnums.ISSUE_CANCEL_TIMED_OUT);
+ ClientMonitor newClient = mPendingClient;
mCurrentClient = null;
- startClient(mPendingClient, false);
+ mPendingClient = null;
+ startClient(newClient, 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/display/color/AppSaturationController.java b/services/core/java/com/android/server/display/color/AppSaturationController.java
index e42be02..6a685bf 100644
--- a/services/core/java/com/android/server/display/color/AppSaturationController.java
+++ b/services/core/java/com/android/server/display/color/AppSaturationController.java
@@ -17,6 +17,7 @@
package com.android.server.display.color;
import android.annotation.UserIdInt;
+import android.util.ArrayMap;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -61,11 +62,12 @@
* Set the saturation level ({@code ColorDisplayManager#SaturationLevel} constant for a given
* package name and userId.
*/
- public boolean setSaturationLevel(String packageName, @UserIdInt int userId,
+ public boolean setSaturationLevel(String callingPackageName, String affectedPackageName,
+ @UserIdInt int userId,
int saturationLevel) {
synchronized (mLock) {
- return getSaturationControllerLocked(packageName, userId)
- .setSaturationLevel(saturationLevel);
+ return getSaturationControllerLocked(affectedPackageName, userId)
+ .setSaturationLevel(callingPackageName, saturationLevel);
}
}
@@ -148,13 +150,19 @@
private static class SaturationController {
+ private static final int FULL_SATURATION = 100;
+
private final List<WeakReference<ColorTransformController>> mControllerRefs =
new ArrayList<>();
- private int mSaturationLevel = 100;
+ private final ArrayMap<String, Integer> mSaturationLevels = new ArrayMap<>();
private float[] mTransformMatrix = new float[9];
- private boolean setSaturationLevel(int saturationLevel) {
- mSaturationLevel = saturationLevel;
+ private boolean setSaturationLevel(String callingPackageName, int saturationLevel) {
+ if (saturationLevel == FULL_SATURATION) {
+ mSaturationLevels.remove(callingPackageName);
+ } else {
+ mSaturationLevels.put(callingPackageName, saturationLevel);
+ }
if (!mControllerRefs.isEmpty()) {
return updateState();
}
@@ -163,17 +171,27 @@
private boolean addColorTransformController(
WeakReference<ColorTransformController> controller) {
+ clearExpiredReferences();
mControllerRefs.add(controller);
- if (mSaturationLevel != 100) {
+ if (!mSaturationLevels.isEmpty()) {
return updateState();
- } else {
- clearExpiredReferences();
}
return false;
}
+ private int calculateSaturationLevel() {
+ int saturationLevel = FULL_SATURATION;
+ for (int i = 0; i < mSaturationLevels.size(); i++) {
+ final int level = mSaturationLevels.valueAt(i);
+ if (level < saturationLevel) {
+ saturationLevel = level;
+ }
+ }
+ return saturationLevel;
+ }
+
private boolean updateState() {
- computeGrayscaleTransformMatrix(mSaturationLevel / 100f, mTransformMatrix);
+ computeGrayscaleTransformMatrix(calculateSaturationLevel() / 100f, mTransformMatrix);
boolean updated = false;
final Iterator<WeakReference<ColorTransformController>> iterator = mControllerRefs
@@ -190,7 +208,6 @@
}
}
return updated;
-
}
private void clearExpiredReferences() {
@@ -206,7 +223,7 @@
}
private void dump(PrintWriter pw) {
- pw.println(" mSaturationLevel: " + mSaturationLevel);
+ pw.println(" mSaturationLevels: " + mSaturationLevels);
pw.println(" mControllerRefs count: " + mControllerRefs.size());
}
}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 2dc2cf0..95a98f1 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -44,6 +44,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.display.ColorDisplayManager;
@@ -73,6 +74,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.server.DisplayThread;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
@@ -817,9 +819,11 @@
return LocalDateTime.MIN;
}
- private boolean setAppSaturationLevelInternal(String packageName, int saturationLevel) {
+ private boolean setAppSaturationLevelInternal(String callingPackageName,
+ String affectedPackageName, int saturationLevel) {
return mAppSaturationController
- .setSaturationLevel(packageName, mCurrentUser, saturationLevel);
+ .setSaturationLevel(callingPackageName, affectedPackageName, mCurrentUser,
+ saturationLevel);
}
private void setColorModeInternal(@ColorMode int colorMode) {
@@ -1533,9 +1537,11 @@
getContext().enforceCallingPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to set display saturation level");
+ final String callingPackageName = LocalServices.getService(PackageManagerInternal.class)
+ .getNameForUid(Binder.getCallingUid());
final long token = Binder.clearCallingIdentity();
try {
- return setAppSaturationLevelInternal(packageName, level);
+ return setAppSaturationLevelInternal(callingPackageName, packageName, level);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/location/AppOpsHelper.java b/services/core/java/com/android/server/location/AppOpsHelper.java
index 9c27916..cb64c50 100644
--- a/services/core/java/com/android/server/location/AppOpsHelper.java
+++ b/services/core/java/com/android/server/location/AppOpsHelper.java
@@ -191,7 +191,7 @@
callerIdentity.uid,
callerIdentity.packageName,
callerIdentity.featureId,
- null) == AppOpsManager.MODE_ALLOWED;
+ callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -210,7 +210,7 @@
callerIdentity.packageName,
false,
callerIdentity.featureId,
- null) == AppOpsManager.MODE_ALLOWED;
+ callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -245,7 +245,7 @@
callerIdentity.uid,
callerIdentity.packageName,
callerIdentity.featureId,
- null) == AppOpsManager.MODE_ALLOWED;
+ callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/location/CallerIdentity.java b/services/core/java/com/android/server/location/CallerIdentity.java
index b84fd13..8d508bb 100644
--- a/services/core/java/com/android/server/location/CallerIdentity.java
+++ b/services/core/java/com/android/server/location/CallerIdentity.java
@@ -83,12 +83,22 @@
*/
public static CallerIdentity fromBinder(Context context, String packageName,
@Nullable String featureId) {
+ return fromBinder(context, packageName, featureId, null);
+ }
+
+ /**
+ * Creates a CallerIdentity from the current binder identity, using the given package, feature
+ * id, and listener id. The package will be checked to enforce it belongs to the calling uid,
+ * and a security exception will be thrown if it is invalid.
+ */
+ public static CallerIdentity fromBinder(Context context, String packageName,
+ @Nullable String featureId, @Nullable String listenerId) {
int uid = Binder.getCallingUid();
if (!ArrayUtils.contains(context.getPackageManager().getPackagesForUid(uid), packageName)) {
throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid);
}
- return fromBinderUnsafe(context, packageName, featureId);
+ return fromBinderUnsafe(context, packageName, featureId, listenerId);
}
/**
@@ -99,8 +109,19 @@
*/
public static CallerIdentity fromBinderUnsafe(Context context, String packageName,
@Nullable String featureId) {
+ return fromBinderUnsafe(context, packageName, featureId, null);
+ }
+
+ /**
+ * Creates a CallerIdentity from the current binder identity, using the given package, feature
+ * id, and listener id. The package will not be checked to enforce that it belongs to the
+ * calling uid - this method should only be used if the package will be validated by some other
+ * means, such as an appops call.
+ */
+ public static CallerIdentity fromBinderUnsafe(Context context, String packageName,
+ @Nullable String featureId, @Nullable String listenerId) {
return new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(),
- UserHandle.getCallingUserId(), packageName, featureId,
+ UserHandle.getCallingUserId(), packageName, featureId, listenerId,
getBinderPermissionLevel(context));
}
@@ -157,6 +178,9 @@
/** The calling feature id. */
public final @Nullable String featureId;
+ /** The calling listener id. */
+ public final @Nullable String listenerId;
+
/**
* The calling location permission level. This field should only be used for validating
* permissions for API access. It should not be used for validating permissions for location
@@ -167,11 +191,18 @@
@VisibleForTesting
public CallerIdentity(int uid, int pid, int userId, String packageName,
@Nullable String featureId, @PermissionLevel int permissionLevel) {
+ this(uid, pid, userId, packageName, featureId, null, permissionLevel);
+ }
+
+ private CallerIdentity(int uid, int pid, int userId, String packageName,
+ @Nullable String featureId, @Nullable String listenerId,
+ @PermissionLevel int permissionLevel) {
this.uid = uid;
this.pid = pid;
this.userId = userId;
this.packageName = Objects.requireNonNull(packageName);
this.featureId = featureId;
+ this.listenerId = listenerId;
this.permissionLevel = Preconditions.checkArgumentInRange(permissionLevel, PERMISSION_NONE,
PERMISSION_FINE, "permissionLevel");
}
@@ -216,7 +247,8 @@
return uid == that.uid
&& pid == that.pid
&& packageName.equals(that.packageName)
- && Objects.equals(featureId, that.featureId);
+ && Objects.equals(featureId, that.featureId)
+ && Objects.equals(listenerId, that.listenerId);
}
@Override
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index ad3c8a6..d8acf0e 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -809,11 +809,11 @@
locationRequest.setProvider(provider);
// Ignore location settings if in emergency mode. This is only allowed for
- // isUserEmergency request (introduced in HAL v2.0), or DBH request in HAL v1.1.
+ // isUserEmergency request (introduced in HAL v2.0), or HAL v1.1.
if (mNIHandler.getInEmergency()) {
GnssConfiguration.HalInterfaceVersion halVersion =
mGnssConfiguration.getHalInterfaceVersion();
- if (isUserEmergency || (halVersion.mMajor < 2 && !independentFromGnss)) {
+ if (isUserEmergency || halVersion.mMajor < 2) {
locationRequest.setLocationSettingsIgnored(true);
durationMillis *= EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER;
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 52e9d7c..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,
@@ -808,7 +833,7 @@
return;
}
- // Can be null if the session is system's.
+ // Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterforSessionLocked(uniqueSessionId);
@@ -829,7 +854,7 @@
return;
}
- // Can be null if the session is system's.
+ // Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterforSessionLocked(uniqueSessionId);
@@ -850,7 +875,7 @@
return;
}
- // Can be null if the session is system's.
+ // Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterforSessionLocked(uniqueSessionId);
@@ -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);
@@ -1232,7 +1290,7 @@
route.getOriginalId(), sessionHints);
}
- // routerRecord can be null if the session is system's.
+ // routerRecord can be null if the session is system's or RCN.
private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
@@ -1250,7 +1308,7 @@
route.getOriginalId());
}
- // routerRecord can be null if the session is system's.
+ // routerRecord can be null if the session is system's or RCN.
private void deselectRouteOnHandler(long uniqueRequestId,
@Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
@@ -1270,7 +1328,7 @@
route.getOriginalId());
}
- // routerRecord can be null if the session is system's.
+ // routerRecord can be null if the session is system's or RCN.
private void transferToRouteOnHandler(long uniqueRequestId,
@Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
@@ -1289,6 +1347,8 @@
route.getOriginalId());
}
+ // routerRecord is null if and only if the session is created without the request, which
+ // includes the system's session and RCN cases.
private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route,
@NonNull String description) {
@@ -1305,12 +1365,6 @@
return true;
}
- //TODO(b/152950479): Handle RCN case.
- if (routerRecord == null) {
- Slog.w(TAG, "Ignoring " + description + " route from unknown router.");
- return false;
- }
-
RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
if (matchingRecord != routerRecord) {
Slog.w(TAG, "Ignoring " + description + " route from non-matching router. "
@@ -1448,7 +1502,9 @@
if (service == null) {
return;
}
- notifySessionInfoChangedToRouters(getRouters(), sessionInfo);
+ notifySessionInfoChangedToRouters(getRouters(true), sessionInfo);
+ notifySessionInfoChangedToRouters(getRouters(false),
+ mSystemProvider.getDefaultSessionInfo());
return;
}
@@ -1569,7 +1625,7 @@
}
}
- private List<IMediaRouter2> getRouters() {
+ private List<IMediaRouter2> getAllRouters() {
final List<IMediaRouter2> routers = new ArrayList<>();
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
@@ -1583,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/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
new file mode 100644
index 0000000..0bdf3f2
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.net.NetworkTemplate.getCollapsedRatType;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.telephony.Annotation;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class that watches for events that are triggered per subscription.
+ */
+// TODO (b/152176562): Write tests to verify subscription changes generate corresponding
+// register/unregister calls.
+public class NetworkStatsSubscriptionsMonitor extends
+ SubscriptionManager.OnSubscriptionsChangedListener {
+
+ /**
+ * Interface that this monitor uses to delegate event handling to NetworkStatsService.
+ */
+ public interface Delegate {
+ /**
+ * Notify that the collapsed RAT type has been changed for any subscription. The method
+ * will also be triggered for any existing sub when start and stop monitoring.
+ *
+ * @param subscriberId IMSI of the subscription.
+ * @param collapsedRatType collapsed RAT type.
+ * @see android.net.NetworkTemplate#getCollapsedRatType(int).
+ */
+ void onCollapsedRatTypeChanged(@NonNull String subscriberId,
+ @Annotation.NetworkType int collapsedRatType);
+ }
+ private final Delegate mDelegate;
+
+ /**
+ * Receivers that watches for {@link ServiceState} changes for each subscription, to
+ * monitor the transitioning between Radio Access Technology(RAT) types for each sub.
+ */
+ @NonNull
+ private final CopyOnWriteArrayList<RatTypeListener> mRatListeners =
+ new CopyOnWriteArrayList<>();
+
+ @NonNull
+ private final SubscriptionManager mSubscriptionManager;
+ @NonNull
+ private final TelephonyManager mTeleManager;
+
+ @NonNull
+ private final Executor mExecutor;
+
+ NetworkStatsSubscriptionsMonitor(@NonNull Context context, @NonNull Executor executor,
+ @NonNull Delegate delegate) {
+ super();
+ mSubscriptionManager = (SubscriptionManager) context.getSystemService(
+ Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ mTeleManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ mExecutor = executor;
+ mDelegate = delegate;
+ }
+
+ @Override
+ public void onSubscriptionsChanged() {
+ // Collect active subId list, hidden subId such as opportunistic subscriptions are
+ // also needed to track CBRS.
+ final List<Integer> newSubs = getActiveSubIdList(mSubscriptionManager);
+
+ for (final int subId : newSubs) {
+ final RatTypeListener match = CollectionUtils.find(mRatListeners,
+ it -> it.mSubId == subId);
+ if (match != null) continue;
+
+ // Create listener for every newly added sub. Also store subscriberId into it to
+ // prevent binder call to telephony when querying RAT.
+ final String subscriberId = mTeleManager.getSubscriberId(subId);
+ if (TextUtils.isEmpty(subscriberId)) {
+ Log.wtf(NetworkStatsService.TAG,
+ "Empty subscriberId for newly added sub: " + subId);
+ }
+ final RatTypeListener listener =
+ new RatTypeListener(mExecutor, this, subId, subscriberId);
+ mRatListeners.add(listener);
+
+ // Register listener to the telephony manager that associated with specific sub.
+ mTeleManager.createForSubscriptionId(subId)
+ .listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE);
+ }
+
+ for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
+ // If the new list contains the subId of the listener, keeps it.
+ final Integer match = CollectionUtils.find(newSubs, it -> it == listener.mSubId);
+ if (match != null) continue;
+
+ handleRemoveRatTypeListener(listener);
+ }
+ }
+
+ @NonNull
+ private List<Integer> getActiveSubIdList(@NonNull SubscriptionManager subscriptionManager) {
+ final ArrayList<Integer> ret = new ArrayList<>();
+ final int[] ids = subscriptionManager.getCompleteActiveSubscriptionIdList();
+ for (int id : ids) ret.add(id);
+ return ret;
+ }
+
+ /**
+ * Get a collapsed RatType for the given subscriberId.
+ *
+ * @param subscriberId the target subscriberId
+ * @return collapsed RatType for the given subscriberId
+ */
+ public int getRatTypeForSubscriberId(@NonNull String subscriberId) {
+ final RatTypeListener match = CollectionUtils.find(mRatListeners,
+ it -> TextUtils.equals(subscriberId, it.mSubscriberId));
+ return match != null ? match.mLastCollapsedRatType : TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ }
+
+ /**
+ * Start monitoring events that triggered per subscription.
+ */
+ public void start() {
+ mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, this);
+ }
+
+ /**
+ * Unregister subscription changes and all listeners for each subscription.
+ */
+ public void stop() {
+ mSubscriptionManager.removeOnSubscriptionsChangedListener(this);
+
+ for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
+ handleRemoveRatTypeListener(listener);
+ }
+ }
+
+ private void handleRemoveRatTypeListener(@NonNull RatTypeListener listener) {
+ mTeleManager.createForSubscriptionId(listener.mSubId)
+ .listen(listener, PhoneStateListener.LISTEN_NONE);
+ mRatListeners.remove(listener);
+
+ // Removal of subscriptions doesn't generate RAT changed event, fire it for every
+ // RatTypeListener.
+ mDelegate.onCollapsedRatTypeChanged(
+ listener.mSubscriberId, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ }
+
+ static class RatTypeListener extends PhoneStateListener {
+ // Unique id for the subscription. See {@link SubscriptionInfo#getSubscriptionId}.
+ @NonNull
+ private final int mSubId;
+
+ // IMSI to identifying the corresponding network from {@link NetworkState}.
+ // See {@link TelephonyManager#getSubscriberId}.
+ @NonNull
+ private final String mSubscriberId;
+
+ private volatile int mLastCollapsedRatType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ @NonNull
+ private final NetworkStatsSubscriptionsMonitor mMonitor;
+
+ RatTypeListener(@NonNull Executor executor,
+ @NonNull NetworkStatsSubscriptionsMonitor monitor, int subId,
+ @NonNull String subscriberId) {
+ super(executor);
+ mSubId = subId;
+ mSubscriberId = subscriberId;
+ mMonitor = monitor;
+ }
+
+ @Override
+ public void onServiceStateChanged(@NonNull ServiceState ss) {
+ final int networkType = ss.getDataNetworkType();
+ final int collapsedRatType = getCollapsedRatType(networkType);
+ if (collapsedRatType == mLastCollapsedRatType) return;
+
+ if (NetworkStatsService.LOGD) {
+ Log.d(NetworkStatsService.TAG, "subtype changed for sub(" + mSubId + "): "
+ + mLastCollapsedRatType + " -> " + collapsedRatType);
+ }
+ mLastCollapsedRatType = collapsedRatType;
+ mMonitor.mDelegate.onCollapsedRatTypeChanged(mSubscriberId, mLastCollapsedRatType);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index ba7583f..dab4bfd 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -46,6 +46,7 @@
import java.io.File;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -106,6 +107,8 @@
private static final long mDowngradeUnusedAppsThresholdInMillis =
getDowngradeUnusedAppsThresholdInMillis();
+ private static List<PackagesUpdatedListener> sPackagesUpdatedListeners = new ArrayList<>();
+
public static void schedule(Context context) {
if (isBackgroundDexoptDisabled()) {
return;
@@ -244,6 +247,7 @@
}
}
notifyPinService(updatedPackages);
+ notifyPackagesUpdated(updatedPackages);
// Ran to completion, so we abandon our timeslice and do not reschedule.
jobFinished(jobParams, /* reschedule */ false);
}
@@ -391,6 +395,7 @@
} finally {
// Always let the pinner service know about changes.
notifyPinService(updatedPackages);
+ notifyPackagesUpdated(updatedPackages);
}
}
@@ -642,6 +647,32 @@
}
}
+ public static interface PackagesUpdatedListener {
+ /** Callback when packages have been updated by the bg-dexopt service. */
+ public void onPackagesUpdated(ArraySet<String> updatedPackages);
+ }
+
+ public static void addPackagesUpdatedListener(PackagesUpdatedListener listener) {
+ synchronized (sPackagesUpdatedListeners) {
+ sPackagesUpdatedListeners.add(listener);
+ }
+ }
+
+ public static void removePackagesUpdatedListener(PackagesUpdatedListener listener) {
+ synchronized (sPackagesUpdatedListeners) {
+ sPackagesUpdatedListeners.remove(listener);
+ }
+ }
+
+ /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */
+ private void notifyPackagesUpdated(ArraySet<String> updatedPackages) {
+ synchronized (sPackagesUpdatedListeners) {
+ for (PackagesUpdatedListener listener : sPackagesUpdatedListeners) {
+ listener.onPackagesUpdated(updatedPackages);
+ }
+ }
+ }
+
private static long getDowngradeUnusedAppsThresholdInMillis() {
final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
String sysPropValue = SystemProperties.get(sysPropKey);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index f37af3a..9fb468e 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -724,6 +724,30 @@
}
/**
+ * Deletes all snapshots of credential encrypted user data, where the snapshot id is not
+ * included in {@code retainSnapshotIds}.
+ *
+ * @param userId id of the user whose user data snapshots to delete.
+ * @param retainSnapshotIds ids of the snapshots that should not be deleted.
+ *
+ * @return {@code true} if the operation was successful, or {@code false} if a remote call
+ * shouldn't be continued. See {@link #checkBeforeRemote}.
+ *
+ * @throws InstallerException if failed to delete user data snapshot.
+ */
+ public boolean destroyCeSnapshotsNotSpecified(@UserIdInt int userId,
+ int[] retainSnapshotIds) throws InstallerException {
+ if (!checkBeforeRemote()) return false;
+
+ try {
+ mInstalld.destroyCeSnapshotsNotSpecified(null, userId, retainSnapshotIds);
+ return true;
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ /**
* Migrates obb data from its legacy location {@code /data/media/obb} to
* {@code /data/media/0/Android/obb}. This call is idempotent and a fast no-op if data has
* already been migrated.
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index a726d39..e3faffa 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -882,7 +882,7 @@
public void grantDefaultPermissionsToDefaultBrowser(String packageName, int userId) {
Log.i(TAG, "Granting permissions to default browser for user:" + userId);
- grantPermissionsToSystemPackage(packageName, userId, ALWAYS_LOCATION_PERMISSIONS);
+ grantPermissionsToSystemPackage(packageName, userId, FOREGROUND_LOCATION_PERMISSIONS);
}
private String getDefaultSystemHandlerActivityPackage(String intentAction, int userId) {
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 161f304..27288d8 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -30,6 +30,7 @@
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -47,6 +48,7 @@
import android.os.UserHandle;
import android.os.UserManagerInternal;
import android.permission.PermissionControllerManager;
+import android.provider.Settings;
import android.provider.Telephony;
import android.telecom.TelecomManager;
import android.util.ArrayMap;
@@ -70,7 +72,9 @@
import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ExecutionException;
/**
@@ -180,8 +184,6 @@
intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
intentFilter.addDataScheme("package");
-
- /* TODO ntmyren: enable receiver when test flakes are fixed
getContext().registerReceiverAsUser(new BroadcastReceiver() {
final List<Integer> mUserSetupUids = new ArrayList<>(200);
final Map<UserHandle, PermissionControllerManager> mPermControllerManagers =
@@ -232,7 +234,6 @@
manager.updateUserSensitiveForApp(uid);
}
}, UserHandle.ALL, intentFilter, null, null);
- */
}
/**
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 5f871ad4..83e99b0 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -505,6 +505,11 @@
rollbackIds[i] = mRollbacks.get(i).info.getRollbackId();
}
ApexManager.getInstance().destroyCeSnapshotsNotSpecified(userId, rollbackIds);
+ try {
+ mInstaller.destroyCeSnapshotsNotSpecified(userId, rollbackIds);
+ } catch (Installer.InstallerException ie) {
+ Slog.e(TAG, "Failed to delete snapshots for user: " + userId, ie);
+ }
}
@WorkerThread
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/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index e5ff865..78d2afc 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -715,9 +715,13 @@
private void setWindowingModeInSurfaceTransaction(int preferredWindowingMode,
boolean creating) {
+ final TaskDisplayArea taskDisplayArea = getDisplayArea();
+ if (taskDisplayArea == null) {
+ Slog.d(TAG, "taskDisplayArea is null, bail early");
+ return;
+ }
final int currentMode = getWindowingMode();
final int currentOverrideMode = getRequestedOverrideWindowingMode();
- final TaskDisplayArea taskDisplayArea = getDisplayArea();
final Task topTask = getTopMostTask();
int windowingMode = preferredWindowingMode;
if (preferredWindowingMode == WINDOWING_MODE_UNDEFINED
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 1e5a924..0470ffa 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1469,7 +1469,7 @@
if (toDisplay.getDisplayId() != stack.getDisplayId()) {
stack.reparent(toDisplay.getDefaultTaskDisplayArea(), false /* onTop */);
} else {
- toDisplay.mTaskContainers.positionStackAtBottom(stack);
+ toDisplay.getDefaultTaskDisplayArea().positionStackAtBottom(stack);
}
mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f86aeb2..78ee1de7 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -76,7 +76,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
import android.annotation.DrawableRes;
@@ -1807,15 +1806,11 @@
}
int getAppStackClipMode() {
- // When dismiss keyguard animation occurs, clip before the animation to prevent docked
- // app from showing beyond the divider
- if (mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY
- || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
- return STACK_CLIP_BEFORE_ANIM;
- }
return mNextAppTransition == TRANSIT_ACTIVITY_RELAUNCH
|| mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS
|| mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL
+ || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY
+ || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
? STACK_CLIP_NONE
: STACK_CLIP_AFTER_ANIM;
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index edd14b7..a512337 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -54,7 +54,6 @@
* - BELOW_TASKS: Can only contain BELOW_TASK DisplayAreas and WindowTokens that go below tasks.
* - ABOVE_TASKS: Can only contain ABOVE_TASK DisplayAreas and WindowTokens that go above tasks.
* - ANY: Can contain any kind of DisplayArea, and any kind of WindowToken or the Task container.
- * Cannot have a sibling that is of type ANY.
*
* @param <T> type of the children of the DisplayArea.
*/
@@ -274,7 +273,6 @@
ANY;
static void checkSiblings(Type bottom, Type top) {
- checkState(!(bottom == ANY && top == ANY), "ANY cannot be a sibling of ANY");
checkState(!(bottom != BELOW_TASKS && top == BELOW_TASKS),
bottom + " must be above BELOW_TASKS");
checkState(!(bottom == ABOVE_TASKS && top != ABOVE_TASKS),
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 9821573..15b6f97 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -16,9 +16,14 @@
package com.android.server.wm;
+import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
+
import android.content.res.Resources;
import android.text.TextUtils;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Policy that manages DisplayAreas.
*/
@@ -37,9 +42,9 @@
protected final DisplayArea<? extends WindowContainer> mImeContainer;
/**
- * The Tasks container. Tasks etc. are automatically added to this container.
+ * The task display areas. Tasks etc. are automatically added to these containers.
*/
- protected final DisplayArea<? extends ActivityStack> mTaskContainers;
+ protected final List<TaskDisplayArea> mTaskDisplayAreas;
/**
* Construct a new {@link DisplayAreaPolicy}
@@ -48,19 +53,19 @@
* @param content the display content for which the policy applies
* @param root the root display area under which the policy operates
* @param imeContainer the ime container that the policy must attach
- * @param taskDisplayArea the task container that the policy must attach
+ * @param taskDisplayAreas the task display areas that the policy must attach
*
* @see #attachDisplayAreas()
*/
protected DisplayAreaPolicy(WindowManagerService wmService,
DisplayContent content, DisplayArea.Root root,
DisplayArea<? extends WindowContainer> imeContainer,
- DisplayArea<? extends ActivityStack> taskDisplayArea) {
+ List<TaskDisplayArea> taskDisplayAreas) {
mWmService = wmService;
mContent = content;
mRoot = root;
mImeContainer = imeContainer;
- mTaskContainers = taskDisplayArea;
+ mTaskDisplayAreas = taskDisplayAreas;
}
/**
@@ -80,15 +85,32 @@
*/
public abstract void addWindow(WindowToken token);
+ /**
+ * @return the number of task display areas on the display.
+ */
+ public int getTaskDisplayAreaCount() {
+ return mTaskDisplayAreas.size();
+ }
+
+ /**
+ * @return the task display area at index.
+ */
+ public TaskDisplayArea getTaskDisplayAreaAt(int index) {
+ return mTaskDisplayAreas.get(index);
+ }
+
/** Provider for platform-default display area policy. */
static final class DefaultProvider implements DisplayAreaPolicy.Provider {
@Override
public DisplayAreaPolicy instantiate(WindowManagerService wmService,
DisplayContent content, DisplayArea.Root root,
- DisplayArea<? extends WindowContainer> imeContainer,
- TaskDisplayArea taskDisplayArea) {
+ DisplayArea<? extends WindowContainer> imeContainer) {
+ final TaskDisplayArea defaultTaskDisplayArea = new TaskDisplayArea(content, wmService,
+ "DefaultTaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER);
+ final List<TaskDisplayArea> tdaList = new ArrayList<>();
+ tdaList.add(defaultTaskDisplayArea);
return new DisplayAreaPolicyBuilder()
- .build(wmService, content, root, imeContainer, taskDisplayArea);
+ .build(wmService, content, root, imeContainer, tdaList);
}
}
@@ -107,8 +129,7 @@
*/
DisplayAreaPolicy instantiate(WindowManagerService wmService,
DisplayContent content, DisplayArea.Root root,
- DisplayArea<? extends WindowContainer> imeContainer,
- TaskDisplayArea taskDisplayArea);
+ DisplayArea<? extends WindowContainer> imeContainer);
/**
* Instantiate the device-specific {@link Provider}.
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index e8becfa..2c2f433 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -201,8 +201,8 @@
Result(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root,
DisplayArea<? extends WindowContainer> imeContainer,
- DisplayArea<? extends ActivityStack> taskDisplayArea, ArrayList<Feature> features) {
- super(wmService, content, root, imeContainer, taskDisplayArea);
+ List<TaskDisplayArea> taskDisplayAreas, ArrayList<Feature> features) {
+ super(wmService, content, root, imeContainer, taskDisplayAreas);
mFeatures = features;
mAreas = new HashMap<>(features.size());
for (int i = 0; i < mFeatures.size(); i++) {
@@ -267,7 +267,7 @@
areaForLayer[layer].mChildren.add(leafArea);
leafType = type;
if (leafType == LEAF_TYPE_TASK_CONTAINERS) {
- leafArea.mExisting = mTaskContainers;
+ addTaskDisplayAreasToLayer(areaForLayer[layer], layer);
} else if (leafType == LEAF_TYPE_IME_CONTAINERS) {
leafArea.mExisting = mImeContainer;
}
@@ -278,6 +278,17 @@
root.instantiateChildren(mRoot, mAreaForLayer, 0, mAreas);
}
+ /** Adds all task display areas to the specified layer */
+ private void addTaskDisplayAreasToLayer(PendingArea parentPendingArea, int layer) {
+ final int count = mTaskDisplayAreas.size();
+ for (int i = 0; i < count; i++) {
+ PendingArea leafArea = new PendingArea(null, layer, parentPendingArea);
+ leafArea.mExisting = mTaskDisplayAreas.get(i);
+ leafArea.mMaxLayer = layer;
+ parentPendingArea.mChildren.add(leafArea);
+ }
+ }
+
@Override
public void addWindow(WindowToken token) {
DisplayArea.Tokens area = findAreaForToken(token);
@@ -317,12 +328,16 @@
return this;
}
+ protected List<Feature> getFeatures() {
+ return mFeatures;
+ }
+
Result build(WindowManagerService wmService,
DisplayContent content, DisplayArea.Root root,
DisplayArea<? extends WindowContainer> imeContainer,
- DisplayArea<? extends ActivityStack> taskDisplayArea) {
+ List<TaskDisplayArea> taskDisplayAreas) {
- return new Result(wmService, content, root, imeContainer, taskDisplayArea, new ArrayList<>(
+ return new Result(wmService, content, root, imeContainer, taskDisplayAreas, new ArrayList<>(
mFeatures));
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a93b962..40243e8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -72,6 +72,7 @@
import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
@@ -197,7 +198,6 @@
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
-import android.window.ITaskOrganizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
@@ -269,8 +269,6 @@
new NonAppWindowContainers("mOverlayContainers", mWmService);
/** The containers below are the only child containers {@link #mWindowContainers} can have. */
- // Contains all window containers that are related to apps (Activities)
- final TaskDisplayArea mTaskContainers = new TaskDisplayArea(this, mWmService);
// Contains all IME window containers. Note that the z-ordering of the IME windows will depend
// on the IME target. We mainly have this container grouping so we can keep track of all the IME
@@ -972,7 +970,7 @@
super.addChild(mOverlayContainers, null);
mDisplayAreaPolicy = mWmService.mDisplayAreaPolicyProvider.instantiate(
- mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers);
+ mWmService, this, mRootDisplayArea, mImeWindowsContainers);
mWindowContainers.addChildren();
// Sets the display content for the children.
@@ -1082,8 +1080,7 @@
return null;
}
mShellRoots.put(windowType, root);
- SurfaceControl out = new SurfaceControl();
- out.copyFrom(rootLeash);
+ SurfaceControl out = new SurfaceControl(rootLeash);
return out;
}
@@ -2071,13 +2068,11 @@
}
protected int getTaskDisplayAreaCount() {
- // TODO(multi-display-area): Report actual display area count
- return 1;
+ return mDisplayAreaPolicy.getTaskDisplayAreaCount();
}
protected TaskDisplayArea getTaskDisplayAreaAt(int index) {
- // TODO(multi-display-area): Report actual display area values
- return mTaskContainers;
+ return mDisplayAreaPolicy.getTaskDisplayAreaAt(index);
}
ActivityStack getStack(int rootTaskId) {
@@ -2403,7 +2398,7 @@
* or for cases when multi-instance is not supported yet (like Split-screen, PiP or Recents).
*/
TaskDisplayArea getDefaultTaskDisplayArea() {
- return mTaskContainers;
+ return mDisplayAreaPolicy.getTaskDisplayAreaAt(0);
}
@Override
@@ -3426,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;
}
@@ -3554,6 +3552,10 @@
onWallpaper, goingToShade, subtle));
}
}, true /* traverseTopToBottom */);
+ for (int i = mShellRoots.size() - 1; i >= 0; --i) {
+ mShellRoots.valueAt(i).startAnimation(policy.createHiddenByKeyguardExit(
+ onWallpaper, goingToShade, subtle));
+ }
}
/** @return {@code true} if there is window to wait before enabling the screen. */
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index 9732637..701feff 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -16,12 +16,20 @@
package com.android.server.wm;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
+import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
+
import android.annotation.NonNull;
+import android.graphics.Point;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import android.view.DisplayInfo;
import android.view.IWindow;
import android.view.SurfaceControl;
+import android.view.animation.Animation;
/**
* Represents a piece of the hierarchy under which a client Shell can manage sub-windows.
@@ -70,5 +78,29 @@
IWindow getClient() {
return mClient;
}
+
+ void startAnimation(Animation anim) {
+ // Only do this for the divider
+ if (mToken.windowType != TYPE_DOCK_DIVIDER) {
+ return;
+ }
+
+ DisplayInfo displayInfo = mToken.getFixedRotationTransformDisplayInfo();
+ if (displayInfo == null) {
+ displayInfo = mDisplayContent.getDisplayInfo();
+ }
+
+ // Mostly copied from WindowState to enable keyguard transition animation
+ anim.initialize(displayInfo.logicalWidth, displayInfo.logicalHeight,
+ displayInfo.appWidth, displayInfo.appHeight);
+ anim.restrictDuration(MAX_ANIMATION_DURATION);
+ anim.scaleCurrentDuration(mDisplayContent.mWmService.getWindowAnimationScaleLocked());
+ final AnimationAdapter adapter = new LocalAnimationAdapter(
+ new WindowAnimationSpec(anim, new Point(0, 0), false /* canSkipFirstFrame */,
+ 0 /* windowCornerRadius */),
+ mDisplayContent.mWmService.mSurfaceAnimationRunner);
+ mToken.startAnimation(mToken.getPendingTransaction(), adapter, false /* hidden */,
+ ANIMATION_TYPE_WINDOW_ANIMATION, null /* animationFinishedCallback */);
+ }
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9adacb8..d31939d 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4121,20 +4121,18 @@
* Any time any of these conditions are updated, the updating code should call
* sendTaskAppeared.
*/
- private boolean taskAppearedReady() {
+ boolean taskAppearedReady() {
return mSurfaceControl != null && mTaskOrganizer != null && getHasBeenVisible();
}
private void sendTaskAppeared() {
- if (taskAppearedReady() && !mTaskAppearedSent) {
- mTaskAppearedSent = true;
+ if (mTaskOrganizer != null) {
mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this);
}
}
private void sendTaskVanished() {
- if (mTaskOrganizer != null && mTaskAppearedSent) {
- mTaskAppearedSent = false;
+ if (mTaskOrganizer != null) {
mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 7fb6f6f..11cfad2 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -31,7 +31,6 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.window.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER;
import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
@@ -141,8 +140,9 @@
*/
private boolean mRemoved;
- TaskDisplayArea(DisplayContent displayContent, WindowManagerService service) {
- super(service, Type.ANY, "TaskContainers", FEATURE_TASK_CONTAINER);
+ TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name,
+ int displayAreaFeature) {
+ super(service, Type.ANY, name, displayAreaFeature);
mDisplayContent = displayContent;
mRootWindowContainer = service.mRoot;
mAtmService = service.mAtmService;
@@ -1612,6 +1612,11 @@
}
}
+ @Override
+ boolean canCreateRemoteAnimationTarget() {
+ return true;
+ }
+
/**
* Callback for when the order of the stacks in the display changes.
*/
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 2bbf8db..22702dd 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -106,19 +106,29 @@
}
void addTask(Task t) {
- mOrganizedTasks.add(t);
- try {
- mOrganizer.onTaskAppeared(t.getTaskInfo());
- } catch (Exception e) {
- Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+ if (t.mTaskAppearedSent) return;
+
+ if (!mOrganizedTasks.contains(t)) {
+ mOrganizedTasks.add(t);
+ }
+ if (t.taskAppearedReady()) {
+ try {
+ t.mTaskAppearedSent = true;
+ mOrganizer.onTaskAppeared(t.getTaskInfo());
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+ }
}
}
void removeTask(Task t) {
- try {
- mOrganizer.onTaskVanished(t.getTaskInfo());
- } catch (Exception e) {
- Slog.e(TAG, "Exception sending taskVanished callback" + e);
+ if (t.mTaskAppearedSent) {
+ try {
+ t.mTaskAppearedSent = false;
+ mOrganizer.onTaskVanished(t.getTaskInfo());
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception sending taskVanished callback" + e);
+ }
}
mOrganizedTasks.remove(t);
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index f6ed2a9..14e5c6c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -379,6 +379,7 @@
frame = null;
mTmpDstFrame.set(mFrame);
}
+ mTmpDstFrame.offsetTo(0, 0);
// Scale the mismatch dimensions to fill the task bounds
mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 569b8f6..f6e952c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2295,6 +2295,10 @@
public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
mLastLayer = -1;
reassignLayer(t);
+
+ // Leash is now responsible for position, so set our position to 0.
+ t.setPosition(mSurfaceControl, 0, 0);
+ mLastSurfacePosition.set(0, 0);
}
@Override
@@ -2302,6 +2306,7 @@
mLastLayer = -1;
mSurfaceFreezer.unfreeze(t);
reassignLayer(t);
+ updateSurfacePosition(t);
}
/**
@@ -2365,11 +2370,15 @@
}
}
- void updateSurfacePosition() {
+ final void updateSurfacePosition() {
+ updateSurfacePosition(getPendingTransaction());
+ }
+
+ void updateSurfacePosition(Transaction t) {
// Avoid fighting with the organizer over Surface position.
if (isOrganized()) return;
- if (mSurfaceControl == null) {
+ if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) {
return;
}
@@ -2378,7 +2387,7 @@
return;
}
- getPendingTransaction().setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
+ t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);
}
@@ -2501,9 +2510,7 @@
// We need to copy the SurfaceControl instead of returning the original
// because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls
// to release themselves.
- SurfaceControl sc = new SurfaceControl();
- sc.copyFrom(wc.getSurfaceControl());
- return sc;
+ return new SurfaceControl(wc.getSurfaceControl());
}
WindowContainerToken toWindowContainerToken() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8d3a4f2..513187d 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 {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d611ad8..5a76bac 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5237,23 +5237,14 @@
@Override
public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
super.onAnimationLeashCreated(t, leash);
-
- // Leash is now responsible for position, so set our position to 0.
- t.setPosition(mSurfaceControl, 0, 0);
- mLastSurfacePosition.set(0, 0);
}
@Override
public void onAnimationLeashLost(Transaction t) {
super.onAnimationLeashLost(t);
- updateSurfacePosition(t);
}
@Override
- void updateSurfacePosition() {
- updateSurfacePosition(getPendingTransaction());
- }
-
@VisibleForTesting
void updateSurfacePosition(Transaction t) {
if (mSurfaceControl == null) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 7457a1d..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) {
@@ -535,8 +595,8 @@
}
@Override
- void updateSurfacePosition() {
- super.updateSurfacePosition();
+ void updateSurfacePosition(SurfaceControl.Transaction t) {
+ super.updateSurfacePosition(t);
if (isFixedRotationTransforming()) {
// The window is layouted in a simulated rotated display but the real display hasn't
// rotated, so here transforms its surface to fit in the real display.
@@ -616,4 +676,8 @@
int getWindowLayerFromType() {
return mWmService.mPolicy.getWindowLayerFromTypeLw(windowType, mOwnerCanManageAppTokens);
}
+
+ int getOwnerUid() {
+ return mOwnerUid;
+ }
}
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 97de1800..2dbbc5a 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -155,11 +155,6 @@
return ok();
}
-binder::Status BinderIncrementalService::setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) {
- *_aidl_return = mImpl.setStorageParams(storage, enableReadLogs);
- return ok();
-}
-
binder::Status BinderIncrementalService::makeDirectory(int32_t storageId, const std::string& path,
int32_t* _aidl_return) {
*_aidl_return = mImpl.makeDir(storageId, path);
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index d0357d9..28613e1 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -71,7 +71,6 @@
binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath,
const std::string& libDirRelativePath,
const std::string& abi, bool* _aidl_return) final;
- binder::Status setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) final;
private:
android::incremental::IncrementalService mImpl;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index de24bcf..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 {
@@ -581,6 +581,7 @@
int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) {
const auto ifs = getIfs(storageId);
if (!ifs) {
+ LOG(ERROR) << "setStorageParams failed, invalid storageId: " << storageId;
return -EINVAL;
}
@@ -1153,6 +1154,7 @@
fsControlParcel.incremental->pendingReads.reset(
base::unique_fd(::dup(ifs.control.pendingReads())));
fsControlParcel.incremental->log.reset(base::unique_fd(::dup(ifs.control.logs())));
+ fsControlParcel.service = new IncrementalServiceConnector(*this, ifs.mountId);
sp<IncrementalDataLoaderListener> listener =
new IncrementalDataLoaderListener(*this,
externalListener ? *externalListener
@@ -1451,4 +1453,10 @@
incrementalService.onAppOpChanged(packageName);
}
+binder::Status IncrementalService::IncrementalServiceConnector::setStorageParams(
+ bool enableReadLogs, int32_t* _aidl_return) {
+ *_aidl_return = incrementalService.setStorageParams(storage, enableReadLogs);
+ return binder::Status::ok();
+}
+
} // namespace android::incremental
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 9b15646..db14a79 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -39,6 +39,7 @@
#include "ServiceWrappers.h"
#include "android/content/pm/BnDataLoaderStatusListener.h"
+#include "android/os/incremental/BnIncrementalServiceConnector.h"
#include "incfs.h"
#include "path.h"
@@ -139,7 +140,7 @@
DataLoaderStatusListener externalListener)
: incrementalService(incrementalService), externalListener(externalListener) {}
// Callbacks interface
- binder::Status onStatusChanged(MountId mount, int newStatus) override;
+ binder::Status onStatusChanged(MountId mount, int newStatus) final;
private:
IncrementalService& incrementalService;
@@ -149,13 +150,24 @@
class AppOpsListener : public android::BnAppOpsCallback {
public:
AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {}
- void opChanged(int32_t op, const String16& packageName) override;
+ void opChanged(int32_t op, const String16& packageName) final;
private:
IncrementalService& incrementalService;
const std::string packageName;
};
+ class IncrementalServiceConnector : public BnIncrementalServiceConnector {
+ public:
+ IncrementalServiceConnector(IncrementalService& incrementalService, int32_t storage)
+ : incrementalService(incrementalService), storage(storage) {}
+ binder::Status setStorageParams(bool enableReadLogs, int32_t* _aidl_return) final;
+
+ private:
+ IncrementalService& incrementalService;
+ int32_t const storage;
+ };
+
private:
static const bool sEnablePerfLogging;
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/src/com/android/server/display/color/AppSaturationControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
index 7c9a81d..a525814 100644
--- a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
@@ -43,7 +43,9 @@
@RunWith(AndroidJUnit4.class)
public class AppSaturationControllerTest {
- private static final String TEST_PACKAGE_NAME = "com.android.test";
+ private static final String TEST_CALLER_PACKAGE_NAME = "com.android.test.caller";
+ private static final String TEST_CALLER_PACKAGE_NAME_TWO = "com.android.test.caller.two";
+ private static final String TEST_AFFECTED_PACKAGE_NAME = "com.android.test.affected";
private int mUserId;
private AppSaturationController mAppSaturationController;
@@ -70,8 +72,11 @@
public void addColorTransformController_appliesExistingSaturation() {
final WeakReference<ColorTransformController> ref = new WeakReference<>(
mColorTransformController);
- mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30);
- mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref);
+ mAppSaturationController
+ .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+ 30);
+ mAppSaturationController
+ .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix);
verify(mColorTransformController).applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
}
@@ -80,14 +85,19 @@
public void setSaturationLevel_resetToDefault() {
final WeakReference<ColorTransformController> ref = new WeakReference<>(
mColorTransformController);
- mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref);
+ mAppSaturationController
+ .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
verify(mColorTransformController, never())
.applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
- mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30);
+ mAppSaturationController
+ .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+ 30);
AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix);
verify(mColorTransformController, times(1))
.applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
- mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 100);
+ mAppSaturationController
+ .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+ 100);
AppSaturationController.computeGrayscaleTransformMatrix(1.0f, mMatrix);
verify(mColorTransformController, times(2))
.applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
@@ -97,19 +107,76 @@
public void setSaturationLevel_updateLevel() {
final WeakReference<ColorTransformController> ref = new WeakReference<>(
mColorTransformController);
- mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref);
+ mAppSaturationController
+ .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
verify(mColorTransformController, never())
.applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
- mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30);
+ mAppSaturationController
+ .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+ 30);
AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix);
verify(mColorTransformController).applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
- mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 70);
+ mAppSaturationController
+ .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+ 70);
AppSaturationController.computeGrayscaleTransformMatrix(.7f, mMatrix);
verify(mColorTransformController, times(2))
.applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
- mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 100);
+ mAppSaturationController
+ .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+ 100);
AppSaturationController.computeGrayscaleTransformMatrix(1.0f, mMatrix);
verify(mColorTransformController, times(3))
.applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
}
+
+ @Test
+ public void setSaturationLevel_multipleCallers_appliesStrongest() {
+ final WeakReference<ColorTransformController> ref = new WeakReference<>(
+ mColorTransformController);
+ mAppSaturationController
+ .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
+ verify(mColorTransformController, never())
+ .applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
+ mAppSaturationController
+ .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+ 30);
+ AppSaturationController.computeGrayscaleTransformMatrix(0.3f, mMatrix);
+ verify(mColorTransformController, times(1))
+ .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+ mAppSaturationController
+ .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME,
+ mUserId,
+ 70);
+ verify(mColorTransformController, times(2))
+ .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+ }
+
+ @Test
+ public void setSaturationLevel_multipleCallers_removingOneDoesNotAffectTheOther() {
+ final WeakReference<ColorTransformController> ref = new WeakReference<>(
+ mColorTransformController);
+ mAppSaturationController
+ .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
+ verify(mColorTransformController, never())
+ .applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
+ mAppSaturationController
+ .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+ 70);
+ AppSaturationController.computeGrayscaleTransformMatrix(0.7f, mMatrix);
+ verify(mColorTransformController, times(1))
+ .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+ mAppSaturationController
+ .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME,
+ mUserId,
+ 30);
+ AppSaturationController.computeGrayscaleTransformMatrix(0.3f, mMatrix);
+ verify(mColorTransformController, times(2))
+ .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+ mAppSaturationController
+ .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME,
+ mUserId,
+ 100);
+ AppSaturationController.computeGrayscaleTransformMatrix(0.7f, mMatrix);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
index 12bdec6..0568be8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -22,6 +22,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
+import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
import static com.android.server.wm.DisplayArea.Type.ANY;
@@ -72,7 +73,11 @@
WindowManagerService wms = mSystemServices.getWindowManagerService();
DisplayArea.Root root = new SurfacelessDisplayAreaRoot(wms);
DisplayArea<WindowContainer> ime = new DisplayArea<>(wms, ABOVE_TASKS, "Ime");
- DisplayArea<ActivityStack> tasks = new DisplayArea<>(wms, ANY, "Tasks");
+ DisplayContent displayContent = mock(DisplayContent.class);
+ TaskDisplayArea taskDisplayArea = new TaskDisplayArea(displayContent, wms, "Tasks",
+ FEATURE_DEFAULT_TASK_CONTAINER);
+ List<TaskDisplayArea> taskDisplayAreaList = new ArrayList<>();
+ taskDisplayAreaList.add(taskDisplayArea);
final Feature foo;
final Feature bar;
@@ -86,7 +91,7 @@
.all()
.except(TYPE_STATUS_BAR)
.build())
- .build(wms, mock(DisplayContent.class), root, ime, tasks);
+ .build(wms, displayContent, root, ime, taskDisplayAreaList);
policy.attachDisplayAreas();
@@ -98,9 +103,9 @@
assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)),
is(not(decendantOfOneOf(policy.getDisplayAreas(bar)))));
- assertThat(tasks,
+ assertThat(taskDisplayArea,
is(decendantOfOneOf(policy.getDisplayAreas(foo))));
- assertThat(tasks,
+ assertThat(taskDisplayArea,
is(decendantOfOneOf(policy.getDisplayAreas(bar))));
assertThat(ime,
@@ -109,7 +114,8 @@
is(decendantOfOneOf(policy.getDisplayAreas(bar))));
List<DisplayArea<?>> actualOrder = collectLeafAreas(root);
- Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime, tasks);
+ Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime,
+ taskDisplayArea);
actualOrder = actualOrder.stream().filter(zSets::containsKey).collect(toList());
Map<DisplayArea<?>, Integer> expectedByMinLayer = mapValues(zSets,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
index 4e4627b..6834ee5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
@@ -77,8 +77,7 @@
@Override
public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content,
- DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer,
- TaskDisplayArea taskDisplayArea) {
+ DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer) {
throw new RuntimeException("test stub");
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 618e608..880c486 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -54,17 +54,6 @@
}
@Test
- public void testDisplayArea_positionChanged_throwsIfIncompatibleSibling() {
- WindowManagerService wms = mWmsRule.getWindowManagerService();
- DisplayArea<WindowContainer> parent = new SurfacelessDisplayArea<>(wms, ANY, "Parent");
- DisplayArea<WindowContainer> child1 = new DisplayArea<>(wms, ANY, "Child1");
- DisplayArea<WindowContainer> child2 = new DisplayArea<>(wms, ANY, "Child2");
-
- parent.addChild(child1, 0);
- assertThrows(IllegalStateException.class, () -> parent.addChild(child2, 0));
- }
-
- @Test
public void testType_typeOf() {
WindowManagerService wms = mWmsRule.getWindowManagerService();
@@ -87,10 +76,10 @@
checkSiblings(BELOW_TASKS, ABOVE_TASKS);
checkSiblings(ANY, ABOVE_TASKS);
checkSiblings(ABOVE_TASKS, ABOVE_TASKS);
+ checkSiblings(ANY, ANY);
assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, BELOW_TASKS));
assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, ANY));
- assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, ANY));
assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, BELOW_TASKS));
}
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/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 9625ffd..a708533 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -51,10 +51,10 @@
import org.junit.runner.RunWith;
/**
- * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}.
+ * Tests for the {@link TaskDisplayArea} container.
*
* Build/Install/Run:
- * atest WmTests:TaskStackContainersTests
+ * atest WmTests:TaskDisplayAreaTests
*/
@SmallTest
@Presubmit
@@ -154,8 +154,9 @@
ACTIVITY_TYPE_STANDARD, mDisplayContent);
final Task newStack = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, mDisplayContent);
- doReturn(newStack).when(mDisplayContent.mTaskContainers).createStack(anyInt(),
- anyInt(), anyBoolean(), any(), any(), anyBoolean());
+ final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea();
+ doReturn(newStack).when(taskDisplayArea).createStack(anyInt(), anyInt(), anyBoolean(),
+ any(), any(), anyBoolean());
final int type = ACTIVITY_TYPE_STANDARD;
assertGetOrCreateStack(WINDOWING_MODE_FULLSCREEN, type, candidateTask,
@@ -186,7 +187,7 @@
private void assertGetOrCreateStack(int windowingMode, int activityType, Task candidateTask,
boolean reuseCandidate) {
- final TaskDisplayArea taskDisplayArea = (TaskDisplayArea) candidateTask.getParent();
+ final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea();
final ActivityStack stack = taskDisplayArea.getOrCreateStack(windowingMode, activityType,
false /* onTop */, null /* intent */, candidateTask /* candidateTask */);
assertEquals(reuseCandidate, stack == candidateTask);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index 2ce9c2b..06ca6c1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -192,6 +192,21 @@
}
@Test
+ public void testTaskNoDraw() throws RemoteException {
+ final ActivityStack stack = createStack();
+ final Task task = createTask(stack, false /* fakeDraw */);
+ final ITaskOrganizer organizer = registerMockOrganizer();
+
+ stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ verify(organizer, never()).onTaskAppeared(any());
+ assertTrue(stack.isOrganized());
+
+ mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
+ verify(organizer, never()).onTaskVanished(any());
+ assertFalse(stack.isOrganized());
+ }
+
+ @Test
public void testClearOrganizer() throws RemoteException {
final ActivityStack stack = createStack();
final Task task = createTask(stack);
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 18737c2..d2a2732 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -43,7 +43,9 @@
// hard-code to FULLSCREEN for tests.
setWindowingMode(WINDOWING_MODE_FULLSCREEN);
spyOn(this);
- spyOn(mTaskContainers);
+ for (int i = getTaskDisplayAreaCount() - 1; i >= 0; --i) {
+ spyOn(getTaskDisplayAreaAt(i));
+ }
final DisplayRotation displayRotation = getDisplayRotation();
spyOn(displayRotation);
@@ -137,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/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index d5851d8..0c25cfb 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -34,18 +34,21 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.pm.BackgroundDexOptService;
import com.android.server.wm.ActivityMetricsLaunchObserver;
import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
import com.android.server.wm.ActivityTaskManagerInternal;
+import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.HashMap;
@@ -286,6 +289,7 @@
private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
private final EventSequenceValidator mEventSequenceValidator = new EventSequenceValidator();
+ private final DexOptPackagesUpdated mDexOptPackagesUpdated = new DexOptPackagesUpdated();
private boolean mRegisteredListeners = false;
private void registerInProcessListenersLocked() {
@@ -308,9 +312,22 @@
launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator);
+ BackgroundDexOptService.addPackagesUpdatedListener(mDexOptPackagesUpdated);
+
+
mRegisteredListeners = true;
}
+ private class DexOptPackagesUpdated implements BackgroundDexOptService.PackagesUpdatedListener {
+ @Override
+ public void onPackagesUpdated(ArraySet<String> updatedPackages) {
+ String[] updated = updatedPackages.toArray(new String[0]);
+ for (String packageName : updated) {
+ Log.d(TAG, "onPackagesUpdated: " + packageName);
+ }
+ }
+ }
+
private class AppLaunchObserver implements ActivityMetricsLaunchObserver {
// We add a synthetic sequence ID here to make it easier to differentiate new
// launch sequences on the native side.
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index f3e9de0..3048ad7 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -311,7 +311,7 @@
}
// If the user or profile is current, permission is granted.
// Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
- return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid);
+ return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, pid, uid);
}
private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 327e8b3..56f3c3e 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1147,6 +1147,21 @@
"support_ims_conference_event_package_bool";
/**
+ * Determines whether processing of conference event package data received on a device other
+ * than the conference host is supported.
+ * <p>
+ * When a device A merges calls B and C into a conference it is considered the conference host
+ * and B and C are considered the conference peers.
+ * <p>
+ * When {@code true}, the conference peer will display the conference state if it receives
+ * conference event package data from the network. When {@code false}, the conference peer will
+ * ignore conference event package data received from the network.
+ * @hide
+ */
+ public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL =
+ "support_ims_conference_event_package_on_peer_bool";
+
+ /**
* Determines whether High Definition audio property is displayed in the dialer UI.
* If {@code false}, remove the HD audio property from the connection so that HD audio related
* UI is not displayed. If {@code true}, keep HD audio property as it is configured.
@@ -1163,6 +1178,25 @@
"support_ims_conference_call_bool";
/**
+ * Determines whether the device will locally disconnect an IMS conference when the participant
+ * count drops to zero. When {@code true}, it is assumed the carrier does NOT disconnect a
+ * conference when the participant count drops to zero and that the device must do this by
+ * disconnecting the conference locally. When {@code false}, it is assumed that the carrier
+ * is responsible for disconnecting the conference when there are no longer any participants
+ * present.
+ * <p>
+ * Note: both {@link #KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL} and
+ * {@link #KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL} must be true for this configuration to
+ * have any effect.
+ * <p>
+ * Defaults to {@code false}, meaning the carrier network is responsible for disconnecting an
+ * empty IMS conference.
+ * @hide
+ */
+ public static final String KEY_LOCAL_DISCONNECT_EMPTY_IMS_CONFERENCE_BOOL =
+ "local_disconnect_empty_ims_conference_bool";
+
+ /**
* Determines whether video conference calls are supported by a carrier. When {@code true},
* video calls can be merged into conference calls, {@code false} otherwiwse.
* <p>
@@ -3778,8 +3812,10 @@
sDefaults.putBoolean(KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
+ sDefaults.putBoolean(KEY_LOCAL_DISCONNECT_EMPTY_IMS_CONFERENCE_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL, true);
+ sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false);
sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5);
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 2b1d9e5..18e2592 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -113,6 +113,7 @@
public static final int EVENT_5G_TIMER_HYSTERESIS = BASE + 53;
public static final int EVENT_5G_TIMER_WATCHDOG = BASE + 54;
public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 55;
+ public static final int EVENT_SIM_STATE_UPDATED = BASE + 56;
/***** Constants *****/
diff --git a/tests/net/common/java/android/net/DependenciesTest.java b/tests/net/common/java/android/net/DependenciesTest.java
new file mode 100644
index 0000000..ac1c28a
--- /dev/null
+++ b/tests/net/common/java/android/net/DependenciesTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.net;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A simple class that tests dependencies to java standard tools from the
+ * Network stack. These tests are not meant to be comprehensive tests of
+ * the relevant APIs : such tests belong in the relevant test suite for
+ * these dependencies. Instead, this just makes sure coverage is present
+ * by calling the methods in the exact way (or a representative way of how)
+ * they are called in the network stack.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DependenciesTest {
+ // Used to in ipmemorystore's RegularMaintenanceJobService to convert
+ // 24 hours into seconds
+ @Test
+ public void testTimeUnit() {
+ final int hours = 24;
+ final long inSeconds = TimeUnit.HOURS.toMillis(hours);
+ assertEquals(inSeconds, hours * 60 * 60 * 1000);
+ }
+
+ private byte[] makeTrivialArray(final int size) {
+ final byte[] src = new byte[size];
+ for (int i = 0; i < size; ++i) {
+ src[i] = (byte) i;
+ }
+ return src;
+ }
+
+ // Used in ApfFilter to find an IP address from a byte array
+ @Test
+ public void testArrays() {
+ final int size = 128;
+ final byte[] src = makeTrivialArray(size);
+
+ // Test copy
+ final int copySize = 16;
+ final int offset = 24;
+ final byte[] expected = new byte[copySize];
+ for (int i = 0; i < copySize; ++i) {
+ expected[i] = (byte) (offset + i);
+ }
+
+ final byte[] copy = Arrays.copyOfRange(src, offset, offset + copySize);
+ assertArrayEquals(expected, copy);
+ assertArrayEquals(new byte[0], Arrays.copyOfRange(src, size, size));
+ }
+
+ // Used mainly in the Dhcp code
+ @Test
+ public void testCopyOf() {
+ final byte[] src = makeTrivialArray(128);
+ final byte[] copy = Arrays.copyOf(src, src.length);
+ assertArrayEquals(src, copy);
+ assertFalse(src == copy);
+
+ assertArrayEquals(new byte[0], Arrays.copyOf(src, 0));
+
+ final int excess = 16;
+ final byte[] biggerCopy = Arrays.copyOf(src, src.length + excess);
+ for (int i = src.length; i < src.length + excess; ++i) {
+ assertEquals(0, biggerCopy[i]);
+ }
+ for (int i = src.length - 1; i >= 0; --i) {
+ assertEquals(src[i], biggerCopy[i]);
+ }
+ }
+
+ // Used mainly in DnsUtils but also various other places
+ @Test
+ public void testAsList() {
+ final int size = 24;
+ final Object[] src = new Object[size];
+ final ArrayList<Object> expected = new ArrayList<>(size);
+ for (int i = 0; i < size; ++i) {
+ final Object o = new Object();
+ src[i] = o;
+ expected.add(o);
+ }
+ assertEquals(expected, Arrays.asList(src));
+ }
+}
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 6e9dc8e..3f8261d 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -17,6 +17,8 @@
package android.net;
import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+import static android.net.NetworkCapabilities.MAX_TRANSPORT;
+import static android.net.NetworkCapabilities.MIN_TRANSPORT;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
@@ -32,10 +34,12 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
+import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
@@ -45,10 +49,15 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.net.wifi.aware.DiscoverySession;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.WifiAwareNetworkSpecifier;
import android.os.Build;
+import android.os.Process;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
@@ -61,6 +70,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import java.util.Arrays;
import java.util.Set;
@@ -74,6 +84,9 @@
@Rule
public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
+ private DiscoverySession mDiscoverySession = Mockito.mock(DiscoverySession.class);
+ private PeerHandle mPeerHandle = Mockito.mock(PeerHandle.class);
+
private boolean isAtLeastR() {
// BuildCompat.isAtLeastR() is used to check the Android version before releasing Android R.
// Build.VERSION.SDK_INT > Build.VERSION_CODES.Q is used to check the Android version after
@@ -685,4 +698,238 @@
assertEquals(TRANSPORT_VPN, transportTypes[2]);
assertEquals(TRANSPORT_TEST, transportTypes[3]);
}
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testTelephonyNetworkSpecifier() {
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+ final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .setNetworkSpecifier(specifier)
+ .build();
+ assertEquals(specifier, nc1.getNetworkSpecifier());
+ try {
+ final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+ .setNetworkSpecifier(specifier)
+ .build();
+ fail("Must have a single transport type. Without transport type or multiple transport"
+ + " types is invalid.");
+ } catch (IllegalStateException expected) { }
+ }
+
+ @Test
+ public void testWifiAwareNetworkSpecifier() {
+ final NetworkCapabilities nc = new NetworkCapabilities()
+ .addTransportType(TRANSPORT_WIFI_AWARE);
+ // If NetworkSpecifier is not set, the default value is null.
+ assertNull(nc.getNetworkSpecifier());
+ final WifiAwareNetworkSpecifier specifier = new WifiAwareNetworkSpecifier.Builder(
+ mDiscoverySession, mPeerHandle).build();
+ nc.setNetworkSpecifier(specifier);
+ assertEquals(specifier, nc.getNetworkSpecifier());
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testAdministratorUidsAndOwnerUid() {
+ // Test default owner uid.
+ // If the owner uid is not set, the default value should be Process.INVALID_UID.
+ final NetworkCapabilities nc1 = new NetworkCapabilities.Builder().build();
+ assertEquals(Process.INVALID_UID, nc1.getOwnerUid());
+ // Test setAdministratorUids and getAdministratorUids.
+ final int[] administratorUids = {1001, 10001};
+ final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+ .setAdministratorUids(administratorUids)
+ .build();
+ assertTrue(Arrays.equals(administratorUids, nc2.getAdministratorUids()));
+ // Test setOwnerUid and getOwnerUid.
+ // The owner UID must be included in administrator UIDs, or throw IllegalStateException.
+ try {
+ final NetworkCapabilities nc3 = new NetworkCapabilities.Builder()
+ .setOwnerUid(1001)
+ .build();
+ fail("The owner UID must be included in administrator UIDs.");
+ } catch (IllegalStateException expected) { }
+ final NetworkCapabilities nc4 = new NetworkCapabilities.Builder()
+ .setAdministratorUids(administratorUids)
+ .setOwnerUid(1001)
+ .build();
+ assertEquals(1001, nc4.getOwnerUid());
+ try {
+ final NetworkCapabilities nc5 = new NetworkCapabilities.Builder()
+ .setAdministratorUids(null)
+ .build();
+ fail("Should not set null into setAdministratorUids");
+ } catch (NullPointerException expected) { }
+ }
+
+ @Test
+ public void testLinkBandwidthKbps() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ // The default value of LinkDown/UpstreamBandwidthKbps should be LINK_BANDWIDTH_UNSPECIFIED.
+ assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkDownstreamBandwidthKbps());
+ assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkUpstreamBandwidthKbps());
+ nc.setLinkDownstreamBandwidthKbps(512);
+ nc.setLinkUpstreamBandwidthKbps(128);
+ assertEquals(512, nc.getLinkDownstreamBandwidthKbps());
+ assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps());
+ assertEquals(128, nc.getLinkUpstreamBandwidthKbps());
+ assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps());
+ }
+
+ @Test
+ public void testSignalStrength() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ // The default value of signal strength should be SIGNAL_STRENGTH_UNSPECIFIED.
+ assertEquals(SIGNAL_STRENGTH_UNSPECIFIED, nc.getSignalStrength());
+ nc.setSignalStrength(-80);
+ assertEquals(-80, nc.getSignalStrength());
+ assertNotEquals(-50, nc.getSignalStrength());
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testDeduceRestrictedCapability() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ // Default capabilities don't have restricted capability.
+ assertFalse(nc.deduceRestrictedCapability());
+ // If there is a force restricted capability, then the network capabilities is restricted.
+ nc.addCapability(NET_CAPABILITY_OEM_PAID);
+ nc.addCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc.deduceRestrictedCapability());
+ // Except for the force restricted capability, if there is any unrestricted capability in
+ // capabilities, then the network capabilities is not restricted.
+ nc.removeCapability(NET_CAPABILITY_OEM_PAID);
+ nc.addCapability(NET_CAPABILITY_CBS);
+ assertFalse(nc.deduceRestrictedCapability());
+ // Except for the force restricted capability, the network capabilities will only be treated
+ // as restricted when there is no any unrestricted capability.
+ nc.removeCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc.deduceRestrictedCapability());
+ }
+
+ private void assertNoTransport(NetworkCapabilities nc) {
+ for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+ assertFalse(nc.hasTransport(i));
+ }
+ }
+
+ // Checks that all transport types from MIN_TRANSPORT to maxTransportType are set and all
+ // transport types from maxTransportType + 1 to MAX_TRANSPORT are not set when positiveSequence
+ // is true. If positiveSequence is false, then the check sequence is opposite.
+ private void checkCurrentTransportTypes(NetworkCapabilities nc, int maxTransportType,
+ boolean positiveSequence) {
+ for (int i = MIN_TRANSPORT; i <= maxTransportType; i++) {
+ if (positiveSequence) {
+ assertTrue(nc.hasTransport(i));
+ } else {
+ assertFalse(nc.hasTransport(i));
+ }
+ }
+ for (int i = MAX_TRANSPORT; i > maxTransportType; i--) {
+ if (positiveSequence) {
+ assertFalse(nc.hasTransport(i));
+ } else {
+ assertTrue(nc.hasTransport(i));
+ }
+ }
+ }
+
+ @Test
+ public void testMultipleTransportTypes() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ assertNoTransport(nc);
+ // Test adding multiple transport types.
+ for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+ nc.addTransportType(i);
+ checkCurrentTransportTypes(nc, i, true /* positiveSequence */);
+ }
+ // Test removing multiple transport types.
+ for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+ nc.removeTransportType(i);
+ checkCurrentTransportTypes(nc, i, false /* positiveSequence */);
+ }
+ assertNoTransport(nc);
+ nc.addTransportType(TRANSPORT_WIFI);
+ assertTrue(nc.hasTransport(TRANSPORT_WIFI));
+ assertFalse(nc.hasTransport(TRANSPORT_VPN));
+ nc.addTransportType(TRANSPORT_VPN);
+ assertTrue(nc.hasTransport(TRANSPORT_WIFI));
+ assertTrue(nc.hasTransport(TRANSPORT_VPN));
+ nc.removeTransportType(TRANSPORT_WIFI);
+ assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+ assertTrue(nc.hasTransport(TRANSPORT_VPN));
+ nc.removeTransportType(TRANSPORT_VPN);
+ assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+ assertFalse(nc.hasTransport(TRANSPORT_VPN));
+ assertNoTransport(nc);
+ }
+
+ @Test
+ public void testAddAndRemoveTransportType() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ try {
+ nc.addTransportType(-1);
+ fail("Should not set invalid transport type into addTransportType");
+ } catch (IllegalArgumentException expected) { }
+ try {
+ nc.removeTransportType(-1);
+ fail("Should not set invalid transport type into removeTransportType");
+ } catch (IllegalArgumentException e) { }
+ }
+
+ private class TestTransportInfo implements TransportInfo {
+ TestTransportInfo() {
+ }
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testBuilder() {
+ final int ownerUid = 1001;
+ final int signalStrength = -80;
+ final int requestUid = 10100;
+ final int[] administratorUids = {ownerUid, 10001};
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+ final TestTransportInfo transportInfo = new TestTransportInfo();
+ final String ssid = "TEST_SSID";
+ final String packageName = "com.google.test.networkcapabilities";
+ final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .removeTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_EIMS)
+ .addCapability(NET_CAPABILITY_CBS)
+ .removeCapability(NET_CAPABILITY_CBS)
+ .setAdministratorUids(administratorUids)
+ .setOwnerUid(ownerUid)
+ .setLinkDownstreamBandwidthKbps(512)
+ .setLinkUpstreamBandwidthKbps(128)
+ .setNetworkSpecifier(specifier)
+ .setTransportInfo(transportInfo)
+ .setSignalStrength(signalStrength)
+ .setSsid(ssid)
+ .setRequestorUid(requestUid)
+ .setRequestorPackageName(packageName)
+ .build();
+ assertEquals(1, nc.getTransportTypes().length);
+ assertEquals(TRANSPORT_WIFI, nc.getTransportTypes()[0]);
+ assertTrue(nc.hasCapability(NET_CAPABILITY_EIMS));
+ assertFalse(nc.hasCapability(NET_CAPABILITY_CBS));
+ assertTrue(Arrays.equals(administratorUids, nc.getAdministratorUids()));
+ assertEquals(ownerUid, nc.getOwnerUid());
+ assertEquals(512, nc.getLinkDownstreamBandwidthKbps());
+ assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps());
+ assertEquals(128, nc.getLinkUpstreamBandwidthKbps());
+ assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps());
+ assertEquals(specifier, nc.getNetworkSpecifier());
+ assertEquals(transportInfo, nc.getTransportInfo());
+ assertEquals(signalStrength, nc.getSignalStrength());
+ assertNotEquals(-50, nc.getSignalStrength());
+ assertEquals(ssid, nc.getSsid());
+ assertEquals(requestUid, nc.getRequestorUid());
+ assertEquals(packageName, nc.getRequestorPackageName());
+ // Cannot assign null into NetworkCapabilities.Builder
+ try {
+ final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(null);
+ fail("Should not set null into NetworkCapabilities.Builder");
+ } catch (NullPointerException expected) { }
+ assertEquals(nc, new NetworkCapabilities.Builder(nc).build());
+ }
}
diff --git a/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt
index 9119d62..7b22e45 100644
--- a/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt
+++ b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt
@@ -31,7 +31,6 @@
import android.os.Build
import androidx.test.filters.SmallTest
import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.assertFieldCountEquals
import com.android.testutils.assertNetworkStatsEquals
import com.android.testutils.assertParcelingIsLossless
@@ -47,70 +46,22 @@
class NetworkStatsApiTest {
@Rule
@JvmField
- val ignoreRule = DevSdkIgnoreRule()
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
private val testStatsEmpty = NetworkStats(0L, 0)
+ // Note that these variables need to be initialized outside of constructor, initialize
+ // here with methods that don't exist in Q devices will result in crash.
+
// stats1 and stats2 will have some entries with common keys, which are expected to
// be merged if performing add on these 2 stats.
- private val testStats1 = NetworkStats(0L, 0)
- // Entries which only appear in set1.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4))
- // Entries which are common for set1 and set2.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0))
- .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8))
- .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0))
-
- private val testStats2 = NetworkStats(0L, 0)
- // Entries which are common for set1 and set2.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45))
- .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7))
- .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0))
- // Entry which only appears in set2.
- .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+ private lateinit var testStats1: NetworkStats
+ private lateinit var testStats2: NetworkStats
// This is a result of adding stats1 and stats2, while the merging of common key items is
// subject to test later, this should not be initialized with for a loop to add stats1
// and stats2 above.
- private val testStats3 = NetworkStats(0L, 9)
- // Entries which are unique either in stats1 or stats2.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
- .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
- // Entries which are common for stats1 and stats2 are being merged.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49))
- .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15))
- .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0))
+ private lateinit var testStats3: NetworkStats
companion object {
private const val TEST_IFACE = "test0"
@@ -120,13 +71,67 @@
@Before
fun setUp() {
+ testStats1 = NetworkStats(0L, 0)
+ // Entries which only appear in set1.
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4))
+ // Entries which are common for set1 and set2.
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0))
+ .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8))
+ .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0))
assertEquals(8, testStats1.size())
+
+ testStats2 = NetworkStats(0L, 0)
+ // Entries which are common for set1 and set2.
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45))
+ .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7))
+ .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0))
+ // Entry which only appears in set2.
+ .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
assertEquals(5, testStats2.size())
+
+ testStats3 = NetworkStats(0L, 9)
+ // Entries which are unique either in stats1 or stats2.
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
+ .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+ // Entries which are common for stats1 and stats2 are being merged.
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49))
+ .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15))
+ .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0))
assertEquals(9, testStats3.size())
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testAddEntry() {
val expectedEntriesInStats2 = arrayOf(
Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
@@ -156,7 +161,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testAdd() {
var stats = NetworkStats(0L, 0)
assertNetworkStatsEquals(testStatsEmpty, stats)
@@ -168,7 +172,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testParcelUnparcel() {
assertParcelingIsLossless(testStatsEmpty)
assertParcelingIsLossless(testStats1)
@@ -177,7 +180,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testDescribeContents() {
assertEquals(0, testStatsEmpty.describeContents())
assertEquals(0, testStats1.describeContents())
@@ -186,7 +188,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testSubtract() {
// STATS3 - STATS2 = STATS1
assertNetworkStatsEquals(testStats1, testStats3.subtract(testStats2))
@@ -195,7 +196,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testMethodsDontModifyReceiver() {
listOf(testStatsEmpty, testStats1, testStats2, testStats3).forEach {
val origStats = it.clone()
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/Collation.h b/tools/stats_log_api_gen/Collation.h
index d56f2be..b513463e 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -160,7 +160,7 @@
int exclusiveField = 0;
int defaultState = INT_MAX;
int triggerStateReset = INT_MAX;
- bool nested;
+ bool nested = true;
int uidField = 0;
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