diff options
240 files changed, 6541 insertions, 3499 deletions
diff --git a/Android.bp b/Android.bp index 874d76fe8d00..d4ca7066781a 100644 --- a/Android.bp +++ b/Android.bp @@ -911,6 +911,7 @@ cc_library { 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 99e82e7a3367..579963b71cf7 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -94,7 +94,6 @@ stubs_defaults { // 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 793247e88614..fc9052e8beac 100644 --- a/apex/permission/framework/Android.bp +++ b/apex/permission/framework/Android.bp @@ -46,12 +46,10 @@ stubs_defaults { 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 { 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 { 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 { 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 707113b9672c..3eabb88fec7d 100644 --- a/apex/sdkextensions/framework/Android.bp +++ b/apex/sdkextensions/framework/Android.bp @@ -48,7 +48,6 @@ stubs_defaults { 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 32e13e31eebe..15d74951019d 100644 --- a/apex/statsd/Android.bp +++ b/apex/statsd/Android.bp @@ -67,7 +67,6 @@ cc_library_shared { "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 2d64f190839c..0df96e149d4f 100644 --- a/apex/statsd/tests/libstatspull/Android.bp +++ b/apex/statsd/tests/libstatspull/Android.bp @@ -32,7 +32,7 @@ android_test { "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 23c1a23dc651..641767c7bccd 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -214,6 +214,7 @@ package android.app { 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"; @@ -4859,6 +4860,13 @@ package android.view { method public abstract String asyncImpl() default ""; } + 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); + } + public class SurfaceControlViewHost { method public void relayout(android.view.WindowManager.LayoutParams); method public void setView(@NonNull android.view.View, @NonNull android.view.WindowManager.LayoutParams); @@ -5208,10 +5216,10 @@ package android.window { 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 d3d7e1d483e8..65061d0c9bda 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -113,17 +113,18 @@ cc_defaults { 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 @@ cc_binary { 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 28bf21ae52bf..743ccc4ed451 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 @@ static void createLogEventAndMatcher(LogEvent* event, FieldMatcher* field_matche 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 c7d01cc406fc..7a455650a31b 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 @@ static void createLogEventAndLink(LogEvent* event, Metric2Condition *link) { 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 482d66fc7556..89fd3d9b29ab 100644 --- a/cmds/statsd/benchmark/metric_util.cpp +++ b/cmds/statsd/benchmark/metric_util.cpp @@ -247,21 +247,37 @@ FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) 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 @@ std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent( 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 @@ std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(uint64_t timestampNs, 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 c5fcf7c27440..3efaa850a921 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 CreateAttributionUidAndTagDimensions(const int atomId, 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 4385964f7f0e..cfc1de49e259 100644 --- a/cmds/statsd/src/FieldValue.cpp +++ b/cmds/statsd/src/FieldValue.cpp @@ -120,9 +120,9 @@ bool isAttributionUidField(const FieldValue& value) { } 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 @@ bool isAttributionUidField(const Field& field, const Value& value) { 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 3536e5a5c962..92e09ea0f8f9 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -367,7 +367,8 @@ public: 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 @@ public: 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 @@ public: // 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 @@ private: } // 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 @@ int getUidIfExists(const FieldValue& value); 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 982a63e3e08c..325cbc7e80e5 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -138,13 +138,6 @@ void StatsLogProcessor::onPeriodicAlarmFired( } } -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 @@ void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event } 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 bd15264c008f..2a1716e0ce2a 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -418,7 +418,8 @@ message Atom { 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 @@ message ExclusionRectStateChanged { 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 6d9c644bb40e..bbae3fef7934 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -43,20 +43,23 @@ using android::base::StringPrintf; 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 @@ void ConfigManager::configReceiverDied(void* cookie) { } 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 79a7e8d318e2..ebe961014336 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -44,19 +44,23 @@ namespace statsd { // Stores the puller as a wp to avoid holding a reference in case it is unregistered and // pullAtomCallbackDied is never called. struct PullAtomCallbackDeathCookie { - PullAtomCallbackDeathCookie(sp<StatsPullerManager> pullerManager, const PullerKey& pullerKey, - const wp<StatsPuller>& puller) - : mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) { + 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 aee725698c30..90247cf9d68c 100644 --- a/cmds/statsd/src/external/puller_util.cpp +++ b/cmds/statsd/src/external/puller_util.cpp @@ -49,10 +49,14 @@ using namespace std; */ 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 @@ void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const } } } 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 96bf04f4f6d6..8b6a86464155 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -240,14 +240,20 @@ void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, 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 9e21c777e6ff..4eeb7d64a463 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -213,6 +213,7 @@ private: 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 1f8bbd7f528c..2b4c6a3cbf1e 100644 --- a/cmds/statsd/src/matchers/matcher_util.cpp +++ b/cmds/statsd/src/matchers/matcher_util.cpp @@ -81,18 +81,17 @@ bool combinationMatch(const vector<int>& children, const LogicalOperation& opera 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, } 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, 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 0bf24f1d3606..f5ba8fd0d60d 100644 --- a/cmds/statsd/tests/FieldValue_test.cpp +++ b/cmds/statsd/tests/FieldValue_test.cpp @@ -41,22 +41,10 @@ void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timest 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 @@ void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timest 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 6f4c8e48eff2..26423d464027 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 @@ void makeIntLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t tim 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 @@ void makeFloatLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t t 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 @@ void makeStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t 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, 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_writeInt32(statsEvent, field); + AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue); - AStatsEvent_release(statsEvent); + parseStatsEventToLogEvent(statsEvent, logEvent); } void makeAttributionLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, @@ -115,22 +92,10 @@ void makeAttributionLogEvent(LogEvent* logEvent, const int32_t atomId, const int 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 @@ void makeBoolLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t ti 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 @@ TEST(AtomMatcherTest, TestUidFieldMatcher) { 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); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); + // Make event with is_uid annotation. 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)); + makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ANNOTATION_ID_IS_UID, true); - // Tag found in kAtomsWithUidField and has matching uid + // 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 7febb35355a3..ba5b032b80d0 100644 --- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp +++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp @@ -67,22 +67,12 @@ void makeWakeLockEvent(LogEvent* logEvent, uint32_t atomId, uint64_t timestamp, 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 81e1c05c1cf4..60403f2a3e0f 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 @@ std::unique_ptr<LogEvent> CreateAppStartOccurredEvent( 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 6aff9ef80a71..4b9bac127dc8 100644 --- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp +++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp @@ -190,13 +190,13 @@ TEST_F(StatsCallbackPullerTest, RegisterAndTimeout) { 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 e8200d5c7f52..504335845cab 100644 --- a/cmds/statsd/tests/external/StatsPuller_test.cpp +++ b/cmds/statsd/tests/external/StatsPuller_test.cpp @@ -64,16 +64,10 @@ std::unique_ptr<LogEvent> createSimpleEvent(int64_t eventTimeNs, int64_t value) 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 15425d8810dc..c2cfb371d329 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 @@ int hostNonAdditiveData = 22; 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 @@ std::shared_ptr<LogEvent> makeUidLogEvent(uint64_t timestampNs, int uid, int dat 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 @@ std::shared_ptr<LogEvent> makeNonUidAtomLogEvent(uint64_t timestampNs, int data1 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 6dc041f9fb6e..a15f95bef358 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 @@ std::unique_ptr<LogEvent> makeLogEvent(uint64_t timestampNs) { 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 d55996cb1b7a..65f8de69711d 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -46,26 +46,17 @@ void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { 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 6143dc0dc5d1..30f815962160 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 @@ void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { 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 e58bbb7893d7..97647a7e0867 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -43,14 +43,9 @@ void makeLogEvent(LogEvent* logEvent, int32_t atomId, int64_t timestampNs, strin 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 2fe05a4430c3..42d0d5d8c530 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -64,14 +64,9 @@ shared_ptr<LogEvent> makeLogEvent(int32_t atomId, int64_t timestampNs, int32_t v 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 b623a0978f18..009e49a5523f 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -611,7 +611,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { 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 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { 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 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { 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 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { 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 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { 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 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { 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 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { 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 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { 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 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMin) { 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 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) { 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 @@ TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { 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 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSum) { 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 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { 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 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { // 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 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { 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 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) // 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 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) // 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 ac3ad690f81e..7b952d7a392e 100644 --- a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp +++ b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp @@ -171,13 +171,9 @@ shared_ptr<LogEvent> makeCpuActiveTimeAtom(int32_t uid, int64_t timeMillis) { 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 a5b8e1c50c33..78c80bc8307c 100644 --- a/cmds/statsd/tests/state/StateTracker_test.cpp +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -62,7 +62,7 @@ int getStateInt(StateManager& mgr, int atomId, const HashableDimensionKey& query // 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 @@ std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& 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 @@ std::unique_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::stri 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 7d765d3fbbf5..ed3cf5b96b42 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -507,23 +507,26 @@ void getPartialWakelockKey(int uid, HashableDimensionKey* key) { } // END: get primary key functions -shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, - int32_t value2) { - 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 @@ void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, 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 @@ void CreateThreeValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeN 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 @@ void CreateRepeatedValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTi 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 @@ void CreateNoValuesLogEvent(LogEvent* logEvent, 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); - 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 @@ std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( 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 @@ std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs) { 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 @@ std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs) { 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 @@ std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestam 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 @@ std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampN 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 @@ std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent( 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 @@ std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(uint64_t timestampNs, 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 @@ std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent( 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 @@ std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(uint64_t timestampNs, 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 @@ std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent( 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 @@ std::unique_ptr<LogEvent> CreateAppCrashOccurredEvent(uint64_t timestampNs, cons 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 @@ std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(uint64_t timestampNs, in 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 @@ std::unique_ptr<LogEvent> CreateUidProcessStateChangedEvent( 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 @@ std::unique_ptr<LogEvent> CreateBleScanStateChangedEvent(uint64_t timestampNs, 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 @@ std::unique_ptr<LogEvent> CreateOverlayStateChangedEvent(int64_t timestampNs, co 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 f24705a0c89f..d6ea77eb2c7d 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, const std::string& tag, HashableDimensionKey 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 2a7cfd306174..d5da0b42402c 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 @@ public class TestDrive { }; 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/annotation/NonNull.java b/core/java/android/annotation/NonNull.java index a95bf3b8061e..c5aff9d6794e 100644 --- a/core/java/android/annotation/NonNull.java +++ b/core/java/android/annotation/NonNull.java @@ -30,8 +30,8 @@ import java.lang.annotation.Target; * <p> * This is a marker annotation and it has no specific attributes. * - * @paramDoc This value must never be {@code null}. - * @returnDoc This value will never be {@code null}. + * @paramDoc This value cannot be {@code null}. + * @returnDoc This value cannot be {@code null}. * @hide */ @Retention(SOURCE) diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 46b06fb64b80..3a708a6f699b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1395,6 +1395,7 @@ public class AppOpsManager { 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/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index e476993f003f..b7ceb6ae1b4c 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -299,7 +299,6 @@ interface IActivityTaskManager { in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations); void suppressResizeConfigChanges(boolean suppress); - void moveTasksToFullscreenStack(int fromStackId, boolean onTop); boolean moveTopActivityToPinnedStack(int stackId, in Rect bounds); boolean isInMultiWindowMode(in IBinder token); boolean isInPictureInPictureMode(in IBinder token); diff --git a/core/java/android/app/IWindowToken.aidl b/core/java/android/app/IWindowToken.aidl index 8ea881fba09c..3627b0f13a7f 100644 --- a/core/java/android/app/IWindowToken.aidl +++ b/core/java/android/app/IWindowToken.aidl @@ -30,4 +30,6 @@ import android.view.IWindow; */ 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 91a857225324..e599a5ce81ef 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1658,6 +1658,9 @@ public final class SystemServiceRegistry { 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 @@ public final class SystemServiceRegistry { // 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 @@ public final class SystemServiceRegistry { 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 878993ebcd19..3a06c9d79fee 100644 --- a/core/java/android/app/WindowContext.java +++ b/core/java/android/app/WindowContext.java @@ -28,6 +28,8 @@ import android.view.IWindowManager; import android.view.WindowManagerGlobal; import android.view.WindowManagerImpl; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.ref.Reference; /** @@ -75,8 +77,6 @@ public class WindowContext extends ContextWrapper { // 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 @@ public class WindowContext extends ContextWrapper { @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 @@ public class WindowContext extends ContextWrapper { 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 ed0179bb9839..301960ec53f9 100644 --- a/core/java/android/app/WindowTokenClient.java +++ b/core/java/android/app/WindowTokenClient.java @@ -20,6 +20,9 @@ import android.content.Context; 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 @@ import android.os.IBinder; 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 @@ public class WindowTokenClient extends IWindowToken.Stub { * @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 db833ec478bd..473725f40cf1 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 @@ import android.view.SurfaceControlViewHost; @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 @@ public class DevicePolicyKeyguardService extends Service { */ @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 fc48e7f18f5f..f216db6fc717 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -979,14 +979,17 @@ public final class BluetoothAdapter { 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 @@ public final class BluetoothAdapter { @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 e446f4fa5eb4..0a4627da223a 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -687,6 +687,19 @@ public abstract class ContentResolver implements ContentInterface { 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 f00feaeb2f5a..92df16ced8a3 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 @@ import android.os.incremental.IncrementalFileSystemControlParcel; 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/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 91dae66d08ae..aa75f6042db8 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 @@ public abstract class CameraMetadata<TKey> { * * @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 b7b3c4fc8add..5d2c9d18c00c 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -607,6 +607,9 @@ public class Process { * 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 @@ public class Process { @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 @@ public class Process { 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 5f3f14facd75..a4c99c006d80 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -333,6 +333,9 @@ public class ZygoteProcess { * 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 @@ public class ZygoteProcess { @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 @@ public class ZygoteProcess { 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 @@ public class ZygoteProcess { * @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 @@ public class ZygoteProcess { @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 @@ public class ZygoteProcess { } 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 @@ public class ZygoteProcess { 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 d8308c7c3362..2dbaea860e2a 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -38,13 +38,6 @@ interface IIncrementalService { 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 000000000000..5800ecf63a1e --- /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 5f01408944e8..35518db32829 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -19,13 +19,11 @@ package android.os.incremental; 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 @@ public final class IncrementalManager { 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 05877a59368a..c047dc0d07c7 100644 --- a/core/java/android/service/dataloader/DataLoaderService.java +++ b/core/java/android/service/dataloader/DataLoaderService.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; 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.InstallationFile; 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 @@ public abstract class DataLoaderService extends Service { 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 84ac90bae258..5648adfb78bc 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -124,13 +124,20 @@ interface IWindowManager * @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 69bab4df2cae..6cb93746a9a4 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -91,6 +91,8 @@ public class InsetsSourceConsumer { if (mSourceControl == control) { return; } + SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null; + final InsetsSourceControl lastControl = mSourceControl; mSourceControl = control; @@ -116,6 +118,12 @@ public class InsetsSourceConsumer { // 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 f3ec65f997ba..e001b668f71a 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -44,8 +44,7 @@ public class InsetsSourceControl implements Parcelable { 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 a37c1cbc76ad..1086774fc8ff 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -32,6 +32,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; import android.graphics.ColorSpace; @@ -215,6 +216,10 @@ public final class SurfaceControl implements Parcelable { 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); private final CloseGuard mCloseGuard = CloseGuard.get(); private String mName; @@ -222,6 +227,7 @@ public final class SurfaceControl implements Parcelable { * @hide */ public long mNativeObject; + private long mNativeHandle; // TODO: Move this to native. private final Object mSizeLock = new Object(); @@ -424,12 +430,13 @@ public final class SurfaceControl implements Parcelable { 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; @@ -849,23 +856,19 @@ public final class SurfaceControl implements Parcelable { 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) { @@ -917,6 +920,18 @@ public final class SurfaceControl implements Parcelable { } /** + * 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}. * @@ -973,6 +988,7 @@ public final class SurfaceControl implements Parcelable { if (mNativeObject != 0) { nativeRelease(mNativeObject); mNativeObject = 0; + mNativeHandle = 0; mCloseGuard.close(); } } @@ -2868,4 +2884,25 @@ public final class SurfaceControl implements Parcelable { } } } + + /** + * 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(). The caller must have the + * ACCESS_SURFACE_FLINGER permission, or else the call will fail, returning 0. + * @hide + */ + @TestApi + public static long acquireFrameRateFlexibilityToken() { + return nativeAcquireFrameRateFlexibilityToken(); + } + + /** + * Release a frame rate flexibility token. + * @hide + */ + @TestApi + public static void releaseFrameRateFlexibilityToken(long token) { + nativeReleaseFrameRateFlexibilityToken(token); + } } diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index 842ba2975b3b..6ad5cb913553 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -27,8 +27,6 @@ import android.os.Parcel; 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 @@ public final class ConversationActions implements Parcelable { */ @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 eee222b9bf4c..6ae70b779960 100644 --- a/core/java/android/window/DisplayAreaOrganizer.java +++ b/core/java/android/window/DisplayAreaOrganizer.java @@ -33,8 +33,8 @@ public class DisplayAreaOrganizer extends WindowOrganizer { 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 ff03f1a1a2ab..505a05eb9c23 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -205,9 +205,15 @@ public final class Zygote { /** 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 @@ public final class Zygote { * @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 @@ public final class Zygote { 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 @@ public final class Zygote { 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 @@ public final class Zygote { * 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 @@ public final class Zygote { 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 @@ public final class Zygote { 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 @@ public final class Zygote { */ @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 1a63765fcaa6..94c1f71a26db 100644 --- a/core/java/com/android/internal/os/ZygoteArguments.java +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -227,11 +227,22 @@ class ZygoteArguments { 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 @@ class ZygoteArguments { } } 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 bc8dfd4aa402..e6a3029c5b2b 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -258,7 +258,8 @@ class ZygoteConnection { 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 ec1f516df5f3..c2b13c971020 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -757,9 +757,11 @@ public class ZygoteInit { 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 71cf5cad316e..b6c58e16b51c 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.ColorDrawable; 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.DecorCaptionView; 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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind 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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind initResizingPaints(); mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK); - mUseNewInsetsApi = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.R; } void setBackgroundFallback(@Nullable Drawable fallbackDrawable) { @@ -1189,23 +1181,23 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // 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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind || (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 138d0dd39537..25c114f4b7c1 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -343,8 +343,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { /** @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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { /** 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @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/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 5e72fa07242e..26684201019c 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -386,14 +386,17 @@ public class ConversationLayout extends FrameLayout /** @hide */ public void setUnreadCount(int unreadCount) { - mUnreadBadge.setVisibility(mIsCollapsed && unreadCount > 1 ? VISIBLE : GONE); - CharSequence text = unreadCount >= 100 - ? getResources().getString(R.string.unread_convo_overflow, 99) - : String.format(Locale.getDefault(), "%d", unreadCount); - mUnreadBadge.setText(text); - mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor)); - boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f; - mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE); + boolean visible = mIsCollapsed && unreadCount > 1; + mUnreadBadge.setVisibility(visible ? VISIBLE : GONE); + if (visible) { + CharSequence text = unreadCount >= 100 + ? getResources().getString(R.string.unread_convo_overflow, 99) + : String.format(Locale.getDefault(), "%d", unreadCount); + mUnreadBadge.setText(text); + mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor)); + boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f; + mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE); + } } private void addRemoteInputHistoryToMessages( diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index b2ca0a7bcbe3..1cfa12df32ab 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -29,11 +29,13 @@ #include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_view_Surface.h> #include <android_runtime/android_view_SurfaceSession.h> +#include <gui/ISurfaceComposer.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> #include <jni.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> +#include <private/gui/ComposerService.h> #include <stdio.h> #include <system/graphics.h> #include <ui/ConfigStoreTypes.h> @@ -624,6 +626,23 @@ static void nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong transactionObj, transaction->setFrameRate(ctrl, frameRate, static_cast<int8_t>(compatibility)); } +static jlong nativeAcquireFrameRateFlexibilityToken(JNIEnv* env, jclass clazz) { + sp<ISurfaceComposer> composer = ComposerService::getComposerService(); + sp<IBinder> token; + status_t result = composer->acquireFrameRateFlexibilityToken(&token); + if (result < 0) { + ALOGE("Failed acquiring frame rate flexibility token: %s (%d)", strerror(-result), result); + return 0; + } + token->incStrong((void*)nativeAcquireFrameRateFlexibilityToken); + return reinterpret_cast<jlong>(token.get()); +} + +static void nativeReleaseFrameRateFlexibilityToken(JNIEnv* env, jclass clazz, jlong tokenLong) { + sp<IBinder> token(reinterpret_cast<IBinder*>(tokenLong)); + token->decStrong((void*)nativeAcquireFrameRateFlexibilityToken); +} + static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) { const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds(); jlongArray array = env->NewLongArray(displayIds.size()); @@ -1411,6 +1430,12 @@ static void nativeSetGlobalShadowSettings(JNIEnv* env, jclass clazz, jfloatArray 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[] = { @@ -1474,6 +1499,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetShadowRadius }, {"nativeSetFrameRate", "(JJFI)V", (void*)nativeSetFrameRate }, + {"nativeAcquireFrameRateFlexibilityToken", "()J", + (void*)nativeAcquireFrameRateFlexibilityToken }, + {"nativeReleaseFrameRateFlexibilityToken", "(J)V", + (void*)nativeReleaseFrameRateFlexibilityToken }, {"nativeGetPhysicalDisplayIds", "()[J", (void*)nativeGetPhysicalDisplayIds }, {"nativeGetPhysicalDisplayToken", "(J)Landroid/os/IBinder;", @@ -1583,6 +1612,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (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 ea3c0fa9fc3c..4b30359e671a 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::StringAppendF; 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 int gSystemServerSocketFd = -1; 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 @@ static void relabelAllDirs(const char* path, security_context_t context, fail_fn * 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 @@ static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list, 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 @@ static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list, 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 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, 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 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, // 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 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( 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 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( 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 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( 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 @@ static void com_android_internal_os_Zygote_nativeSpecializeAppProcess( 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 @@ static jint com_android_internal_os_Zygote_nativeParseSigChld(JNIEnv* env, jclas 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 @@ static const JNINativeMethod gMethods[] = { {"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 @@ static const JNINativeMethod gMethods[] = { (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 684a29249294..896ee4fa42a5 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 @@ enum EventId { 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 76f8f0febf59..61b9b25fe7cf 100644 --- a/core/proto/android/stats/dnsresolver/dns_resolver.proto +++ b/core/proto/android/stats/dnsresolver/dns_resolver.proto @@ -62,6 +62,13 @@ enum NsRcode { 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 @@ enum NetworkType { 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 451363f6bd3d..c72239cd684e 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 000000000000..630e16ac80d4 --- /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 cbb379bf8207..d432dda4a1be 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -696,8 +696,7 @@ public class InsetsControllerTest { // 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 74524bf6d76f..ddc977d380ae 100644 --- a/core/tests/coretests/src/android/view/WindowMetricsTest.java +++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java @@ -56,17 +56,17 @@ public class WindowMetricsTest { @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 59bdf3dad43e..0389639e9edb 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -137,6 +137,7 @@ applications that come with the platform <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 07cf41560cf3..19ad6f717ea5 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 415092623531..75ea0cbada92 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -47,10 +47,10 @@ interface ILocationManager 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 fcbd3e540291..d1b41dfccf63 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -718,7 +718,7 @@ public class LocationManager { 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 @@ public class LocationManager { 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 @@ public class LocationManager { 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 @@ public class LocationManager { 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 @@ public class LocationManager { 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 @@ public class LocationManager { 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 4dd1a29d8595..5f0acc8f7647 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -264,8 +264,8 @@ public final class LocationRequest implements Parcelable { /* numUpdates= */ Integer.MAX_VALUE, /* smallestDisplacement= */ 0, /* hideFromAppOps= */ false, - /* lowPowerMode= */ false, /* locationSettingsIgnored= */ false, + /* lowPowerMode= */ false, /* workSource= */ null); } @@ -282,8 +282,8 @@ public final class LocationRequest implements Parcelable { src.mNumUpdates, src.mSmallestDisplacement, src.mHideFromAppOps, - src.mLowPowerMode, src.mLocationSettingsIgnored, + src.mLowPowerMode, src.mWorkSource); } diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java index 88a829546989..540955f3b393 100644 --- a/media/java/android/media/MediaMetrics.java +++ b/media/java/android/media/MediaMetrics.java @@ -17,6 +17,7 @@ package android.media; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.os.Bundle; @@ -24,6 +25,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Objects; /** * MediaMetrics is the Java interface to the MediaMetrics service. @@ -50,6 +52,77 @@ public class MediaMetrics { private static final Charset MEDIAMETRICS_CHARSET = StandardCharsets.UTF_8; /** + * Key interface. + * + * The presence of this {@code Key} interface on an object allows + * it to be used to set metrics. + * + * @param <T> type of value associated with {@code Key}. + */ + public interface Key<T> { + /** + * Returns the internal name of the key. + */ + @NonNull + String getName(); + + /** + * Returns the class type of the associated value. + */ + @NonNull + Class<T> getValueClass(); + } + + /** + * Returns a Key object with the correct interface for MediaMetrics. + * + * @param name The name of the key. + * @param type The class type of the value represented by the key. + * @param <T> The type of value. + * @return a new key interface. + */ + @NonNull + public static <T> Key<T> createKey(@NonNull String name, @NonNull Class<T> type) { + // Implementation specific. + return new Key<T>() { + private final String mName = name; + private final Class<T> mType = type; + + @Override + @NonNull + public String getName() { + return mName; + } + + @Override + @NonNull + public Class<T> getValueClass() { + return mType; + } + + /** + * Return true if the name and the type of two objects are the same. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Key)) { + return false; + } + Key<?> other = (Key<?>) obj; + return mName.equals(other.getName()) && mType.equals(other.getValueClass()); + } + + @Override + public int hashCode() { + return Objects.hash(mName, mType); + } + }; + } + + /** * Item records properties and delivers to the MediaMetrics service * */ @@ -202,6 +275,28 @@ public class MediaMetrics { } /** + * Sets a metrics typed key + * @param key + * @param value + * @param <T> + * @return + */ + @NonNull + public <T> Item set(@NonNull Key<T> key, @Nullable T value) { + if (value instanceof Integer) { + putInt(key.getName(), (int) value); + } else if (value instanceof Long) { + putLong(key.getName(), (long) value); + } else if (value instanceof Double) { + putDouble(key.getName(), (double) value); + } else if (value instanceof String) { + putString(key.getName(), (String) value); + } + // if value is null, etc. no error is raised. + return this; + } + + /** * Sets the property with key to an integer (32 bit) value. * * @param key diff --git a/packages/CarSystemUI/res/layout/notification_center_activity.xml b/packages/CarSystemUI/res/layout/notification_center_activity.xml index 4fef48918c97..0af74c4462a6 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 bf71396984b8..3b53c6aaeac3 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 1b0a211b733d..067e359e0e49 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 b057198d5177..44e43fe9af8c 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 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; */ 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 1901a2db879d..d8a894cfa8ba 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.LayoutInflater; 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.plugins.statusbar.StatusBarStateController; 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 @@ import javax.inject.Singleton; /** View controller for the notification panel. */ @Singleton -public class NotificationPanelViewController extends OverlayViewController { - - // 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; +public class NotificationPanelViewController extends OverlayPanelViewController { + 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 @@ public class NotificationPanelViewController extends OverlayViewController { 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 @@ public class NotificationPanelViewController extends OverlayViewController { Context context, @Main Resources resources, OverlayViewGlobalStateController overlayViewGlobalStateController, + FlingAnimationUtils.Builder flingAnimationUtilsBuilder, /* Other things */ CarServiceProvider carServiceProvider, @@ -135,26 +109,21 @@ public class NotificationPanelViewController extends OverlayViewController { 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 @@ public class NotificationPanelViewController extends OverlayViewController { + " 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 @@ public class NotificationPanelViewController extends OverlayViewController { 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 @@ public class NotificationPanelViewController extends OverlayViewController { 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); + + 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; }); + } - // Attached to the notification ui to detect close request of the notification shade. + 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 @@ public class NotificationPanelViewController extends OverlayViewController { 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 @@ public class NotificationPanelViewController extends OverlayViewController { 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 @@ public class NotificationPanelViewController extends OverlayViewController { } 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 @@ public class NotificationPanelViewController extends OverlayViewController { mNotificationDataManager.clearAll(); } - View.OnTouchListener getTopNavBarNotificationTouchListener() { - return mTopNavBarNotificationTouchListener; - } - - View.OnTouchListener getNavBarNotificationTouchListener() { - return mNavBarNotificationTouchListener; - } - - 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 shouldAnimateCollapsePanel() { + return true; } - /** - * 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); - } - }); + @Override + protected void onAnimateCollapsePanel() { + // No op. } - private void animateCollapsePanels() { - if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) { - return; - } - getOverlayViewGlobalStateController().setWindowFocusable(false); - animateNotificationPanel(mClosingVelocity, true); + @Override + protected boolean shouldAnimateExpandPanel() { + return mCommandQueue.panelsEnabled(); } - 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 @@ public class NotificationPanelViewController extends OverlayViewController { } } - 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 @@ public class NotificationPanelViewController extends OverlayViewController { } } + @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 @@ public class NotificationPanelViewController extends OverlayViewController { + ((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 @@ public class NotificationPanelViewController extends OverlayViewController { } /** - * 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 @@ public class NotificationPanelViewController extends OverlayViewController { // 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 110c2ee8854b..9d71797794b8 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 @@ public class NotificationPanelViewMediator implements OverlayViewMediator, @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 000000000000..58022f12e58c --- /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 f2748b89c95c..76557fda6926 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.verify; 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 000000000000..04f2d06ca71c --- /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 3be962627f62..331326168ba4 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 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper @SmallTest public class OverlayViewControllerTest extends SysuiTestCase { - private MockOverlayViewController mOverlayViewController; + private TestOverlayViewController mOverlayViewController; private ViewGroup mBaseLayout; @Mock @@ -56,7 +56,7 @@ public class OverlayViewControllerTest extends SysuiTestCase { 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 @@ public class OverlayViewControllerTest extends SysuiTestCase { 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 8aa0aec28fb8..a53bc9f966d2 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 @@ public class WifiEntryPreference extends Preference implements WifiEntry.WifiEnt // 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 a9f31ce12b42..46e699d3bed5 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 @@ public class WifiEntryPreferenceTest { 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 @@ public class WifiEntryPreferenceTest { } @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 ca085b69c35d..f5cd727a6d03 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 2cc4b220fe2b..000000000000 --- 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 fc3bf941b27a..e5ac5f89cd25 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 9b9fbed0d904..344479f371d7 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 7eb5a8f2b06c..e99245fa438f 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -34,6 +34,8 @@ import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; +import java.util.NoSuchElementException; + /** * Encapsulates all logic for secondary lockscreen state management. */ @@ -79,7 +81,9 @@ public class AdminSecondaryLockScreenController { private final IKeyguardCallback mCallback = new IKeyguardCallback.Stub() { @Override public void onDismiss() { - dismiss(UserHandle.getCallingUserId()); + mHandler.post(() -> { + dismiss(UserHandle.getCallingUserId()); + }); } @Override @@ -91,7 +95,9 @@ public class AdminSecondaryLockScreenController { if (surfacePackage != null) { mView.setChildSurfacePackage(surfacePackage); } else { - dismiss(KeyguardUpdateMonitor.getCurrentUser()); + mHandler.post(() -> { + dismiss(KeyguardUpdateMonitor.getCurrentUser()); + }); } } }; @@ -122,6 +128,7 @@ public class AdminSecondaryLockScreenController { // 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 @@ public class AdminSecondaryLockScreenController { * 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 @@ public class AdminSecondaryLockScreenController { 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 @@ public class AdminSecondaryLockScreenController { 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 d5a08dda9853..aa2fe3c7f8fc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java @@ -85,7 +85,8 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback { // 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 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback { * @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 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback { } @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 d1544346a25a..af5196f92bcb 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.Bitmap; 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 androidx.palette.graphics.Palette; 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 @@ public class KeyguardMediaPlayer { 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 @@ public class KeyguardMediaPlayer { 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 class KeyguardMediaPlayer { 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 49dcfffb0d72..e38472745234 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java @@ -25,6 +25,15 @@ public interface KeyguardSecurityCallback { 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 ba8a1a945a77..1e1ce4e6d159 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -115,7 +115,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe // 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 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe * @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 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe } } // 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 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe 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 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe @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 23fa645eff16..5442299881c0 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -120,7 +120,7 @@ public class ImageWallpaper extends WallpaperService { 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 @@ public class ImageWallpaper extends WallpaperService { 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 6923079dd5c4..13d6a9bef266 100644 --- a/packages/SystemUI/src/com/android/systemui/Interpolators.java +++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java @@ -49,6 +49,8 @@ public class Interpolators { 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 9d885fd3c207..99e5eb66a00f 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** - * 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** - * 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 be9cd5f01c86..4c149ddd3939 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.content.Context; 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 @@ public class BubbleData { } @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 @@ public class BubbleData { 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 eff693436451..644e54fb82ae 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 static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME 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.Point; 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.QuickStepContract; 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 @@ public class BubbleStackView extends FrameLayout { mExpandedAnimationController.dump(fd, pw, args); } - private BubbleTouchHandler mTouchHandler; private BubbleController.BubbleExpandListener mExpandListener; private SysUiState mSysUiState; @@ -296,7 +298,7 @@ public class BubbleStackView extends FrameLayout { @Override public void setValue(Object o, float v) { - onFlyoutDragged(v); + setFlyoutStateForDragLength(v); } }; @@ -337,13 +339,6 @@ public class BubbleStackView extends FrameLayout { 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 class BubbleStackView extends FrameLayout { public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { mExpandedAnimationController.dismissDraggedOutBubble( mExpandedAnimationController.getDraggedOutBubble(), - mReleasedInDismissTargetAction); + BubbleStackView.this::dismissMagnetizedObject); hideDismissTarget(); } }; @@ -410,7 +405,7 @@ public class BubbleStackView extends FrameLayout { mStackAnimationController.implodeStack( () -> { resetDesaturationAndDarken(); - mReleasedInDismissTargetAction.run(); + dismissMagnetizedObject(); } ); @@ -418,6 +413,197 @@ public class BubbleStackView extends FrameLayout { } }; + /** + * 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 @@ public class BubbleStackView extends FrameLayout { private BubbleManageEducationView mManageEducationView; private boolean mAnimatingManageEducationAway; + @SuppressLint("ClickableViewAccessibility") public BubbleStackView(Context context, BubbleData data, @Nullable SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, @@ -444,8 +631,6 @@ public class BubbleStackView extends FrameLayout { mBubbleData = data; mInflater = LayoutInflater.from(context); - mTouchHandler = new BubbleTouchHandler(this, data, context); - setOnTouchListener(mTouchHandler); mSysUiState = sysUiState; @@ -514,7 +699,7 @@ public class BubbleStackView extends FrameLayout { 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 @@ public class BubbleStackView extends FrameLayout { // 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 @@ public class BubbleStackView extends FrameLayout { 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 @@ public class BubbleStackView extends FrameLayout { } } + @SuppressLint("ClickableViewAccessibility") private void setUpFlyout() { if (mFlyout != null) { removeView(mFlyout); @@ -699,6 +897,8 @@ public class BubbleStackView extends FrameLayout { 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 @@ public class BubbleStackView extends FrameLayout { 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 @@ public class BubbleStackView extends FrameLayout { } // via BubbleData.Listener + @SuppressLint("ClickableViewAccessibility") void addBubble(Bubble bubble) { if (DEBUG_BUBBLE_STACK_VIEW) { Log.d(TAG, "addBubble: " + bubble); @@ -944,6 +1146,9 @@ public class BubbleStackView extends FrameLayout { 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 @@ public class BubbleStackView extends FrameLayout { 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 @@ public class BubbleStackView extends FrameLayout { } } - /* - * 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 @@ public class BubbleStackView extends FrameLayout { } /** - * @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 @@ public class BubbleStackView extends FrameLayout { } } - /** Called when the collapsed stack is tapped on. */ - void onStackTapped() { - if (!maybeShowStackUserEducation()) { - mBubbleData.setExpanded(true); - } - } - - /** 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); - } - - /** 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; - } - - mExpandedAnimationController.dragBubbleOut(bubble, x, y); - springInDismissTarget(); - } - - /** 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(); + /** + * 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) { - // 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); + /** + * 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); } - void onDragFinish(float x, float y, float velX, float velY) { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "onDragFinish"); - } + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + boolean dispatched = super.dispatchTouchEvent(ev); - if (mIsExpanded || mIsExpansionAnimating) { - return; + // 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); } - 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(); - } + mIsGestureInProgress = + ev.getAction() != MotionEvent.ACTION_UP + && ev.getAction() != MotionEvent.ACTION_CANCEL; - void onFlyoutDragStart() { - mFlyout.removeCallbacks(mHideFlyout); + return dispatched; } - 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,59 +1629,27 @@ public class BubbleStackView extends FrameLayout { 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(); + /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */ + private boolean passEventToMagnetizedObject(MotionEvent event) { + return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event); } /** - * Called when the first touch event of a gesture (stack drag, bubble drag, flyout drag, etc.) - * is received. + * Dismisses the magnetized object - either an individual bubble, if we're expanded, or the + * stack, if we're collapsed. */ - void onGestureStart() { - mIsGestureInProgress = true; - } - - /** Called when a gesture is completed or cancelled. */ - void onGestureFinished() { - mIsGestureInProgress = false; - + private void dismissMagnetizedObject() { if (mIsExpanded) { - mExpandedAnimationController.onGestureFinished(); - } - } + final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject(); + final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView); - /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */ - boolean passEventToMagnetizedObject(MotionEvent event) { - return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event); + 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. */ @@ -1624,7 +1683,7 @@ public class BubbleStackView extends FrameLayout { } /** Animates in the dismiss target. */ - private void springInDismissTarget() { + private void springInDismissTargetMaybe() { if (mShowingDismiss) { return; } @@ -1827,13 +1886,6 @@ public class BubbleStackView extends FrameLayout { 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 132c45fab3d2..000000000000 --- 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 a0b49384d49f..d974adc34ee0 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 @@ public class ExpandedAnimationController 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 @@ public class ExpandedAnimationController } }; 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 c292769f1066..b1bbafc1ed8f 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 @@ public class PhysicsAnimationLayout extends FrameLayout { 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 d33cd94004fd..8fd840e6cf58 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 @@ data class RenderInfo( 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 c16dce12041d..3f88f252bfe7 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 @@ public class DozeDockHandler implements DozeMachine.Part { 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 @@ public class DozeDockHandler implements DozeMachine.Part { @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 @@ public class DozeDockHandler implements DozeMachine.Part { 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 f7f9afdd2928..18bfd899a4e7 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 @@ public class DozeMachine { 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 000000000000..71bc7c20c026 --- /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 aa5ebaa22f2d..b7658a9f178d 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 @@ class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> { /** 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 @@ class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> { 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 1868536dca98..e24d29f1aedf 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 @@ public class PipBoundsHandler { /** * @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 @@ public class PipBoundsHandler { transformBoundsToAspectRatio(destinationBounds, aspectRatio, false /* useCurrentMinEdgeSize */); } - if (destinationBounds.equals(bounds)) { - return bounds; - } mAspectRatio = aspectRatio; - onResetReentryBoundsUnchecked(); mLastDestinationBounds.set(destinationBounds); return destinationBounds; } @@ -483,6 +484,7 @@ public class PipBoundsHandler { 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 d9872d7dcf17..d2994aebeb33 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 @@ public class PipTaskOrganizer extends TaskOrganizer { @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 @@ public class PipTaskOrganizer extends TaskOrganizer { animator.getTransitionDirection()); } }); - finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection()); } @Override @@ -247,7 +247,7 @@ public class PipTaskOrganizer extends TaskOrganizer { 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 @@ public class PipTaskOrganizer extends TaskOrganizer { 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 @@ public class PipTaskOrganizer extends TaskOrganizer { } 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 b7258117c48c..000000000000 --- 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 8397c65dbdb0..a8a5d896537f 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.statusbar.FlingAnimationUtils; 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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** 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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** * 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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, 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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, */ 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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, 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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } /** - * @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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, 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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * 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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, (target, values) -> updateAction.run()); } - startBoundsAnimator(dismissEndPoint.x /* toX */, dismissEndPoint.y /* toY */); + startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */); } /** @@ -408,6 +416,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, 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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * 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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } /** - * @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; - - // 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 float getHeight(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.height(); + } + + @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 7cc2759ad59a..bbb493966533 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_STAT 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.os.RemoteException; 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.pip.PipTaskOrganizer; 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 @@ import java.io.PrintWriter; 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 @@ public class PipTouchHandler { // 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 @@ public class PipTouchHandler { 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 @@ public class PipTouchHandler { } } + @SuppressLint("InflateParams") public PipTouchHandler(Context context, IActivityManager activityManager, IActivityTaskManager activityTaskManager, PipMenuActivityController menuController, InputConsumerController inputConsumerController, @@ -180,9 +219,9 @@ public class PipTouchHandler { 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 @@ public class PipTouchHandler { 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 @@ public class PipTouchHandler { 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 class PipTouchHandler { } public void onActivityPinned() { - cleanUpDismissTarget(); + createDismissTargetMaybe(); + mShowPipMenuOnAnimationEnd = true; mPipResizeGestureHandler.onActivityPinned(); mFloatingContentCoordinator.onContentAdded(mMotionHelper); @@ -264,6 +355,10 @@ public class PipTouchHandler { 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 @@ public class PipTouchHandler { } } + /** 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 @@ public class PipTouchHandler { 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 @@ public class PipTouchHandler { 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 @@ public class PipTouchHandler { mSavedSnapFraction = -1f; if (mEnableDismissDragToEdge) { - mHandler.removeCallbacks(mShowDismissAffordance); - mDismissViewController.showDismissTarget(); + if (mTargetViewContainer.getVisibility() != View.VISIBLE) { + mHandler.removeCallbacks(mShowTargetAction); + showDismissTargetMaybe(); + } } } @@ -644,10 +821,6 @@ public class PipTouchHandler { 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 @@ public class PipTouchHandler { @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 @@ public class PipTouchHandler { } 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 @@ public class PipTouchHandler { } /** - * 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 e3f65ef812fb..dc286c1c2de5 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 @@ public class PipTouchState { // Initialize the velocity tracker initOrResetVelocityTracker(); - addMovement(ev); + addMovementToVelocityTracker(ev); mActivePointerId = ev.getPointerId(0); if (DEBUG) { @@ -120,7 +120,7 @@ public class PipTouchState { } // 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 @@ public class PipTouchState { } // Update the velocity tracker - addMovement(ev); + addMovementToVelocityTracker(ev); int pointerIndex = ev.getActionIndex(); int pointerId = ev.getPointerId(pointerIndex); @@ -174,7 +174,7 @@ public class PipTouchState { } // Update the velocity tracker - addMovement(ev); + addMovementToVelocityTracker(ev); mVelocityTracker.computeCurrentVelocity(1000, mViewConfig.getScaledMaximumFlingVelocity()); mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); @@ -318,6 +318,20 @@ public class PipTouchState { 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 @@ public class PipTouchState { } } - 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 9c175bc2b756..8efeef1ffa0a 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 @@ import com.android.systemui.R; */ 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 52c8960d1ccf..99a01d3f6a7f 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 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); - mPipTaskOrganizer.registerPipTransitionCallback(this); try { WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener); @@ -334,6 +333,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio * 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 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio * 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 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio * 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 class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio public void suspendPipResizing(int reason) { if (DEBUG) Log.d(TAG, "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2)); + mSuspendPipResizingReason |= reason; } @@ -408,7 +420,11 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio * @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 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio if (DEBUG) Log.d(TAG, "resizePinnedStack() deferring" + " mSuspendPipResizingReason=" + mSuspendPipResizingReason + " mResumeResizePinnedStackRunnableState=" - + mResumeResizePinnedStackRunnableState); + + stateToName(mResumeResizePinnedStackRunnableState)); return; } mState = state; @@ -458,7 +474,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio * 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 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio private void onPipTransitionFinishedOrCanceled() { if (DEBUG) Log.d(TAG, "onPipTransitionFinishedOrCanceled()"); + if (getState() == STATE_PIP_MENU) { showPipMenu(); } @@ -753,4 +771,28 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio 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 f43f8e795fe6..c7e77ccfa488 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.app.Activity; 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 @@ import javax.inject.Inject; * 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 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { private boolean mRestorePipSizeWhenClose; private PipControlsViewController mPipControlsViewController; - @Inject public PipMenuActivity(TvPipComponent.Builder pipComponentBuilder, PipManager pipManager) { super(); @@ -57,6 +58,8 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override protected void onCreate(Bundle bundle) { + if (DEBUG) Log.d(TAG, "onCreate()"); + super.onCreate(bundle); if (!mPipManager.isPipShown()) { finish(); @@ -81,13 +84,18 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @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 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @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 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override protected void onDestroy() { + if (DEBUG) Log.d(TAG, "onDestroy()"); + super.onDestroy(); mPipManager.removeListener(this); mPipManager.resumePipResizing( @@ -117,29 +131,41 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @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 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @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 d2da2628276a..729f934937da 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.AnimatorListenerAdapter 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 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 @@ class NotificationShadeDepthController @Inject constructor( } /** + * 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 @@ class NotificationShadeDepthController @Inject constructor( 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 @@ class NotificationShadeDepthController @Inject constructor( brightnessMirrorSpring.finishIfRunning() } } + + override fun onDozeAmountChanged(linear: Float, eased: Float) { + wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased) + } } init { @@ -209,6 +231,10 @@ class NotificationShadeDepthController @Inject constructor( 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 @@ class NotificationShadeDepthController @Inject constructor( 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/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index 1696f0715865..53ec57090321 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -103,12 +103,20 @@ class ConversationNotificationManager @Inject constructor( override fun onEntryInflated(entry: NotificationEntry) { if (!entry.ranking.isConversation) return fun updateCount(isExpanded: Boolean) { - if (isExpanded && !notifPanelCollapsed) { + if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded())) { resetCount(entry.key) entry.row?.let(::resetBadgeUi) } } - entry.row?.setOnExpansionChangedListener(::updateCount) + entry.row?.setOnExpansionChangedListener { isExpanded -> + if (entry.row?.isShown == true && isExpanded) { + entry.row.performOnIntrinsicHeightReached { + updateCount(isExpanded) + } + } else { + updateCount(isExpanded) + } + } updateCount(entry.row?.isExpanded == true) } @@ -169,7 +177,8 @@ class ConversationNotificationManager @Inject constructor( private fun resetBadgeUi(row: ExpandableNotificationRow): Unit = (row.layouts?.asSequence() ?: emptySequence()) - .mapNotNull { layout -> layout.contractedChild as? ConversationLayout } + .flatMap { layout -> layout.allViews.asSequence()} + .mapNotNull { view -> view as? ConversationLayout } .forEach { convoLayout -> convoLayout.setUnreadCount(0) } private data class ConversationState(val unreadCount: Int, val notification: Notification) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index dd7be2775209..c1ba26d034ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -601,6 +601,13 @@ public final class NotificationEntry extends ListEntry { return row != null && row.isPinned(); } + /** + * Is this entry pinned and was expanded while doing so + */ + public boolean isPinnedAndExpanded() { + return row != null && row.isPinnedAndExpanded(); + } + public void setRowPinned(boolean pinned) { if (row != null) row.setPinned(pinned); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 2917346153d2..5c578dfc5744 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -284,6 +284,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (isPinned()) { nowExpanded = !mExpandedWhenPinned; mExpandedWhenPinned = nowExpanded; + // Also notify any expansion changed listeners. This is necessary since the + // expansion doesn't actually change (it's already system expanded) but it + // changes visually + if (mExpansionChangedListener != null) { + mExpansionChangedListener.onExpansionChanged(nowExpanded); + } } else { nowExpanded = !isExpanded(); setUserExpanded(nowExpanded); @@ -326,6 +332,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationInlineImageResolver mImageResolver; private NotificationMediaManager mMediaManager; @Nullable private OnExpansionChangedListener mExpansionChangedListener; + @Nullable private Runnable mOnIntrinsicHeightReachedRunnable; private SystemNotificationAsyncTask mSystemNotificationAsyncTask = new SystemNotificationAsyncTask(); @@ -358,6 +365,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return Arrays.copyOf(mLayouts, mLayouts.length); } + /** + * Is this entry pinned and was expanded while doing so + */ + public boolean isPinnedAndExpanded() { + if (!isPinned()) { + return false; + } + return mExpandedWhenPinned; + } + @Override public boolean isGroupExpansionChanging() { if (isChildInGroup()) { @@ -2690,6 +2707,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.onParentHeightUpdate(); } + handleIntrinsicHeightReached(); } @Override @@ -2907,6 +2925,24 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mExpansionChangedListener = listener; } + /** + * Perform an action when the notification height has reached its intrinsic height. + * + * @param runnable the runnable to run + */ + public void performOnIntrinsicHeightReached(@Nullable Runnable runnable) { + mOnIntrinsicHeightReachedRunnable = runnable; + handleIntrinsicHeightReached(); + } + + private void handleIntrinsicHeightReached() { + if (mOnIntrinsicHeightReachedRunnable != null + && getActualHeight() == getIntrinsicHeight()) { + mOnIntrinsicHeightReachedRunnable.run(); + mOnIntrinsicHeightReachedRunnable = null; + } + } + @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index b18bf01ea91f..3c3f1b21fb3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; @@ -988,6 +989,14 @@ public class NotificationContentView extends FrameLayout { } } + public @NonNull View[] getAllViews() { + return new View[] { + mContractedChild, + mHeadsUpChild, + mExpandedChild, + mSingleLineView }; + } + public NotificationViewWrapper getVisibleWrapper(int visibleType) { switch (visibleType) { case VISIBLE_TYPE_EXPANDED: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index f06cfec9480a..82e02b47974c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -203,7 +203,9 @@ public class KeyguardBouncer { Log.wtf(TAG, "onFullyShown when view was null"); } else { mKeyguardView.onResume(); - mRoot.announceForAccessibility(mKeyguardView.getAccessibilityTitleForCurrentMode()); + if (mRoot != null) { + mRoot.announceForAccessibility(mKeyguardView.getAccessibilityTitleForCurrentMode()); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index d70484e9cf41..a19d35ac4e81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -50,19 +50,22 @@ public class LockIcon extends KeyguardAffordanceView { static final int STATE_BIOMETRICS_ERROR = 3; private float mDozeAmount; private int mIconColor; - private StateProvider mStateProvider; private int mOldState; + private int mState; private boolean mPulsing; private boolean mDozing; private boolean mKeyguardJustShown; + private boolean mPredrawRegistered; private final SparseArray<Drawable> mDrawableCache = new SparseArray<>(); private final OnPreDrawListener mOnPreDrawListener = new OnPreDrawListener() { @Override public boolean onPreDraw() { getViewTreeObserver().removeOnPreDrawListener(this); + mPredrawRegistered = false; - int newState = mStateProvider.getState(); + int newState = mState; + mOldState = mState; Drawable icon = getIcon(newState); setImageDrawable(icon, false); @@ -80,7 +83,7 @@ public class LockIcon extends KeyguardAffordanceView { @Override public void onAnimationEnd(Drawable drawable) { if (getDrawable() == animation - && newState == mStateProvider.getState() + && newState == mState && newState == STATE_SCANNING_FACE) { animation.start(); } else { @@ -100,10 +103,6 @@ public class LockIcon extends KeyguardAffordanceView { super(context, attrs); } - void setStateProvider(StateProvider stateProvider) { - mStateProvider = stateProvider; - } - @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -135,13 +134,16 @@ public class LockIcon extends KeyguardAffordanceView { return false; } - void update(int oldState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { - mOldState = oldState; + void update(int newState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { + mState = newState; mPulsing = pulsing; mDozing = dozing; mKeyguardJustShown = keyguardJustShown; - getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); + if (!mPredrawRegistered) { + mPredrawRegistered = true; + getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); + } } void setDozeAmount(float dozeAmount) { @@ -175,7 +177,7 @@ public class LockIcon extends KeyguardAffordanceView { return mDrawableCache.get(iconRes); } - static int getIconForState(int state) { + private static int getIconForState(int state) { int iconRes; switch (state) { case STATE_LOCKED: @@ -196,7 +198,7 @@ public class LockIcon extends KeyguardAffordanceView { return iconRes; } - static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing, + private static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { // Never animate when screen is off @@ -260,9 +262,4 @@ public class LockIcon extends KeyguardAffordanceView { } return LOCK_ANIM_RES_IDS[0][lockAnimIndex]; } - - interface StateProvider { - int getState(); - } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java index f7c861b84a68..a633e1979bad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java @@ -352,7 +352,6 @@ public class LockscreenLockIconController { mLockIcon.setOnClickListener(this::handleClick); mLockIcon.setOnLongClickListener(this::handleLongClick); mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate); - mLockIcon.setStateProvider(this::getState); if (mLockIcon.isAttachedToWindow()) { mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon); @@ -462,7 +461,7 @@ public class LockscreenLockIconController { shouldUpdate = false; } if (shouldUpdate && mLockIcon != null) { - mLockIcon.update(mLastState, mPulsing, mDozing, mKeyguardJustShown); + mLockIcon.update(state, mPulsing, mDozing, mKeyguardJustShown); } mLastState = state; mKeyguardJustShown = false; 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 e1e679f06eef..462b42a44c17 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.ref.WeakReference; 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 @@ public class NotificationShadeWindowController implements Callback, Dumpable, 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 @@ public class NotificationShadeWindowController implements Callback, Dumpable, 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 class NotificationShadeWindowController implements Callback, Dumpable, 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 000000000000..d65b285adb0c --- /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 812a1e4bc121..f27bdbfbeda0 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 @@ abstract class MagnetizedObject<T : Any>( 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 @@ abstract class MagnetizedObject<T : Any>( .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 @@ abstract class MagnetizedObject<T : Any>( private val tempLoc = IntArray(2) fun updateLocationOnScreen() { - 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) + 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) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index bae5bb41aa5a..c22b718fa50f 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -272,13 +272,18 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } mAnimation.cancel(); } - mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; final float defaultY = mImeSourceControl.getSurfacePosition().y; final float x = mImeSourceControl.getSurfacePosition().x; final float hiddenY = defaultY + imeSource.getFrame().height(); final float shownY = defaultY; final float startY = show ? hiddenY : shownY; final float endY = show ? shownY : hiddenY; + if (mAnimationDirection == DIRECTION_NONE && mImeShowing && show) { + // IME is already showing, so set seek to end + seekValue = shownY; + seek = true; + } + mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; mImeShowing = show; mAnimation = ValueAnimator.ofFloat(startY, endY); mAnimation.setDuration( diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java index cf1299fc66e0..ce032c94deab 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 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { @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 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { 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 072bc446fd21..4bcf917fa95d 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 androidx.test.filters.SmallTest 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.Before 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 @@ import org.mockito.Mockito.`when` as whenever 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 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { @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 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { 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 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { @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 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { } @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 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { 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 475023e2506d..5227aaf01249 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 @@ public class ImageWallpaperTest extends SysuiTestCase { LOW_BMP_HEIGHT /* bmpHeight */, LOW_BMP_WIDTH /* surfaceWidth */, LOW_BMP_HEIGHT /* surfaceHeight */, - true /* assertion */); + false /* assertion */); } @Test @@ -172,7 +172,7 @@ public class ImageWallpaperTest extends SysuiTestCase { 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 037f04ec1d7c..e472de349466 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 @@ public class BubbleControllerTest extends SysuiTestCase { 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 @@ public class BubbleControllerTest extends SysuiTestCase { // 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 @@ public class BubbleControllerTest extends SysuiTestCase { // Expand BubbleStackView stackView = mBubbleController.getStackView(); - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); @@ -448,7 +448,7 @@ public class BubbleControllerTest extends SysuiTestCase { 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 @@ public class BubbleControllerTest extends SysuiTestCase { 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 @@ public class BubbleControllerTest extends SysuiTestCase { 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 @@ public class BubbleControllerTest extends SysuiTestCase { // Expand BubbleStackView stackView = mBubbleController.getStackView(); - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mSysUiStateBubblesExpanded); @@ -726,7 +726,7 @@ public class BubbleControllerTest extends SysuiTestCase { 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 545de210d5b5..5f4f2ef04c1d 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // Expand BubbleStackView stackView = mBubbleController.getStackView(); - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); @@ -385,7 +385,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // 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 class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { 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 c9bb4016c7bf..9985d21e8515 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.any; 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 @@ public class DozeDockHandlerTest extends SysuiTestCase { 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 @@ public class DozeDockHandlerTest extends SysuiTestCase { 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 c483314918fc..1f07f46bf764 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 @@ public class DozeMachineTest extends SysuiTestCase { } @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 260f52070a70..d407b8a1e449 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 @@ public class SeekBarObserverTest : SysuiTestCase() { 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 0bf0f04d2d43..425bf88ebec0 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 @@ public class PipBoundsHandlerTest extends SysuiTestCase { 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 @@ public class PipBoundsHandlerTest extends SysuiTestCase { }; 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 @@ public class PipBoundsHandlerTest extends SysuiTestCase { }; 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 @@ public class PipBoundsHandlerTest extends SysuiTestCase { 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 @@ public class PipBoundsHandlerTest extends SysuiTestCase { 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 @@ public class PipBoundsHandlerTest extends SysuiTestCase { 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 @@ public class PipBoundsHandlerTest extends SysuiTestCase { } @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 @@ public class PipBoundsHandlerTest extends SysuiTestCase { + " 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 f1672b1c644d..f6b7b74d4bfc 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 @@ class MagnetizedObjectTest : SysuiTestCase() { 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 @@ class MagnetizedObjectTest : SysuiTestCase() { `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 1a1c30d1d5f9..620261b375d2 100644 --- a/packages/Tethering/tests/integration/Android.bp +++ b/packages/Tethering/tests/integration/Android.bp @@ -39,4 +39,9 @@ android_test { "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 dbd68ef77cb7..b02bb23f9807 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 @@ public class EthernetTetheringTest { 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 530bc0788a78..4ff1d3777f25 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 b3a30abca6f1..6695eed0ff65 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 com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess 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.MockitoSession; 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 @@ public final class EntitlementManagerTest { @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 @@ public final class EntitlementManagerTest { @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 @@ public final class EntitlementManagerTest { @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 @@ public final class EntitlementManagerTest { @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 @@ public final class EntitlementManagerTest { @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 @@ public final class EntitlementManagerTest { @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 @@ public final class EntitlementManagerTest { @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 @@ public final class EntitlementManagerTest { @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 000000000000..355ece9a44a1 --- /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 000000000000..d9d3e73eb4e3 --- /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 a59c6fd9e193..3a580dd8e5bd 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.networkstack.tethering.R; 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 @@ public class TetheringTest { 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 93bffe9f54e4..52a82dd2a156 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.FileUtils; 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 @@ public class WallpaperBackupAgent extends BackupAgent { // 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 @@ public class WallpaperBackupAgent extends BackupAgent { } } + @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 @@ public class WallpaperBackupAgent extends BackupAgent { 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 @@ public class WallpaperBackupAgent extends BackupAgent { 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 index 255fdef36cd7..4367075abc74 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.anyString; 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 @@ import java.util.List; 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 @@ public class WallpaperBackupAgentTest { @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 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent(mTemporaryFolder.getRoot()); mWallpaperBackupAgent.attach(mContext); mWallpaperBackupAgent.onCreate(); + + mWallpaperComponent = new ComponentName(TEST_WALLPAPER_PACKAGE, ""); } @Test @@ -130,6 +140,69 @@ public class WallpaperBackupAgentTest { 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 @@ public class WallpaperBackupAgentTest { 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 class WallpaperBackupAgentTest { 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 2100c1a8be44..7230b00f87ad 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub && 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 ff59c24a7ca2..20a11bd9acd3 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 @@ final class AccessibilityShellCommand extends ShellCommand { 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 000000000000..3612e093c8bd --- /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 000000000000..ca230b6936ff --- /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 e2d511212a71..000000000000 --- 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 12905696ff98..4ecffd8d29b0 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState inlineSuggestionsRequest.get(), response, filterText, mCurrentViewId, this, () -> { synchronized (mLock) { - mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId); + mInlineSessionController.hideInlineSuggestionsUiLocked( + mCurrentViewId); } }, remoteRenderService); if (inlineSuggestionsResponse == null) { @@ -2809,7 +2811,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return false; } - return mInlineSuggestionSession.onInlineSuggestionsResponse(mCurrentViewId, + return mInlineSessionController.onInlineSuggestionsResponseLocked(mCurrentViewId, inlineSuggestionsResponse); } @@ -3106,8 +3108,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState && (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 192ea72224b1..3c0d880916ee 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -119,8 +119,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * 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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * 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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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 a9c3079adcc0..7f25de6b3470 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -1836,12 +1836,13 @@ public class LocationManagerService extends ILocationManager.Stub { @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 @@ public class LocationManagerService extends ILocationManager.Stub { @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 @@ public class LocationManagerService extends ILocationManager.Stub { } } - 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 067147703b0c..9aefc8dc78ec 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -4468,9 +4468,8 @@ class StorageManagerService extends IStorageManager.Stub 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 061ff42de60b..58972a5346c7 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -123,6 +123,8 @@ public class Watchdog extends Thread { "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 bc455338fc52..8f8990fdaae7 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 @@ class AnrHelper { 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 dbcb3da3e2f4..2d6ef81faf1c 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 @@ public final class OomAdjuster { 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 595275d20154..786e9cf66bfa 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.os.storage.StorageManagerInternal; 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.BitSet; 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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 (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)); - } + if (inode == 0) { + Slog.w(TAG, packageName + " inode == 0 (b/152760674)"); + return null; } + result.put(packageName, Pair.create(volumeUuid, inode)); } return result; @@ -2160,35 +2157,56 @@ public final class ProcessList { app.setHasForegroundActivities(true); } - StorageManagerInternal storageManagerInternal = LocalServices.getService( - StorageManagerInternal.class); 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); - 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; - } + // 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); + 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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 4431abe43136..808f8c21cc8d 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 @@ public abstract class BiometricServiceBase extends SystemService 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 962f337a8b3f..9a910bf5e859 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 @@ public final class ContentService extends IContentService.Stub { // 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 e42be02d4a9e..6a685bf187ad 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 @@ class AppSaturationController { * 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 @@ class AppSaturationController { 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 @@ class AppSaturationController { 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 @@ class AppSaturationController { } } return updated; - } private void clearExpiredReferences() { @@ -206,7 +223,7 @@ class AppSaturationController { } 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 2dc2cf0d8e90..95a98f1e9494 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.Context; 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.R; 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 @@ public final class ColorDisplayService extends SystemService { 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 @@ public final class ColorDisplayService extends SystemService { 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 9c279166ac8a..cb64c50bf11d 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 @@ public class AppOpsHelper { callerIdentity.uid, callerIdentity.packageName, callerIdentity.featureId, - null) == AppOpsManager.MODE_ALLOWED; + callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED; } finally { Binder.restoreCallingIdentity(identity); } @@ -210,7 +210,7 @@ public class AppOpsHelper { callerIdentity.packageName, false, callerIdentity.featureId, - null) == AppOpsManager.MODE_ALLOWED; + callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED; } finally { Binder.restoreCallingIdentity(identity); } @@ -245,7 +245,7 @@ public class AppOpsHelper { 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 b84fd13415fc..8d508bb6256d 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 final class CallerIdentity { */ 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 final class CallerIdentity { */ 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 @@ public final class CallerIdentity { /** 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 @@ public final class CallerIdentity { @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 @@ public final class CallerIdentity { 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 ad3c8a61182f..d8acf0e331af 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 @@ public class GnssLocationProvider extends AbstractLocationProvider implements 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 52e9d7c67605..c3413e8d2934 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { } } - 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { 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 @@ class MediaRouter2ServiceImpl { if (service == null) { return; } - notifySessionInfoChangedToRouters(getRouters(), sessionInfo); + notifySessionInfoChangedToRouters(getRouters(true), sessionInfo); + notifySessionInfoChangedToRouters(getRouters(false), + mSystemProvider.getDefaultSessionInfo()); return; } @@ -1569,7 +1625,7 @@ class MediaRouter2ServiceImpl { } } - 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 @@ class MediaRouter2ServiceImpl { 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 5b16d686e04c..6e2feeb15e21 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { // 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } }); updateSessionInfosIfNeeded(); + mContext.registerReceiver(new VolumeChangeReceiver(), new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION)); @@ -156,6 +158,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { @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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { */ 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } 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 000000000000..0bdf3f22ee9a --- /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 ba7583fe7f7a..dab4bfd4df5a 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 com.android.server.pm.dex.DexoptOptions; 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 @@ public class BackgroundDexOptService extends JobService { 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 @@ public class BackgroundDexOptService extends JobService { } } notifyPinService(updatedPackages); + notifyPackagesUpdated(updatedPackages); // Ran to completion, so we abandon our timeslice and do not reschedule. jobFinished(jobParams, /* reschedule */ false); } @@ -391,6 +395,7 @@ public class BackgroundDexOptService extends JobService { } finally { // Always let the pinner service know about changes. notifyPinService(updatedPackages); + notifyPackagesUpdated(updatedPackages); } } @@ -642,6 +647,32 @@ public class BackgroundDexOptService extends JobService { } } + 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 f37af3aef657..9fb468e8db6e 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 @@ public class Installer extends SystemService { } /** + * 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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 1aaaab3956ef..f693555d9761 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9684,6 +9684,20 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void notifyDexLoad(String loadingPackageName, Map<String, String> classLoaderContextMap, String loaderIsa) { + if (PLATFORM_PACKAGE_NAME.equals(loadingPackageName) + && Binder.getCallingUid() != Process.SYSTEM_UID) { + Slog.w(TAG, "Non System Server process reporting dex loads as system server. uid=" + + Binder.getCallingUid()); + // Do not record dex loads from processes pretending to be system server. + // Only the system server should be assigned the package "android", so reject calls + // that don't satisfy the constraint. + // + // notifyDexLoad is a PM API callable from the app process. So in theory, apps could + // craft calls to this API and pretend to be system server. Doing so poses no particular + // danger for dex load reporting or later dexopt, however it is a sensible check to do + // in order to verify the expectations. + return; + } int userId = UserHandle.getCallingUserId(); ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId); if (ai == null) { @@ -11310,7 +11324,7 @@ public class PackageManagerService extends IPackageManager.Stub Slog.i(TAG, "Using ABIS and native lib paths from settings : " + parsedPackage.getPackageName() + " " + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage) - + ", " + + ", " + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage)); } } diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index e8765ad973f3..ebdf85691e58 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -17,13 +17,17 @@ package com.android.server.pm.dex; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; +import static java.util.function.Function.identity; + import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackagePartitions; import android.os.FileUtils; import android.os.RemoteException; import android.os.SystemProperties; @@ -67,13 +71,12 @@ import java.util.zip.ZipEntry; */ public class DexManager { private static final String TAG = "DexManager"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob"; private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST = "pm.dexopt.priv-apps-oob-list"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private final Context mContext; // Maps package name to code locations. @@ -178,12 +181,14 @@ public class DexManager { boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; - if (primaryOrSplit && !isUsedByOtherApps) { + if (primaryOrSplit && !isUsedByOtherApps + && !PLATFORM_PACKAGE_NAME.equals(searchResult.mOwningPackageName)) { // If the dex file is the primary apk (or a split) and not isUsedByOtherApps // do not record it. This case does not bring any new usable information // and can be safely skipped. // Note this is just an optimization that makes things easier to read in the // package-dex-use file since we don't need to pollute it with redundant info. + // However, we always record system server packages. continue; } @@ -217,6 +222,23 @@ public class DexManager { } /** + * Check if the dexPath belongs to system server. + * System server can load code from different location, so we cast a wide-net here, and + * assume that if the paths is on any of the registered system partitions then it can be loaded + * by system server. + */ + private boolean isSystemServerDexPathSupportedForOdex(String dexPath) { + ArrayList<PackagePartitions.SystemPartition> partitions = + PackagePartitions.getOrderedPartitions(identity()); + for (int i = 0; i < partitions.size(); i++) { + if (partitions.get(i).containsPath(dexPath)) { + return true; + } + } + return false; + } + + /** * Read the dex usage from disk and populate the code cache locations. * @param existingPackages a map containing information about what packages * are available to what users. Only packages in this list will be @@ -607,12 +629,6 @@ public class DexManager { */ private DexSearchResult getDexPackage( ApplicationInfo loadingAppInfo, String dexPath, int userId) { - // Ignore framework code. - // TODO(calin): is there a better way to detect it? - if (dexPath.startsWith("/system/framework/")) { - return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); - } - // First, check if the package which loads the dex file actually owns it. // Most of the time this will be true and we can return early. PackageCodeLocations loadingPackageCodeLocations = @@ -635,6 +651,28 @@ public class DexManager { } } + // We could not find the owning package amongst regular apps. + // If the loading package is system server, see if the dex file resides + // on any of the potentially system server owning location and if so, + // assuming system server ownership. + // + // Note: We don't have any way to detect which code paths are actually + // owned by system server. We can only assume that such paths are on + // system partitions. + if (PLATFORM_PACKAGE_NAME.equals(loadingAppInfo.packageName)) { + if (isSystemServerDexPathSupportedForOdex(dexPath)) { + // We record system server dex files as secondary dex files. + // The reason is that we only record the class loader context for secondary dex + // files and we expect that all primary apks are loaded with an empty class loader. + // System server dex files may be loaded in non-empty class loader so we need to + // keep track of their context. + return new DexSearchResult(PLATFORM_PACKAGE_NAME, DEX_SEARCH_FOUND_SECONDARY); + } else { + Slog.wtf(TAG, "System server loads dex files outside paths supported for odex: " + + dexPath); + } + } + if (DEBUG) { // TODO(calin): Consider checking for /data/data symlink. // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps diff --git a/services/core/java/com/android/server/pm/dex/SystemServerDexLoadReporter.java b/services/core/java/com/android/server/pm/dex/SystemServerDexLoadReporter.java new file mode 100644 index 000000000000..807c82d887e3 --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/SystemServerDexLoadReporter.java @@ -0,0 +1,83 @@ +/* + * 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.pm.dex; + +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; + +import android.content.pm.IPackageManager; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; + +import dalvik.system.BaseDexClassLoader; +import dalvik.system.VMRuntime; + +import java.util.Map; + +/** + * Reports dex file use to the package manager on behalf of system server. + */ +public class SystemServerDexLoadReporter implements BaseDexClassLoader.Reporter { + private static final String TAG = "SystemServerDexLoadReporter"; + + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final IPackageManager mPackageManager; + + private SystemServerDexLoadReporter(IPackageManager pm) { + mPackageManager = pm; + } + + @Override + public void report(Map<String, String> classLoaderContextMap) { + if (DEBUG) { + Slog.i(TAG, "Reporting " + classLoaderContextMap); + } + if (classLoaderContextMap.isEmpty()) { + Slog.wtf(TAG, "Bad call to DexLoadReporter: empty classLoaderContextMap"); + return; + } + + try { + mPackageManager.notifyDexLoad( + PLATFORM_PACKAGE_NAME, + classLoaderContextMap, + VMRuntime.getRuntime().vmInstructionSet()); + } catch (RemoteException ignored) { + // We're in system server, it can't happen. + } + } + + /** + * Configures system server dex file reporting. + * <p>The method will install a reporter in the BaseDexClassLoader and also + * force the reporting of any dex files already loaded by the system server. + */ + public static void configureSystemServerDexReporter(IPackageManager pm) { + Slog.i(TAG, "Configuring system server dex reporter"); + + SystemServerDexLoadReporter reporter = new SystemServerDexLoadReporter(pm); + BaseDexClassLoader.setReporter(reporter); + ClassLoader currrentClassLoader = reporter.getClass().getClassLoader(); + if (currrentClassLoader instanceof BaseDexClassLoader) { + ((BaseDexClassLoader) currrentClassLoader).reportClassLoaderChain(); + } else { + Slog.wtf(TAG, "System server class loader is not a BaseDexClassLoader. type=" + + currrentClassLoader.getClass().getName()); + } + } +} 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 a726d39b8595..e3faffa0699b 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 final class DefaultPermissionGrantPolicy { 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 161f30449a52..27288d852fb2 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.Nullable; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.AppOpsManagerInternal; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -47,6 +48,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManagerInternal; import android.permission.PermissionControllerManager; +import android.provider.Settings; import android.provider.Telephony; import android.telecom.TelecomManager; import android.util.ArrayMap; @@ -70,7 +72,9 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; /** @@ -180,8 +184,6 @@ public final class PermissionPolicyService extends SystemService { intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); intentFilter.addDataScheme("package"); - - /* TODO ntmyren: enable receiver when test flakes are fixed getContext().registerReceiverAsUser(new BroadcastReceiver() { final List<Integer> mUserSetupUids = new ArrayList<>(200); final Map<UserHandle, PermissionControllerManager> mPermControllerManagers = @@ -232,7 +234,6 @@ public final class PermissionPolicyService extends SystemService { 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 5f871ad4f9e4..83e99b008b68 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 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { 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 5ce63de87ee9..c4d034207449 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -42,7 +42,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.activityTypeToString; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; @@ -109,7 +108,6 @@ import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS; import static android.view.WindowManager.TRANSIT_TASK_CLOSE; import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_UNSET; -import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; @@ -5812,18 +5810,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @VisibleForTesting - boolean shouldAnimate(int transit) { - if (task != null && !task.shouldAnimate()) { - return false; - } - final boolean isSplitScreenPrimary = - getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; - final boolean allowSplitScreenPrimaryAnimation = transit != TRANSIT_WALLPAPER_OPEN; - - // We animate always if it's not split screen primary, and only some special cases in split - // screen primary because it causes issues with stack clipping when we run an un-minimize - // animation at the same time. - return !isSplitScreenPrimary || allowSplitScreenPrimaryAnimation; + boolean shouldAnimate() { + return task == null || task.shouldAnimate(); } /** @@ -6496,11 +6484,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ -6569,7 +6564,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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) { @@ -7666,8 +7661,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 f5eba96a96d1..78d2afc64f96 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -20,7 +20,6 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP; @@ -685,9 +684,7 @@ class ActivityStack extends Task { return; } - setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, - false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, - false /* creating */); + setWindowingMode(windowingMode, false /* creating */); } /** @@ -709,27 +706,22 @@ class ActivityStack extends Task { * @param preferredWindowingMode the preferred windowing mode. This may not be honored depending * on the state of things. For example, WINDOWING_MODE_UNDEFINED will resolve to the * previous non-transient mode if this stack is currently in a transient mode. - * @param animate Can be used to prevent animation. - * @param showRecents Controls whether recents is shown on the other side of a split while - * entering split mode. - * @param enteringSplitScreenMode {@code true} if entering split mode. - * @param deferEnsuringVisibility Whether visibility updates are deferred. This is set when - * many operations (which can effect visibility) are being performed in bulk. * @param creating {@code true} if this is being run during ActivityStack construction. */ - void setWindowingMode(int preferredWindowingMode, boolean animate, boolean showRecents, - boolean enteringSplitScreenMode, boolean deferEnsuringVisibility, boolean creating) { + void setWindowingMode(int preferredWindowingMode, boolean creating) { mWmService.inSurfaceTransaction(() -> setWindowingModeInSurfaceTransaction( - preferredWindowingMode, animate, showRecents, enteringSplitScreenMode, - deferEnsuringVisibility, creating)); + preferredWindowingMode, creating)); } - private void setWindowingModeInSurfaceTransaction(int preferredWindowingMode, boolean animate, - boolean showRecents, boolean enteringSplitScreenMode, boolean deferEnsuringVisibility, + 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 @@ -753,12 +745,9 @@ class ActivityStack extends Task { final boolean alreadyInSplitScreenMode = taskDisplayArea.isSplitScreenModeActivated(); - // Don't send non-resizeable notifications if the windowing mode changed was a side effect - // of us entering split-screen mode. - final boolean sendNonResizeableNotification = !enteringSplitScreenMode; // Take any required action due to us not supporting the preferred windowing mode. if (alreadyInSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN - && sendNonResizeableNotification && isActivityTypeStandardOrUndefined()) { + && isActivityTypeStandardOrUndefined()) { final boolean preferredSplitScreen = preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; @@ -794,7 +783,7 @@ class ActivityStack extends Task { if (currentMode == WINDOWING_MODE_PINNED) { mAtmService.getTaskChangeNotificationController().notifyActivityUnpinned(); } - if (sendNonResizeableNotification && likelyResolvedMode != WINDOWING_MODE_FULLSCREEN + if (likelyResolvedMode != WINDOWING_MODE_FULLSCREEN && topActivity != null && !topActivity.noDisplay && topActivity.isNonResizableOrForcedResizable(likelyResolvedMode)) { // Inform the user that they are starting an app that may not work correctly in @@ -806,7 +795,7 @@ class ActivityStack extends Task { mAtmService.deferWindowLayout(); try { - if (!animate && topActivity != null) { + if (topActivity != null) { mStackSupervisor.mNoAnimActivities.add(topActivity); } super.setWindowingMode(windowingMode); @@ -845,36 +834,11 @@ class ActivityStack extends Task { false /*preserveWindows*/, true /*deferResume*/); } } finally { - if (showRecents && !alreadyInSplitScreenMode && isOnHomeDisplay() - && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // Make sure recents stack exist when creating a dock stack as it normally needs to - // be on the other side of the docked stack and we make visibility decisions based - // on that. - // TODO: This is only here to help out with the case where recents stack doesn't - // exist yet. For that case the initial size of the split-screen stack will be the - // the one where the home stack is visible since recents isn't visible yet, but the - // divider will be off. I think we should just make the initial bounds that of home - // so that the divider matches and remove this logic. - // TODO: This is currently only called when entering split-screen while in another - // task, and from the tests - // TODO (b/78247419): Fix the rotation animation from fullscreen to minimized mode - final boolean isRecentsComponentHome = - mAtmService.getRecentTasks().isRecentsComponentHomeActivity(mCurrentUser); - final ActivityStack recentStack = taskDisplayArea.getOrCreateStack( - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, - isRecentsComponentHome ? ACTIVITY_TYPE_HOME : ACTIVITY_TYPE_RECENTS, - true /* onTop */); - recentStack.moveToFront("setWindowingMode"); - // If task moved to docked stack - show recents if needed. - mWmService.showRecentApps(); - } mAtmService.continueWindowLayout(); } - if (!deferEnsuringVisibility) { - mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); - mRootWindowContainer.resumeFocusedStacksTopActivities(); - } + mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); + mRootWindowContainer.resumeFocusedStacksTopActivities(); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 02601ff4b6e3..0470ffa6cdca 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -80,8 +80,6 @@ import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE; import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; import static com.android.server.wm.Task.LOCK_TASK_AUTH_WHITELISTED; import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT; -import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE; -import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -140,7 +138,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; import com.android.internal.os.TransferPipe; -import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledConsumer; import com.android.internal.util.function.pooled.PooledLambda; @@ -382,71 +379,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { private boolean mInitialized; - private final MoveTaskToFullscreenHelper mMoveTaskToFullscreenHelper = - new MoveTaskToFullscreenHelper(); - private class MoveTaskToFullscreenHelper { - private TaskDisplayArea mToDisplayArea; - private boolean mOnTop; - private Task mTopTask; - private boolean mSchedulePictureInPictureModeChange; - - void process(ActivityStack fromStack, TaskDisplayArea toDisplayArea, boolean onTop, - boolean schedulePictureInPictureModeChange) { - mSchedulePictureInPictureModeChange = schedulePictureInPictureModeChange; - mToDisplayArea = toDisplayArea; - mOnTop = onTop; - mTopTask = fromStack.getTopMostTask(); - - final PooledConsumer c = PooledLambda.obtainConsumer( - MoveTaskToFullscreenHelper::processLeafTask, this, PooledLambda.__(Task.class)); - fromStack.forAllLeafTasks(c, false /* traverseTopToBottom */); - c.recycle(); - mToDisplayArea = null; - mTopTask = null; - } - - private void processLeafTask(Task task) { - // This is a one level task that we don't need to create stack for reparenting to. - if (task.isRootTask() && DisplayContent.alwaysCreateStack(WINDOWING_MODE_FULLSCREEN, - task.getActivityType())) { - final ActivityStack stack = (ActivityStack) task; - stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - if (mToDisplayArea.getDisplayId() != stack.getDisplayId()) { - stack.reparent(mToDisplayArea, mOnTop); - } else if (mOnTop) { - mToDisplayArea.positionStackAtTop(stack, false /* includingParents */); - } else { - mToDisplayArea.positionStackAtBottom(stack); - } - return; - } - - final ActivityStack toStack = mToDisplayArea.getOrCreateStack(null, mTmpOptions, task, - task.getActivityType(), mOnTop); - if (task == toStack) { - // The task was reused as the root task. - return; - } - - if (mOnTop) { - final boolean isTopTask = task == mTopTask; - // Defer resume until all the tasks have been moved to the fullscreen stack - task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, isTopTask /*animate*/, - DEFER_RESUME, mSchedulePictureInPictureModeChange, - "moveTasksToFullscreenStack - onTop"); - MetricsLoggerWrapper.logPictureInPictureFullScreen(mService.mContext, - task.effectiveUid, task.realActivity.flattenToString()); - } else { - // Position the tasks in the fullscreen stack in order at the bottom of the - // stack. Also defer resume until all the tasks have been moved to the - // fullscreen stack. - task.reparent(toStack, ON_TOP, REPARENT_LEAVE_STACK_IN_PLACE, - !ANIMATE, DEFER_RESUME, mSchedulePictureInPictureModeChange, - "moveTasksToFullscreenStack - NOT_onTop"); - } - } - } - /** * Description of a request to start a new activity, which has been held * due to app switches being disabled. @@ -1501,41 +1433,43 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mResizingTasksDuringAnimation.clear(); } - /** - * TODO: This should just change the windowing mode and resize vs. actually moving task around. - * Can do that once we are no longer using static stack ids. - */ - private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack, - int toDisplayId, boolean onTop) { + void setSplitScreenResizing(boolean resizing) { + if (resizing == mDockedStackResizing) { + return; + } - mService.deferWindowLayout(); - try { - final int windowingMode = fromStack.getWindowingMode(); - final TaskDisplayArea toDisplayArea = mRootWindowContainer - .getDisplayContent(toDisplayId).getDefaultTaskDisplayArea(); + mDockedStackResizing = resizing; + mWindowManager.setDockedStackResizing(resizing); + } - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // We are moving all tasks from the docked stack to the fullscreen stack, - // which is dismissing the docked stack, so resize all other stacks to - // fullscreen here already so we don't end up with resize trashing. - for (int i = toDisplayArea.getStackCount() - 1; i >= 0; --i) { - final ActivityStack otherStack = toDisplayArea.getStackAt(i); - if (!otherStack.inSplitScreenSecondaryWindowingMode()) { - continue; - } - otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED); - } - } + private void removePinnedStackInSurfaceTransaction(ActivityStack stack) { + /** + * Workaround: Force-stop all the activities in the pinned stack before we reparent them + * to the fullscreen stack. This is to guarantee that when we are removing a stack, + * that the client receives onStop() before it is reparented. We do this by detaching + * the stack from the display so that it will be considered invisible when + * ensureActivitiesVisible() is called, and all of its activities will be marked + * invisible as well and added to the stopping list. After which we process the + * stopping list by handling the idle. + */ + stack.cancelAnimation(); + stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */); + stack.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); + stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */); + activityIdleInternal(null /* idleActivity */, false /* fromTimeout */, + true /* processPausingActivities */, null /* configuration */); - // If we are moving from the pinned stack, then the animation takes care of updating - // the picture-in-picture mode. - final boolean schedulePictureInPictureModeChange = - windowingMode == WINDOWING_MODE_PINNED; + // Reparent all the tasks to the bottom of the display + final DisplayContent toDisplay = + mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY); - if (fromStack.hasChild()) { - mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); - mMoveTaskToFullscreenHelper.process( - fromStack, toDisplayArea, onTop, schedulePictureInPictureModeChange); + mService.deferWindowLayout(); + try { + stack.setWindowingMode(WINDOWING_MODE_UNDEFINED); + if (toDisplay.getDisplayId() != stack.getDisplayId()) { + stack.reparent(toDisplay.getDefaultTaskDisplayArea(), false /* onTop */); + } else { + toDisplay.getDefaultTaskDisplayArea().positionStackAtBottom(stack); } mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); @@ -1545,41 +1479,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } } - void moveTasksToFullscreenStackLocked(ActivityStack fromStack, boolean onTop) { - // TODO(b/153089193): Support moving within the same task display area - mWindowManager.inSurfaceTransaction(() -> - moveTasksToFullscreenStackInSurfaceTransaction(fromStack, DEFAULT_DISPLAY, onTop)); - } - - void setSplitScreenResizing(boolean resizing) { - if (resizing == mDockedStackResizing) { - return; - } - - mDockedStackResizing = resizing; - mWindowManager.setDockedStackResizing(resizing); - } - private void removeStackInSurfaceTransaction(ActivityStack stack) { if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) { - /** - * Workaround: Force-stop all the activities in the pinned stack before we reparent them - * to the fullscreen stack. This is to guarantee that when we are removing a stack, - * that the client receives onStop() before it is reparented. We do this by detaching - * the stack from the display so that it will be considered invisible when - * ensureActivitiesVisible() is called, and all of its activities will be marked - * invisible as well and added to the stopping list. After which we process the - * stopping list by handling the idle. - */ - stack.cancelAnimation(); - stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */); - stack.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); - stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */); - activityIdleInternal(null /* idleActivity */, false /* fromTimeout */, - true /* processPausingActivities */, null /* configuration */); - - // Move all the tasks to the bottom of the fullscreen stack - moveTasksToFullscreenStackLocked(stack, !ON_TOP); + removePinnedStackInSurfaceTransaction(stack); } else { final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStackSupervisor::processRemoveTask, this, PooledLambda.__(Task.class)); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0b1968765300..c253cd2c3297 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3324,33 +3324,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found"); return; } - // Place the task in the right stack if it isn't there already based on - // the requested bounds. - // The stack transition logic is: - // - a null bounds on a freeform task moves that task to fullscreen - // - a non-null bounds on a non-freeform (fullscreen OR docked) task moves - // that task to freeform - // - otherwise the task is not moved - ActivityStack stack = task.getStack(); if (!task.getWindowConfiguration().canResizeTask()) { throw new IllegalArgumentException("resizeTask not allowed on task=" + task); } - if (bounds == null && stack.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - stack = stack.getDisplayArea().getOrCreateStack( - WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP); - } else if (bounds != null && stack.getWindowingMode() != WINDOWING_MODE_FREEFORM) { - stack = stack.getDisplayArea().getOrCreateStack( - WINDOWING_MODE_FREEFORM, stack.getActivityType(), ON_TOP); - } // Reparent the task to the right stack if necessary boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0; - if (stack != task.getStack()) { - // Defer resume until the task is resized below - task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, - DEFER_RESUME, "resizeTask"); - preserveWindow = false; - } // After reparenting (which only resizes the task to the stack bounds), resize the // task to the actual bounds provided @@ -4022,28 +4001,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - @Override - // TODO: API should just be about changing windowing modes... - public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) { - enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, - "moveTasksToFullscreenStack()"); - synchronized (mGlobalLock) { - final long origId = Binder.clearCallingIdentity(); - try { - final ActivityStack stack = mRootWindowContainer.getStack(fromStackId); - if (stack != null){ - if (!stack.isActivityTypeStandardOrUndefined()) { - throw new IllegalArgumentException( - "You can't move tasks from non-standard stacks."); - } - mStackSupervisor.moveTasksToFullscreenStackLocked(stack, onTop); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - /** * Moves the top activity in the input stackId to the pinned stack. * diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index f86aeb2244dc..78ee1de78079 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_WITH_CLASS_NAME 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 @@ public class AppTransition implements Dump { } 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 edd14b7bebf3..a512337c96ec 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 @@ import java.util.function.Predicate; * - 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 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { 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 982157336295..15b6f9735bf1 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 @@ public abstract class DisplayAreaPolicy { 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 @@ public abstract class DisplayAreaPolicy { * @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 class DisplayAreaPolicy { */ 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 @@ public abstract class DisplayAreaPolicy { */ 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 e8becfa27fac..2c2f43392eca 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 @@ class DisplayAreaPolicyBuilder { 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 @@ class DisplayAreaPolicyBuilder { 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 @@ class DisplayAreaPolicyBuilder { 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 @@ class DisplayAreaPolicyBuilder { 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 a93b962c33b4..40243e8bbedf 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.LayoutParams.TYPE_WALLPAPER; 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.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerPolicyConstants.PointerEventListener; -import android.window.ITaskOrganizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -269,8 +269,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return null; } mShellRoots.put(windowType, root); - SurfaceControl out = new SurfaceControl(); - out.copyFrom(rootLeash); + SurfaceControl out = new SurfaceControl(rootLeash); return out; } @@ -2071,13 +2068,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 9732637fdd4d..701feff8c6be 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 @@ public class ShellRoot { 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 9adacb8c578d..d31939dec509 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 @@ class Task extends WindowContainer<WindowContainer> { * 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 1bc7244996ab..11cfad263fb2 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.app.WindowConfiguration.isSplitScreenWindowingMode; 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 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { */ 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; @@ -936,9 +936,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } } else { addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM); - stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, - false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, - true /* creating */); + stack.setWindowingMode(windowingMode, true /* creating */); } return stack; } @@ -1148,7 +1146,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { for (int i = getStackCount() - 1; i >= 0; --i) { final ActivityStack stack = getStackAt(i); // Collect the root tasks that are currently being organized. - if (stack.isOrganized()) { + if (stack.mCreatedByOrganizer) { for (int k = stack.getChildCount() - 1; k >= 0; --k) { final ActivityStack childStack = (ActivityStack) stack.getChildAt(k); if (childStack.getActivityType() == activityType) { @@ -1614,6 +1612,11 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } } + @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 2bbf8dbb274c..22702dd6b566 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } 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 f6ed2a9a44f4..14e5c6cbf28d 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 @@ class TaskSnapshotSurface implements StartingSurface { 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 569b8f61c4f4..f6e952c4cea1 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mLastLayer = -1; mSurfaceFreezer.unfreeze(t); reassignLayer(t); + updateSurfacePosition(t); } /** @@ -2365,11 +2370,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } - 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // 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 8d3a4f27a257..513187dc17f5 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.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub @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 @@ public class WindowManagerService extends IWindowManager.Stub 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 d611ad81ca85..5a76bac67d64 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @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 7457a1d05335..5976b48db764 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.graphics.Rect; 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 @@ class WindowToken extends WindowContainer<WindowState> { @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 @@ class WindowToken extends WindowContainer<WindowState> { } } + 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 @@ class WindowToken extends WindowContainer<WindowState> { 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 @@ class WindowToken extends WindowContainer<WindowState> { } void setExiting() { - if (mChildren.size() == 0) { + if (isEmpty()) { super.removeImmediately(); return; } @@ -340,6 +383,21 @@ class WindowToken extends WindowContainer<WindowState> { // 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 @@ class WindowToken extends WindowContainer<WindowState> { } 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 @@ class WindowToken extends WindowContainer<WindowState> { 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 @@ class WindowToken extends WindowContainer<WindowState> { } @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 @@ class WindowToken extends WindowContainer<WindowState> { 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 97de1800cae2..2dbbc5ac6806 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -155,11 +155,6 @@ binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) { 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 d0357d924586..28613e101b7c 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -71,7 +71,6 @@ public: 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 d36eae89c1ff..92366e51eb47 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -51,7 +51,7 @@ using namespace android::content::pm; 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 { @@ -73,7 +73,7 @@ struct Constants { }; static const Constants& constants() { - static Constants c; + static constexpr Constants c; return c; } @@ -159,6 +159,9 @@ std::string makeBindMdName() { } } // namespace +const bool IncrementalService::sEnablePerfLogging = + android::base::GetBoolProperty("incremental.perflogging", false); + IncrementalService::IncFsMount::~IncFsMount() { incrementalService.mDataLoaderManager->destroyDataLoader(mountId); LOG(INFO) << "Unmounting and cleaning up mount " << mountId << " with root '" << root << '\''; @@ -578,6 +581,7 @@ StorageId IncrementalService::findStorageId(std::string_view path) const { int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) { const auto ifs = getIfs(storageId); if (!ifs) { + LOG(ERROR) << "setStorageParams failed, invalid storageId: " << storageId; return -EINVAL; } @@ -719,7 +723,10 @@ int IncrementalService::bind(StorageId storage, std::string_view source, std::st if (storageInfo == ifs->storages.end()) { return -EINVAL; } - std::string normSource = normalizePathToStorage(ifs, storage, source); + std::string normSource = normalizePathToStorageLocked(storageInfo, source); + if (normSource.empty()) { + return -EINVAL; + } l.unlock(); std::unique_lock l2(mLock, std::defer_lock); return addBindMount(*ifs, storage, storageInfo->second.name, std::move(normSource), @@ -768,22 +775,28 @@ int IncrementalService::unbind(StorageId storage, std::string_view target) { return 0; } -std::string IncrementalService::normalizePathToStorage(const IncrementalService::IfsMountPtr ifs, - StorageId storage, std::string_view path) { - const auto storageInfo = ifs->storages.find(storage); - if (storageInfo == ifs->storages.end()) { - return {}; - } +std::string IncrementalService::normalizePathToStorageLocked( + IncFsMount::StorageMap::iterator storageIt, std::string_view path) { std::string normPath; if (path::isAbsolute(path)) { normPath = path::normalize(path); + if (!path::startsWith(normPath, storageIt->second.name)) { + return {}; + } } else { - normPath = path::normalize(path::join(storageInfo->second.name, path)); + normPath = path::normalize(path::join(storageIt->second.name, path)); } - if (!path::startsWith(normPath, storageInfo->second.name)) { + return normPath; +} + +std::string IncrementalService::normalizePathToStorage(const IncrementalService::IfsMountPtr& ifs, + StorageId storage, std::string_view path) { + std::unique_lock l(ifs->lock); + const auto storageInfo = ifs->storages.find(storage); + if (storageInfo == ifs->storages.end()) { return {}; } - return normPath; + return normalizePathToStorageLocked(storageInfo, path); } int IncrementalService::makeFile(StorageId storage, std::string_view path, int mode, FileId id, @@ -791,7 +804,8 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m if (auto ifs = getIfs(storage)) { std::string normPath = normalizePathToStorage(ifs, storage, path); if (normPath.empty()) { - LOG(ERROR) << "Internal error: storageId " << storage << " failed to normalize: " << path; + LOG(ERROR) << "Internal error: storageId " << storage + << " failed to normalize: " << path; return -EINVAL; } auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params); @@ -799,10 +813,6 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err; return err; } - std::vector<uint8_t> metadataBytes; - if (params.metadata.data && params.metadata.size > 0) { - metadataBytes.assign(params.metadata.data, params.metadata.data + params.metadata.size); - } return 0; } return -EINVAL; @@ -842,8 +852,9 @@ int IncrementalService::makeDirs(StorageId storageId, std::string_view path, int int IncrementalService::link(StorageId sourceStorageId, std::string_view oldPath, StorageId destStorageId, std::string_view newPath) { - if (auto ifsSrc = getIfs(sourceStorageId), ifsDest = getIfs(destStorageId); - ifsSrc && ifsSrc == ifsDest) { + auto ifsSrc = getIfs(sourceStorageId); + auto ifsDest = sourceStorageId == destStorageId ? ifsSrc : getIfs(destStorageId); + if (ifsSrc && ifsSrc == ifsDest) { std::string normOldPath = normalizePathToStorage(ifsSrc, sourceStorageId, oldPath); std::string normNewPath = normalizePathToStorage(ifsDest, destStorageId, newPath); if (normOldPath.empty() || normNewPath.empty()) { @@ -1143,6 +1154,7 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, 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 @@ -1156,11 +1168,25 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, return true; } -// Extract lib filse from zip, create new files in incfs and write data to them +template <class Duration> +static long elapsedMcs(Duration start, Duration end) { + return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); +} + +// Extract lib files from zip, create new files in incfs and write data to them bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_view apkFullPath, std::string_view libDirRelativePath, std::string_view abi) { + namespace sc = std::chrono; + using Clock = sc::steady_clock; + auto start = Clock::now(); + const auto ifs = getIfs(storage); + if (!ifs) { + LOG(ERROR) << "Invalid storage " << storage; + return false; + } + // First prepare target directories if they don't exist yet if (auto res = makeDirs(storage, libDirRelativePath, 0755)) { LOG(ERROR) << "Failed to prepare target lib directory " << libDirRelativePath @@ -1168,112 +1194,145 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ return false; } - std::unique_ptr<ZipFileRO> zipFile(ZipFileRO::open(apkFullPath.data())); + auto mkDirsTs = Clock::now(); + + std::unique_ptr<ZipFileRO> zipFile(ZipFileRO::open(path::c_str(apkFullPath))); if (!zipFile) { LOG(ERROR) << "Failed to open zip file at " << apkFullPath; return false; } void* cookie = nullptr; const auto libFilePrefix = path::join(constants().libDir, abi); - if (!zipFile.get()->startIteration(&cookie, libFilePrefix.c_str() /* prefix */, - constants().libSuffix.data() /* suffix */)) { + if (!zipFile->startIteration(&cookie, libFilePrefix.c_str() /* prefix */, + constants().libSuffix.data() /* suffix */)) { LOG(ERROR) << "Failed to start zip iteration for " << apkFullPath; return false; } + auto endIteration = [&zipFile](void* cookie) { zipFile->endIteration(cookie); }; + auto iterationCleaner = std::unique_ptr<void, decltype(endIteration)>(cookie, endIteration); + + auto openZipTs = Clock::now(); + + std::vector<IncFsDataBlock> instructions; ZipEntryRO entry = nullptr; - bool success = true; - while ((entry = zipFile.get()->nextEntry(cookie)) != nullptr) { + while ((entry = zipFile->nextEntry(cookie)) != nullptr) { + auto startFileTs = Clock::now(); + char fileName[PATH_MAX]; - if (zipFile.get()->getEntryFileName(entry, fileName, sizeof(fileName))) { + if (zipFile->getEntryFileName(entry, fileName, sizeof(fileName))) { continue; } const auto libName = path::basename(fileName); const auto targetLibPath = path::join(libDirRelativePath, libName); const auto targetLibPathAbsolute = normalizePathToStorage(ifs, storage, targetLibPath); // If the extract file already exists, skip - struct stat st; - if (stat(targetLibPathAbsolute.c_str(), &st) == 0) { - LOG(INFO) << "Native lib file already exists: " << targetLibPath - << "; skipping extraction"; + if (access(targetLibPathAbsolute.c_str(), F_OK) == 0) { + if (sEnablePerfLogging) { + LOG(INFO) << "incfs: Native lib file already exists: " << targetLibPath + << "; skipping extraction, spent " + << elapsedMcs(startFileTs, Clock::now()) << "mcs"; + } continue; } - uint32_t uncompressedLen; - if (!zipFile.get()->getEntryInfo(entry, nullptr, &uncompressedLen, nullptr, nullptr, - nullptr, nullptr)) { + uint32_t uncompressedLen, compressedLen; + if (!zipFile->getEntryInfo(entry, nullptr, &uncompressedLen, &compressedLen, nullptr, + nullptr, nullptr)) { LOG(ERROR) << "Failed to read native lib entry: " << fileName; - success = false; - break; + return false; } // Create new lib file without signature info - incfs::NewFileParams libFileParams{}; - libFileParams.size = uncompressedLen; - libFileParams.signature = {}; - // Metadata of the new lib file is its relative path - IncFsSpan libFileMetadata; - libFileMetadata.data = targetLibPath.c_str(); - libFileMetadata.size = targetLibPath.size(); - libFileParams.metadata = libFileMetadata; + incfs::NewFileParams libFileParams = { + .size = uncompressedLen, + .signature = {}, + // Metadata of the new lib file is its relative path + .metadata = {targetLibPath.c_str(), (IncFsSize)targetLibPath.size()}, + }; incfs::FileId libFileId = idFromMetadata(targetLibPath); - if (auto res = makeFile(storage, targetLibPath, 0777, libFileId, libFileParams)) { + if (auto res = mIncFs->makeFile(ifs->control, targetLibPathAbsolute, 0777, libFileId, + libFileParams)) { LOG(ERROR) << "Failed to make file for: " << targetLibPath << " errno: " << res; - success = false; // If one lib file fails to be created, abort others as well - break; + return false; } + + auto makeFileTs = Clock::now(); + // If it is a zero-byte file, skip data writing if (uncompressedLen == 0) { + if (sEnablePerfLogging) { + LOG(INFO) << "incfs: Extracted " << libName << "(" << compressedLen << " -> " + << uncompressedLen << " bytes): " << elapsedMcs(startFileTs, makeFileTs) + << "mcs, make: " << elapsedMcs(startFileTs, makeFileTs); + } continue; } // Write extracted data to new file - std::vector<uint8_t> libData(uncompressedLen); - if (!zipFile.get()->uncompressEntry(entry, &libData[0], uncompressedLen)) { + // NOTE: don't zero-initialize memory, it may take a while + auto libData = std::unique_ptr<uint8_t[]>(new uint8_t[uncompressedLen]); + if (!zipFile->uncompressEntry(entry, libData.get(), uncompressedLen)) { LOG(ERROR) << "Failed to extract native lib zip entry: " << fileName; - success = false; - break; + return false; } + + auto extractFileTs = Clock::now(); + const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId); if (!writeFd.ok()) { LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd; - success = false; - break; + return false; } - const int numBlocks = uncompressedLen / constants().blockSize + 1; - std::vector<IncFsDataBlock> instructions; - auto remainingData = std::span(libData); - for (int i = 0; i < numBlocks - 1; i++) { + + auto openFileTs = Clock::now(); + + const int numBlocks = (uncompressedLen + constants().blockSize - 1) / constants().blockSize; + instructions.clear(); + instructions.reserve(numBlocks); + auto remainingData = std::span(libData.get(), uncompressedLen); + for (int i = 0; i < numBlocks; i++) { + const auto blockSize = std::min<uint16_t>(constants().blockSize, remainingData.size()); auto inst = IncFsDataBlock{ - .fileFd = writeFd, + .fileFd = writeFd.get(), .pageIndex = static_cast<IncFsBlockIndex>(i), .compression = INCFS_COMPRESSION_KIND_NONE, .kind = INCFS_BLOCK_KIND_DATA, - .dataSize = static_cast<uint16_t>(constants().blockSize), + .dataSize = blockSize, .data = reinterpret_cast<const char*>(remainingData.data()), }; instructions.push_back(inst); - remainingData = remainingData.subspan(constants().blockSize); + remainingData = remainingData.subspan(blockSize); } - // Last block - auto inst = IncFsDataBlock{ - .fileFd = writeFd, - .pageIndex = static_cast<IncFsBlockIndex>(numBlocks - 1), - .compression = INCFS_COMPRESSION_KIND_NONE, - .kind = INCFS_BLOCK_KIND_DATA, - .dataSize = static_cast<uint16_t>(remainingData.size()), - .data = reinterpret_cast<const char*>(remainingData.data()), - }; - instructions.push_back(inst); + auto prepareInstsTs = Clock::now(); + size_t res = mIncFs->writeBlocks(instructions); if (res != instructions.size()) { LOG(ERROR) << "Failed to write data into: " << targetLibPath; - success = false; + return false; + } + + if (sEnablePerfLogging) { + auto endFileTs = Clock::now(); + LOG(INFO) << "incfs: Extracted " << libName << "(" << compressedLen << " -> " + << uncompressedLen << " bytes): " << elapsedMcs(startFileTs, endFileTs) + << "mcs, make: " << elapsedMcs(startFileTs, makeFileTs) + << " extract: " << elapsedMcs(makeFileTs, extractFileTs) + << " open: " << elapsedMcs(extractFileTs, openFileTs) + << " prepare: " << elapsedMcs(openFileTs, prepareInstsTs) + << " write:" << elapsedMcs(prepareInstsTs, endFileTs); } - instructions.clear(); } - zipFile.get()->endIteration(cookie); - return success; + + if (sEnablePerfLogging) { + auto end = Clock::now(); + LOG(INFO) << "incfs: configureNativeBinaries complete in " << elapsedMcs(start, end) + << "mcs, make dirs: " << elapsedMcs(start, mkDirsTs) + << " open zip: " << elapsedMcs(mkDirsTs, openZipTs) + << " extract all: " << elapsedMcs(openZipTs, end); + } + + return true; } void IncrementalService::registerAppOpsCallback(const std::string& packageName) { @@ -1394,4 +1453,10 @@ void IncrementalService::AppOpsListener::opChanged(int32_t, const String16&) { 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 58002974e180..db14a794457e 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 @@ public: 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,14 +150,27 @@ public: 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; + struct IncFsMount { struct Bind { StorageId storage; @@ -227,8 +241,10 @@ private: void deleteStorage(IncFsMount& ifs); void deleteStorageLocked(IncFsMount& ifs, std::unique_lock<std::mutex>&& ifsLock); MountMap::iterator getStorageSlotLocked(); - std::string normalizePathToStorage(const IfsMountPtr incfs, StorageId storage, + std::string normalizePathToStorage(const IfsMountPtr& incfs, StorageId storage, std::string_view path); + std::string normalizePathToStorageLocked(IncFsMount::StorageMap::iterator storageIt, + std::string_view path); binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs); diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index 0635ae169281..18ae4b5af435 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -137,6 +137,7 @@ public: bool* _aidl_return) { mId = mountId; mListener = listener; + mServiceConnector = control.service; *_aidl_return = true; return binder::Status::ok(); } @@ -166,9 +167,17 @@ public: 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) { 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChang 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsCheckPermissionFails) { 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) { 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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e5ffbacb357b..e2a247394a81 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -136,6 +136,7 @@ import com.android.server.pm.OtaDexoptService; import com.android.server.pm.PackageManagerService; import com.android.server.pm.ShortcutService; import com.android.server.pm.UserManagerService; +import com.android.server.pm.dex.SystemServerDexLoadReporter; import com.android.server.policy.PermissionPolicyService; import com.android.server.policy.PhoneWindowManager; import com.android.server.policy.role.LegacyRoleResolutionPolicy; @@ -837,6 +838,11 @@ public final class SystemServer { Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain"); } + // Now that the package manager has started, register the dex load reporter to capture any + // dex files loaded by system server. + // These dex files will be optimized by the BackgroundDexOptService. + SystemServerDexLoadReporter.configureSystemServerDexReporter(mPackageManagerService); + mFirstBoot = mPackageManagerService.isFirstBoot(); mPackageManager = mSystemContext.getPackageManager(); t.traceEnd(); diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java index c225d3feb063..1aab6722dfee 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 @@ final class RemoteSystemCaptionsManagerService { 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 @@ final class RemoteSystemCaptionsManagerService { if (mVerbose) { Slog.v(TAG, "initialize()"); } - ensureBound(); + scheduleBind(); + } + + /** + * Destroys this service. + */ + public void destroy() { + mHandler.sendMessage( + obtainMessage(RemoteSystemCaptionsManagerService::handleDestroy, this)); } - void destroy() { + 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 @@ final class RemoteSystemCaptionsManagerService { } } - 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 7c9a81d2e094..a525814435ea 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 @@ import java.lang.ref.WeakReference; @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 class AppSaturationControllerTest { 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 class AppSaturationControllerTest { 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 class AppSaturationControllerTest { 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/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index d69e1b8786b4..8398585ca74a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -85,6 +85,9 @@ public class DexManagerTests { private TestData mBarUser0UnsupportedClassLoader; private TestData mBarUser0DelegateLastClassLoader; + private TestData mSystemServerJar; + private TestData mSystemServerJarInvalid; + private int mUser0; private int mUser1; @@ -108,6 +111,9 @@ public class DexManagerTests { mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0, DELEGATE_LAST_CLASS_LOADER_NAME); + mSystemServerJar = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME); + mSystemServerJarInvalid = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME); + mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock); @@ -587,6 +593,25 @@ public class DexManagerTests { assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); } + + @Test + public void testNotifySystemServerUse() { + List<String> dexFiles = new ArrayList<String>(); + dexFiles.add("/system/framework/foo"); + notifyDexLoad(mSystemServerJar, dexFiles, mUser0); + PackageUseInfo pui = getPackageUseInfo(mSystemServerJar); + assertIsUsedByOtherApps(mSystemServerJar, pui, false); + } + + @Test + public void testNotifySystemServerInvalidUse() { + List<String> dexFiles = new ArrayList<String>(); + dexFiles.add("/data/foo"); + notifyDexLoad(mSystemServerJarInvalid, dexFiles, mUser0); + assertNoUseInfo(mSystemServerJarInvalid); + assertNoDclInfo(mSystemServerJarInvalid); + } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId, String[] expectedContexts) { 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 12bdec6ec1e1..0568be8d7fa6 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_NAVIGATION_BAR; 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 @@ public class DisplayAreaPolicyBuilderTest { 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 @@ public class DisplayAreaPolicyBuilderTest { .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 @@ public class DisplayAreaPolicyBuilderTest { 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 @@ public class DisplayAreaPolicyBuilderTest { 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 4e4627bf7e52..6834ee5f3950 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 @@ public class DisplayAreaProviderTest { @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 618e6086b582..880c486c15af 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 @@ public class DisplayAreaTest { } @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 @@ public class DisplayAreaTest { 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/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index add4e9cf3948..ea933dfe42dc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -1124,8 +1124,6 @@ public class RecentTasksTest extends ActivityTestsBase { } }); assertSecurityException(expectCallable, - () -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true)); - assertSecurityException(expectCallable, () -> mService.startActivityFromRecents(0, new Bundle())); assertSecurityException(expectCallable, () -> mService.getTaskSnapshot(0, true)); assertSecurityException(expectCallable, () -> mService.registerTaskStackListener(null)); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 6d2b7b1e86fe..f19550ced0bf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -23,7 +23,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; @@ -318,7 +317,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { // Assume activity transition should animate when no // IRecentsAnimationController#setDeferCancelUntilNextTransition called. assertFalse(mController.shouldDeferCancelWithScreenshot()); - assertTrue(activity.shouldAnimate(TRANSIT_ACTIVITY_CLOSE)); + assertTrue(activity.shouldAnimate()); } @Test 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 673469474709..e47792f4920c 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 @@ public class SizeCompatTests extends ActivityTestsBase { } @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 9625ffdac052..a7085334bece 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.Test; 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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { 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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { 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 2ce9c2b9ced0..06ca6c110613 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 @@ public class TaskOrganizerTests extends WindowTestsBase { } @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 18737c2b4bb7..d2a2732d60ab 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 @@ class TestDisplayContent extends DisplayContent { // 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 @@ class TestDisplayContent extends DisplayContent { 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 da4bde59a09e..79b9ae1b902a 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 @@ public class WindowManagerServiceTests extends WindowTestsBase { 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 7a347cb050be..76479cb95b09 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 @@ public class WindowTokenTests extends WindowTestsBase { 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 class WindowTokenTests extends WindowTestsBase { 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 @@ public class WindowTokenTests extends WindowTestsBase { 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 d5851d85345f..0c25cfb968fd 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.Parcel; 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 @@ public class IorapForwardingService extends SystemService { 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 @@ public class IorapForwardingService extends SystemService { 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 f3e9de0d2688..3048ad7c1fb0 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -311,7 +311,7 @@ public final class LocationAccessPolicy { } // 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 327e8b344eeb..56f3c3ec0622 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1147,6 +1147,21 @@ public class CarrierConfigManager { "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 @@ public class CarrierConfigManager { "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 @@ public class CarrierConfigManager { 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 2b1d9e58c4d5..18e25921555a 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -113,6 +113,7 @@ public class DctConstants { 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 000000000000..ac1c28a45462 --- /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 6e9dc8eaf2dc..3f8261d5ad7f 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_PARTIAL_CONNECTIVIT 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.assertArrayEquals; 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 com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; 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 @@ public class NetworkCapabilitiesTest { @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 @@ public class NetworkCapabilitiesTest { 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 9119d62fb023..7b22e45db90a 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.net.NetworkStats.TAG_NONE 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 @@ import kotlin.test.assertEquals 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 @@ class NetworkStatsApiTest { @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 @@ class NetworkStatsApiTest { } @Test - @IgnoreUpTo(Build.VERSION_CODES.Q) fun testAdd() { var stats = NetworkStats(0L, 0) assertNetworkStatsEquals(testStatsEmpty, stats) @@ -168,7 +172,6 @@ class NetworkStatsApiTest { } @Test - @IgnoreUpTo(Build.VERSION_CODES.Q) fun testParcelUnparcel() { assertParcelingIsLossless(testStatsEmpty) assertParcelingIsLossless(testStats1) @@ -177,7 +180,6 @@ class NetworkStatsApiTest { } @Test - @IgnoreUpTo(Build.VERSION_CODES.Q) fun testDescribeContents() { assertEquals(0, testStatsEmpty.describeContents()) assertEquals(0, testStats1.describeContents()) @@ -186,7 +188,6 @@ class NetworkStatsApiTest { } @Test - @IgnoreUpTo(Build.VERSION_CODES.Q) fun testSubtract() { // STATS3 - STATS2 = STATS1 assertNetworkStatsEquals(testStats1, testStats3.subtract(testStats2)) @@ -195,7 +196,6 @@ class NetworkStatsApiTest { } @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 4c2a984f8198..737665fb97e4 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 android.view.IWindowManager; import junit.framework.TestCase; +import org.junit.Test; + /** * TODO: Remove this. This is only a placeholder, need to implement this. */ @@ -53,7 +56,7 @@ public class WindowManagerPermissionTests extends TestCase { } 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 @@ public class WindowManagerPermissionTests extends TestCase { } 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 @@ public class WindowManagerPermissionTests extends TestCase { 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 3026e0b51133..0dd45bad8a01 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 @@ public final class FrameworksTestsFilter extends SelectTest { "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 d56f2be7ecb3..b513463ec98f 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -160,7 +160,7 @@ struct AtomDecl { 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 23a0f7278271..5fe94987aa65 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 @@ static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) { 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 @@ static void write_atoms_info_cpp_body(FILE* out, const Atoms& atoms) { 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 eeb006ee6ab2..cf11f4347503 100644 --- a/wifi/jarjar-rules.txt +++ b/wifi/jarjar-rules.txt @@ -53,6 +53,9 @@ rule com.google.protobuf.** com.android.server.x.wifi.protobuf.@1 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 |