diff options
179 files changed, 4235 insertions, 1737 deletions
diff --git a/apex/Android.bp b/apex/Android.bp index c1715a002d6d..371bd7fc9479 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -63,9 +63,9 @@ mainline_service_stubs_args = "--hide-annotation android.annotation.Hide " + "--hide InternalClasses " // com.android.* classes are okay in this interface -// Defaults for mainline module provided java_sdk_library instances. +// Defaults common to all mainline module java_sdk_library instances. java_defaults { - name: "framework-module-defaults", + name: "framework-module-common-defaults", // Additional annotations used for compiling both the implementation and the // stubs libraries. @@ -88,14 +88,6 @@ java_defaults { enabled: true, sdk_version: "module_current", }, - system: { - enabled: true, - sdk_version: "module_current", - }, - module_lib: { - enabled: true, - sdk_version: "module_current", - }, // Configure framework module specific metalava options. droiddoc_options: [mainline_stubs_args], @@ -127,6 +119,32 @@ java_defaults { sdk_version: "module_current", } +// Defaults for mainline module provided java_sdk_library instances. +java_defaults { + name: "framework-module-defaults", + defaults: ["framework-module-common-defaults"], + + system: { + enabled: true, + sdk_version: "module_current", + }, + module_lib: { + enabled: true, + sdk_version: "module_current", + }, +} + +// Defaults for mainline module system server provided java_sdk_library instances. +java_defaults { + name: "framework-system-server-module-defaults", + defaults: ["framework-module-common-defaults"], + + system_server: { + enabled: true, + sdk_version: "module_current", + }, +} + stubs_defaults { name: "framework-module-stubs-defaults-publicapi", args: mainline_framework_stubs_args, diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp index 61449763540b..7f3187949712 100644 --- a/apex/permission/service/Android.bp +++ b/apex/permission/service/Android.bp @@ -20,14 +20,26 @@ filegroup { path: "java", } -java_library { +java_sdk_library { name: "service-permission", + defaults: ["framework-system-server-module-defaults"], + visibility: [ + "//frameworks/base/services/core", + "//frameworks/base/apex/permission", + "//frameworks/base/apex/permission/testing", + "//frameworks/base/apex/permission/tests", + "//frameworks/base/services/tests/mockingservicestests", + ], + impl_library_visibility: [ + "//visibility:override", + "//frameworks/base/apex/permission/tests", + "//frameworks/base/services/tests/mockingservicestests", + "//frameworks/base/services/tests/servicestests", + ], srcs: [ ":service-permission-sources", ], - sdk_version: "module_current", libs: [ - "framework-annotations-lib", "framework-permission", ], apex_available: [ @@ -36,28 +48,3 @@ java_library { ], installable: true, } - -droidstubs { - name: "service-permission-stubs-srcs", - srcs: [ ":service-permission-sources" ], - defaults: ["service-module-stubs-srcs-defaults"], - check_api: { - last_released: { - api_file: ":service-permission.api.system-server.latest", - removed_api_file: ":service-permission-removed.api.system-server.latest", - }, - api_lint: { - new_since: ":service-permission.api.system-server.latest", - }, - }, - visibility: ["//visibility:private"], - dist: { dest: "service-permission.txt" }, -} - -java_library { - name: "service-permission-stubs", - srcs: [":service-permission-stubs-srcs"], - defaults: ["service-module-stubs-defaults"], - visibility: ["//frameworks/base/services/core"], - dist: { dest: "service-permission.jar" }, -} diff --git a/apex/permission/service/api/current.txt b/apex/permission/service/api/current.txt index c76cc3275737..d802177e249b 100644 --- a/apex/permission/service/api/current.txt +++ b/apex/permission/service/api/current.txt @@ -1,46 +1 @@ // Signature format: 2.0 -package com.android.permission.persistence { - - public interface RuntimePermissionsPersistence { - method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance(); - method public void deleteForUser(@NonNull android.os.UserHandle); - method @Nullable public com.android.permission.persistence.RuntimePermissionsState readForUser(@NonNull android.os.UserHandle); - method public void writeForUser(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle); - } - - public final class RuntimePermissionsState { - ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>); - method @Nullable public String getFingerprint(); - method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getPackagePermissions(); - method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getSharedUserPermissions(); - method public int getVersion(); - field public static final int NO_VERSION = -1; // 0xffffffff - } - - public static final class RuntimePermissionsState.PermissionState { - ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int); - method public int getFlags(); - method @NonNull public String getName(); - method public boolean isGranted(); - } - -} - -package com.android.role.persistence { - - public interface RolesPersistence { - method @NonNull public static com.android.role.persistence.RolesPersistence createInstance(); - method public void deleteForUser(@NonNull android.os.UserHandle); - method @Nullable public com.android.role.persistence.RolesState readForUser(@NonNull android.os.UserHandle); - method public void writeForUser(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle); - } - - public final class RolesState { - ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>); - method @Nullable public String getPackagesHash(); - method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles(); - method public int getVersion(); - } - -} - diff --git a/apex/permission/service/api/system-server-current.txt b/apex/permission/service/api/system-server-current.txt new file mode 100644 index 000000000000..c76cc3275737 --- /dev/null +++ b/apex/permission/service/api/system-server-current.txt @@ -0,0 +1,46 @@ +// Signature format: 2.0 +package com.android.permission.persistence { + + public interface RuntimePermissionsPersistence { + method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance(); + method public void deleteForUser(@NonNull android.os.UserHandle); + method @Nullable public com.android.permission.persistence.RuntimePermissionsState readForUser(@NonNull android.os.UserHandle); + method public void writeForUser(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle); + } + + public final class RuntimePermissionsState { + ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>); + method @Nullable public String getFingerprint(); + method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getPackagePermissions(); + method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getSharedUserPermissions(); + method public int getVersion(); + field public static final int NO_VERSION = -1; // 0xffffffff + } + + public static final class RuntimePermissionsState.PermissionState { + ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int); + method public int getFlags(); + method @NonNull public String getName(); + method public boolean isGranted(); + } + +} + +package com.android.role.persistence { + + public interface RolesPersistence { + method @NonNull public static com.android.role.persistence.RolesPersistence createInstance(); + method public void deleteForUser(@NonNull android.os.UserHandle); + method @Nullable public com.android.role.persistence.RolesState readForUser(@NonNull android.os.UserHandle); + method public void writeForUser(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle); + } + + public final class RolesState { + ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>); + method @Nullable public String getPackagesHash(); + method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles(); + method public int getVersion(); + } + +} + diff --git a/apex/permission/service/api/system-server-removed.txt b/apex/permission/service/api/system-server-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/apex/permission/service/api/system-server-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/apex/permission/tests/Android.bp b/apex/permission/tests/Android.bp index a1f7a544434c..271e328c1139 100644 --- a/apex/permission/tests/Android.bp +++ b/apex/permission/tests/Android.bp @@ -19,7 +19,7 @@ android_test { "java/**/*.kt", ], static_libs: [ - "service-permission", + "service-permission.impl", "androidx.test.rules", "androidx.test.ext.junit", "androidx.test.ext.truth", diff --git a/cmds/idmap2/idmap2/CommandUtils.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp index e058cd6e7e70..8f5845bf2e53 100644 --- a/cmds/idmap2/idmap2/CommandUtils.cpp +++ b/cmds/idmap2/idmap2/CommandUtils.cpp @@ -29,7 +29,7 @@ using android::idmap2::Result; using android::idmap2::Unit; Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path, - const std::string& overlay_path, uint32_t fulfilled_policies, + const std::string& overlay_path, PolicyBitmask fulfilled_policies, bool enforce_overlayable) { SYSTRACE << "Verify " << idmap_path; std::ifstream fin(idmap_path); diff --git a/cmds/idmap2/idmap2/CommandUtils.h b/cmds/idmap2/idmap2/CommandUtils.h index 99605de30988..e717e046d15d 100644 --- a/cmds/idmap2/idmap2/CommandUtils.h +++ b/cmds/idmap2/idmap2/CommandUtils.h @@ -17,12 +17,13 @@ #ifndef IDMAP2_IDMAP2_COMMAND_UTILS_H_ #define IDMAP2_IDMAP2_COMMAND_UTILS_H_ +#include "idmap2/PolicyUtils.h" #include "idmap2/Result.h" android::idmap2::Result<android::idmap2::Unit> Verify(const std::string& idmap_path, const std::string& target_path, const std::string& overlay_path, - uint32_t fulfilled_policies, + PolicyBitmask fulfilled_policies, bool enforce_overlayable); #endif // IDMAP2_IDMAP2_COMMAND_UTILS_H_ diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index 908d96612269..f95b73f17222 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -70,12 +70,12 @@ PolicyBitmask ConvertAidlArgToPolicyBitmask(int32_t arg) { } Status GetCrc(const std::string& apk_path, uint32_t* out_crc) { - const auto overlay_zip = ZipFile::Open(apk_path); - if (!overlay_zip) { + const auto zip = ZipFile::Open(apk_path); + if (!zip) { return error(StringPrintf("failed to open apk %s", apk_path.c_str())); } - const auto crc = GetPackageCrc(*overlay_zip); + const auto crc = GetPackageCrc(*zip); if (!crc) { return error(crc.GetErrorMessage()); } @@ -121,6 +121,7 @@ Status Idmap2Service::verifyIdmap(const std::string& target_apk_path, bool* _aidl_return) { SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_apk_path; assert(_aidl_return); + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); std::ifstream fin(idmap_path); const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin); @@ -156,13 +157,10 @@ Status Idmap2Service::verifyIdmap(const std::string& target_apk_path, auto up_to_date = header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), target_crc, overlay_crc, - fulfilled_policies, enforce_overlayable); - if (!up_to_date) { - *_aidl_return = false; - return error(up_to_date.GetErrorMessage()); - } + ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable); - return ok(); + *_aidl_return = static_cast<bool>(up_to_date); + return *_aidl_return ? ok() : error(up_to_date.GetErrorMessage()); } Status Idmap2Service::createIdmap(const std::string& target_apk_path, diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h index 8f25b8d6a734..0f05592b70f3 100644 --- a/cmds/idmap2/include/idmap2/Idmap.h +++ b/cmds/idmap2/include/idmap2/Idmap.h @@ -141,9 +141,9 @@ class IdmapHeader { // field *must* be incremented. Because of this, we know that if the idmap // header is up-to-date the entire file is up-to-date. Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, - uint32_t fulfilled_policies, bool enforce_overlayable) const; + PolicyBitmask fulfilled_policies, bool enforce_overlayable) const; Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, uint32_t target_crc, - uint32_t overlay_crc, uint32_t fulfilled_policies, + uint32_t overlay_crc, PolicyBitmask fulfilled_policies, bool enforce_overlayable) const; void accept(Visitor* v) const; diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp index 0bea21735b24..23c25a7089de 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -115,8 +115,7 @@ std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& s uint8_t enforce_overlayable; if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) || !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) || - !Read32(stream, &idmap_header->fulfilled_policies_) || - !Read8(stream, &enforce_overlayable) || + !Read32(stream, &idmap_header->fulfilled_policies_) || !Read8(stream, &enforce_overlayable) || !ReadString256(stream, idmap_header->target_path_) || !ReadString256(stream, idmap_header->overlay_path_)) { return nullptr; @@ -134,7 +133,8 @@ std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& s } Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path, - uint32_t fulfilled_policies, bool enforce_overlayable) const { + PolicyBitmask fulfilled_policies, + bool enforce_overlayable) const { const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path); if (!target_zip) { return Error("failed to open target %s", target_path); @@ -161,7 +161,8 @@ Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overla Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path, uint32_t target_crc, uint32_t overlay_crc, - uint32_t fulfilled_policies, bool enforce_overlayable) const { + PolicyBitmask fulfilled_policies, + bool enforce_overlayable) const { if (magic_ != kIdmapMagic) { return Error("bad magic: actual 0x%08x, expected 0x%08x", magic_, kIdmapMagic); } @@ -187,8 +188,7 @@ Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overla if (enforce_overlayable != enforce_overlayable_) { return Error("bad enforce overlayable: idmap version %s, file system version %s", - enforce_overlayable ? "true" : "false", - enforce_overlayable_ ? "true" : "false"); + enforce_overlayable ? "true" : "false", enforce_overlayable_ ? "true" : "false"); } if (strcmp(target_path, target_path_) != 0) { diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 095dd1e9be59..e7b32c56551a 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -529,7 +529,9 @@ void StatsLogProcessor::OnConfigUpdatedLocked( VLOG("StatsdConfig valid"); } else { // If there is any error in the config, don't use it. + // Remove any existing config with the same key. ALOGE("StatsdConfig NOT valid"); + mMetricsManagers.erase(key); } } diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 7090bd46635d..23f2584655b0 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -284,6 +284,7 @@ private: FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize); FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast); FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); + FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved); FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index e2fd511a222b..1823bad0076d 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -193,7 +193,7 @@ message Atom { BiometricAcquired biometric_acquired = 87 [(module) = "framework"]; BiometricAuthenticated biometric_authenticated = 88 [(module) = "framework"]; BiometricErrorOccurred biometric_error_occurred = 89 [(module) = "framework"]; - UiEventReported ui_event_reported = 90 [(module) = "framework"]; + UiEventReported ui_event_reported = 90 [(module) = "framework", (module) = "sysui"]; BatteryHealthSnapshot battery_health_snapshot = 91; SlowIo slow_io = 92; BatteryCausedShutdown battery_caused_shutdown = 93; @@ -419,7 +419,7 @@ message Atom { DisplayJankReported display_jank_reported = 257; AppStandbyBucketChanged app_standby_bucket_changed = 258 [(module) = "framework"]; SharesheetStarted sharesheet_started = 259 [(module) = "framework"]; - RankingSelected ranking_selected = 260 [(module) = "framework"]; + RankingSelected ranking_selected = 260 [(module) = "framework", (module) = "sysui"]; TvSettingsUIInteracted tvsettings_ui_interacted = 261 [(module) = "tv_settings"]; LauncherStaticLayout launcher_snapshot = 262 [(module) = "sysui"]; PackageInstallerV2Reported package_installer_v2_reported = 263 [(module) = "framework"]; diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 8587e1452543..7cac026c2421 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -660,6 +660,8 @@ private: FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats); FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit); FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats); + + FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved); }; } // namespace statsd diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 72decf2c7bd0..acdffd3d4712 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -198,6 +198,9 @@ message EventMetric { optional int64 condition = 3; repeated MetricConditionLink links = 4; + + reserved 100; + reserved 101; } message CountMetric { @@ -218,6 +221,9 @@ message CountMetric { repeated MetricStateLink state_link = 9; optional FieldMatcher dimensions_in_condition = 7 [deprecated = true]; + + reserved 100; + reserved 101; } message DurationMetric { @@ -245,6 +251,9 @@ message DurationMetric { optional TimeUnit bucket = 7; optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; + + reserved 100; + reserved 101; } message GaugeMetric { @@ -281,6 +290,9 @@ message GaugeMetric { optional int32 max_pull_delay_sec = 13 [default = 30]; optional bool split_bucket_for_app_upgrade = 14 [default = true]; + + reserved 100; + reserved 101; } message ValueMetric { @@ -333,6 +345,9 @@ message ValueMetric { optional bool split_bucket_for_app_upgrade = 17 [default = true]; optional FieldMatcher dimensions_in_condition = 9 [deprecated = true]; + + reserved 100; + reserved 101; } message Alert { diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 076f32752223..9a9702c34562 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -324,6 +324,41 @@ TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate) { EXPECT_EQ(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end()); } +TEST(StatsLogProcessorTest, InvalidConfigRemoved) { + // Setup simple config key corresponding to empty config. + StatsdStats::getInstance().reset(); + sp<UidMap> m = new UidMap(); + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")}, + {String16("p1"), String16("p2")}, {String16(""), String16("")}); + sp<AlarmMonitor> anomalyAlarmMonitor; + sp<AlarmMonitor> subscriberAlarmMonitor; + StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [](const ConfigKey& key) { return true; }, + [](const int&, const vector<int64_t>&) {return true;}); + ConfigKey key(3, 4); + StatsdConfig config = MakeConfig(true); + p.OnConfigUpdated(0, key, config); + EXPECT_EQ(1, p.mMetricsManagers.size()); + EXPECT_NE(p.mMetricsManagers.find(key), p.mMetricsManagers.end()); + // Cannot assert the size of mConfigStats since it is static and does not get cleared on reset. + EXPECT_NE(StatsdStats::getInstance().mConfigStats.end(), + StatsdStats::getInstance().mConfigStats.find(key)); + EXPECT_EQ(0, StatsdStats::getInstance().mIceBox.size()); + + StatsdConfig invalidConfig = MakeConfig(true); + invalidConfig.clear_allowed_log_source(); + p.OnConfigUpdated(0, key, invalidConfig); + EXPECT_EQ(0, p.mMetricsManagers.size()); + // The current configs should not contain the invalid config. + EXPECT_EQ(StatsdStats::getInstance().mConfigStats.end(), + StatsdStats::getInstance().mConfigStats.find(key)); + // Both "config" and "invalidConfig" should be in the icebox. + EXPECT_EQ(2, StatsdStats::getInstance().mIceBox.size()); + +} + + TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { int uid = 1111; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index c65064324c8c..1f90e401dee5 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -5868,12 +5868,22 @@ public class DevicePolicyManager { * returned by {@link #getParentProfileInstance(ComponentName)}, where the caller must be * the profile owner of an organization-owned managed profile. * <p> - * If the caller is device owner or called on the parent instance, then the - * restriction will be applied to all users. + * If the caller is device owner, then the restriction will be applied to all users. If + * called on the parent instance, then the restriction will be applied on the personal profile. * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA} to be able to call this method; if it has * not, a security exception will be thrown. + * <p> + * <b>Note</b>, this policy type is deprecated for legacy device admins since + * {@link android.os.Build.VERSION_CODES#Q}. On Android + * {@link android.os.Build.VERSION_CODES#Q} devices, legacy device admins targeting SDK + * version {@link android.os.Build.VERSION_CODES#P} or below can still call this API to + * disable camera, while legacy device admins targeting SDK version + * {@link android.os.Build.VERSION_CODES#Q} will receive a SecurityException. Starting + * from Android {@link android.os.Build.VERSION_CODES#R}, requests to disable camera from + * legacy device admins targeting SDK version {@link android.os.Build.VERSION_CODES#P} or + * below will be silently ignored. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param disabled Whether or not the camera should be disabled. diff --git a/core/java/android/content/pm/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java index 653b9ec9e8f2..98a20f73a120 100644 --- a/core/java/android/content/pm/PackagePartitions.java +++ b/core/java/android/content/pm/PackagePartitions.java @@ -183,17 +183,20 @@ public class PackagePartitions { /** Returns whether the partition contains the specified file in its priv-app folder. */ public boolean containsPrivApp(@NonNull File scanFile) { - return FileUtils.contains(mPrivAppFolder.getFile(), canonicalize(scanFile)); + return mPrivAppFolder != null + && FileUtils.contains(mPrivAppFolder.getFile(), canonicalize(scanFile)); } /** Returns whether the partition contains the specified file in its app folder. */ public boolean containsApp(@NonNull File scanFile) { - return FileUtils.contains(mAppFolder.getFile(), canonicalize(scanFile)); + return mAppFolder != null + && FileUtils.contains(mAppFolder.getFile(), canonicalize(scanFile)); } /** Returns whether the partition contains the specified file in its overlay folder. */ public boolean containsOverlay(@NonNull File scanFile) { - return FileUtils.contains(mOverlayFolder.getFile(), canonicalize(scanFile)); + return mOverlayFolder != null + && FileUtils.contains(mOverlayFolder.getFile(), canonicalize(scanFile)); } } diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 6bbc37a90fae..7f834afd7b30 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -586,13 +586,27 @@ public final class CameraManager { * priority when accessing the camera, and this method will succeed even if the camera device is * in use by another camera API client. Any lower-priority application that loses control of the * camera in this way will receive an - * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback.</p> + * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback. + * Opening the same camera ID twice in the same application will similarly cause the + * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback + * being fired for the {@link CameraDevice} from the first open call and all ongoing tasks + * being droppped.</p> * * <p>Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up * for operation by calling {@link CameraDevice#createCaptureSession} and * {@link CameraDevice#createCaptureRequest}</p> * + * <p>Before API level 30, when the application tries to open multiple {@link CameraDevice} of + * different IDs and the device does not support opening such combination, either the + * {@link #openCamera} will fail and throw a {@link CameraAccessException} or one or more of + * already opened {@link CameraDevice} will be disconnected and receive + * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback. Which + * behavior will happen depends on the device implementation and can vary on different devices. + * Starting in API level 30, if the device does not support the combination of cameras being + * opened, it is guaranteed the {@link #openCamera} call will fail and none of existing + * {@link CameraDevice} will be disconnected.</p> + * * <!-- * <p>Since the camera device will be opened asynchronously, any asynchronous operations done * on the returned CameraDevice instance will be queued up until the device startup has @@ -618,7 +632,8 @@ public final class CameraManager { * {@code null} to use the current thread's {@link android.os.Looper looper}. * * @throws CameraAccessException if the camera is disabled by device policy, - * has been disconnected, or is being used by a higher-priority camera API client. + * has been disconnected, is being used by a higher-priority camera API client, or the device + * has reached its maximal resource and cannot open this camera device. * * @throws IllegalArgumentException if cameraId or the callback was null, * or the cameraId does not match any currently or previously available diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 23c86029f3be..6d49add65c5b 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -1072,7 +1072,7 @@ public class CameraDeviceImpl extends CameraDevice * @param lastFrameNumber last frame number returned from binder. * @param repeatingRequestTypes the repeating requests' types. */ - private void checkEarlyTriggerSequenceCompleteLocked( + private void checkEarlyTriggerSequenceComplete( final int requestId, final long lastFrameNumber, final int[] repeatingRequestTypes) { // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request @@ -1212,7 +1212,7 @@ public class CameraDeviceImpl extends CameraDevice if (repeating) { if (mRepeatingRequestId != REQUEST_ID_NONE) { - checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId, + checkEarlyTriggerSequenceComplete(mRepeatingRequestId, requestInfo.getLastFrameNumber(), mRepeatingRequestTypes); } @@ -1269,7 +1269,7 @@ public class CameraDeviceImpl extends CameraDevice return; } - checkEarlyTriggerSequenceCompleteLocked(requestId, lastFrameNumber, requestTypes); + checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber, requestTypes); } } } @@ -1302,7 +1302,7 @@ public class CameraDeviceImpl extends CameraDevice long lastFrameNumber = mRemoteDevice.flush(); if (mRepeatingRequestId != REQUEST_ID_NONE) { - checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId, lastFrameNumber, + checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber, mRepeatingRequestTypes); mRepeatingRequestId = REQUEST_ID_NONE; mRepeatingRequestTypes = null; @@ -1442,135 +1442,78 @@ public class CameraDeviceImpl extends CameraDevice long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber(); long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber(); long completedZslStillFrameNumber = mFrameNumberTracker.getCompletedZslStillFrameNumber(); - + boolean isReprocess = false; Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator(); while (iter.hasNext()) { final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next(); + boolean sequenceCompleted = false; final int requestId = requestLastFrameNumbers.getRequestId(); final CaptureCallbackHolder holder; - if (mRemoteDevice == null) { - Log.w(TAG, "Camera closed while checking sequences"); - return; - } - if (!requestLastFrameNumbers.isSequenceCompleted()) { - long lastRegularFrameNumber = - requestLastFrameNumbers.getLastRegularFrameNumber(); - long lastReprocessFrameNumber = - requestLastFrameNumbers.getLastReprocessFrameNumber(); - long lastZslStillFrameNumber = - requestLastFrameNumbers.getLastZslStillFrameNumber(); - if (lastRegularFrameNumber <= completedFrameNumber - && lastReprocessFrameNumber <= completedReprocessFrameNumber - && lastZslStillFrameNumber <= completedZslStillFrameNumber) { - Log.v(TAG, String.format( - "Mark requestId %d as completed, because lastRegularFrame %d " - + "is <= %d, lastReprocessFrame %d is <= %d, " - + "lastZslStillFrame %d is <= %d", requestId, - lastRegularFrameNumber, completedFrameNumber, - lastReprocessFrameNumber, completedReprocessFrameNumber, - lastZslStillFrameNumber, completedZslStillFrameNumber)); - requestLastFrameNumbers.markSequenceCompleted(); + synchronized(mInterfaceLock) { + if (mRemoteDevice == null) { + Log.w(TAG, "Camera closed while checking sequences"); + return; } - // Call onCaptureSequenceCompleted int index = mCaptureCallbackMap.indexOfKey(requestId); holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null; - if (holder != null && requestLastFrameNumbers.isSequenceCompleted()) { - Runnable resultDispatch = new Runnable() { - @Override - public void run() { - if (!CameraDeviceImpl.this.isClosed()){ - if (DEBUG) { - Log.d(TAG, String.format( - "fire sequence complete for request %d", - requestId)); - } - - holder.getCallback().onCaptureSequenceCompleted( - CameraDeviceImpl.this, - requestId, - requestLastFrameNumbers.getLastFrameNumber()); - } + if (holder != null) { + long lastRegularFrameNumber = + requestLastFrameNumbers.getLastRegularFrameNumber(); + long lastReprocessFrameNumber = + requestLastFrameNumbers.getLastReprocessFrameNumber(); + long lastZslStillFrameNumber = + requestLastFrameNumbers.getLastZslStillFrameNumber(); + // check if it's okay to remove request from mCaptureCallbackMap + if (lastRegularFrameNumber <= completedFrameNumber + && lastReprocessFrameNumber <= completedReprocessFrameNumber + && lastZslStillFrameNumber <= completedZslStillFrameNumber) { + sequenceCompleted = true; + mCaptureCallbackMap.removeAt(index); + if (DEBUG) { + Log.v(TAG, String.format( + "Remove holder for requestId %d, because lastRegularFrame %d " + + "is <= %d, lastReprocessFrame %d is <= %d, " + + "lastZslStillFrame %d is <= %d", requestId, + lastRegularFrameNumber, completedFrameNumber, + lastReprocessFrameNumber, completedReprocessFrameNumber, + lastZslStillFrameNumber, completedZslStillFrameNumber)); } - }; - final long ident = Binder.clearCallingIdentity(); - try { - holder.getExecutor().execute(resultDispatch); - } finally { - Binder.restoreCallingIdentity(ident); } } } - if (requestLastFrameNumbers.isSequenceCompleted() && - requestLastFrameNumbers.isInflightCompleted()) { - int index = mCaptureCallbackMap.indexOfKey(requestId); - if (index >= 0) { - mCaptureCallbackMap.removeAt(index); - } - if (DEBUG) { - Log.v(TAG, String.format( - "Remove holder for requestId %d", requestId)); - } + // If no callback is registered for this requestId or sequence completed, remove it + // from the frame number->request pair because it's not needed anymore. + if (holder == null || sequenceCompleted) { iter.remove(); } - } - } - - private void removeCompletedCallbackHolderLocked(long lastCompletedRegularFrameNumber, - long lastCompletedReprocessFrameNumber, long lastCompletedZslStillFrameNumber) { - if (DEBUG) { - Log.v(TAG, String.format("remove completed callback holders for " - + "lastCompletedRegularFrameNumber %d, " - + "lastCompletedReprocessFrameNumber %d, " - + "lastCompletedZslStillFrameNumber %d", - lastCompletedRegularFrameNumber, - lastCompletedReprocessFrameNumber, - lastCompletedZslStillFrameNumber)); - } - - Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator(); - while (iter.hasNext()) { - final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next(); - final int requestId = requestLastFrameNumbers.getRequestId(); - final CaptureCallbackHolder holder; - if (mRemoteDevice == null) { - Log.w(TAG, "Camera closed while removing completed callback holders"); - return; - } - long lastRegularFrameNumber = - requestLastFrameNumbers.getLastRegularFrameNumber(); - long lastReprocessFrameNumber = - requestLastFrameNumbers.getLastReprocessFrameNumber(); - long lastZslStillFrameNumber = - requestLastFrameNumbers.getLastZslStillFrameNumber(); - - if (lastRegularFrameNumber <= lastCompletedRegularFrameNumber - && lastReprocessFrameNumber <= lastCompletedReprocessFrameNumber - && lastZslStillFrameNumber <= lastCompletedZslStillFrameNumber) { + // Call onCaptureSequenceCompleted + if (sequenceCompleted) { + Runnable resultDispatch = new Runnable() { + @Override + public void run() { + if (!CameraDeviceImpl.this.isClosed()){ + if (DEBUG) { + Log.d(TAG, String.format( + "fire sequence complete for request %d", + requestId)); + } - if (requestLastFrameNumbers.isSequenceCompleted()) { - int index = mCaptureCallbackMap.indexOfKey(requestId); - if (index >= 0) { - mCaptureCallbackMap.removeAt(index); - } - if (DEBUG) { - Log.v(TAG, String.format( - "Remove holder for requestId %d, because lastRegularFrame %d " - + "is <= %d, lastReprocessFrame %d is <= %d, " - + "lastZslStillFrame %d is <= %d", requestId, - lastRegularFrameNumber, lastCompletedRegularFrameNumber, - lastReprocessFrameNumber, lastCompletedReprocessFrameNumber, - lastZslStillFrameNumber, lastCompletedZslStillFrameNumber)); - } - iter.remove(); - } else { - if (DEBUG) { - Log.v(TAG, "Sequence not yet completed for request id " + requestId); + holder.getCallback().onCaptureSequenceCompleted( + CameraDeviceImpl.this, + requestId, + requestLastFrameNumbers.getLastFrameNumber()); + } } - requestLastFrameNumbers.markInflightCompleted(); + }; + final long ident = Binder.clearCallingIdentity(); + try { + holder.getExecutor().execute(resultDispatch); + } finally { + Binder.restoreCallingIdentity(ident); } } } @@ -1759,12 +1702,6 @@ public class CameraDeviceImpl extends CameraDevice return; } - // Remove all capture callbacks now that device has gone to IDLE state. - removeCompletedCallbackHolderLocked( - Long.MAX_VALUE, /*lastCompletedRegularFrameNumber*/ - Long.MAX_VALUE, /*lastCompletedReprocessFrameNumber*/ - Long.MAX_VALUE /*lastCompletedZslStillFrameNumber*/); - if (!CameraDeviceImpl.this.mIdle) { final long ident = Binder.clearCallingIdentity(); try { @@ -1810,7 +1747,7 @@ public class CameraDeviceImpl extends CameraDevice return; } - checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId, lastFrameNumber, + checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber, mRepeatingRequestTypes); // Check if there is already a new repeating request if (mRepeatingRequestId == repeatingRequestId) { @@ -1829,18 +1766,9 @@ public class CameraDeviceImpl extends CameraDevice public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) { int requestId = resultExtras.getRequestId(); final long frameNumber = resultExtras.getFrameNumber(); - final long lastCompletedRegularFrameNumber = - resultExtras.getLastCompletedRegularFrameNumber(); - final long lastCompletedReprocessFrameNumber = - resultExtras.getLastCompletedReprocessFrameNumber(); - final long lastCompletedZslFrameNumber = - resultExtras.getLastCompletedZslFrameNumber(); if (DEBUG) { - Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber - + ": completedRegularFrameNumber " + lastCompletedRegularFrameNumber - + ", completedReprocessFrameNUmber " + lastCompletedReprocessFrameNumber - + ", completedZslFrameNumber " + lastCompletedZslFrameNumber); + Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber); } final CaptureCallbackHolder holder; @@ -1856,12 +1784,6 @@ public class CameraDeviceImpl extends CameraDevice return; } - // Check if it's okay to remove completed callbacks from mCaptureCallbackMap. - // A callback is completed if the corresponding inflight request has been removed - // from the inflight queue in cameraservice. - removeCompletedCallbackHolderLocked(lastCompletedRegularFrameNumber, - lastCompletedReprocessFrameNumber, lastCompletedZslFrameNumber); - // Get the callback for this frame ID, if there is one holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java index 413caf5e22e0..1d9d644c9306 100644 --- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java @@ -182,12 +182,6 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession return; } - // Remove all capture callbacks now that device has gone to IDLE state. - removeCompletedCallbackHolderLocked( - Long.MAX_VALUE, /*lastCompletedRegularFrameNumber*/ - Long.MAX_VALUE, /*lastCompletedReprocessFrameNumber*/ - Long.MAX_VALUE /*lastCompletedZslStillFrameNumber*/); - Runnable idleDispatch = new Runnable() { @Override public void run() { @@ -210,22 +204,10 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) { int requestId = resultExtras.getRequestId(); final long frameNumber = resultExtras.getFrameNumber(); - final long lastCompletedRegularFrameNumber = - resultExtras.getLastCompletedRegularFrameNumber(); - final long lastCompletedReprocessFrameNumber = - resultExtras.getLastCompletedReprocessFrameNumber(); - final long lastCompletedZslFrameNumber = - resultExtras.getLastCompletedZslFrameNumber(); final CaptureCallbackHolder holder; synchronized(mInterfaceLock) { - // Check if it's okay to remove completed callbacks from mCaptureCallbackMap. - // A callback is completed if the corresponding inflight request has been removed - // from the inflight queue in cameraservice. - removeCompletedCallbackHolderLocked(lastCompletedRegularFrameNumber, - lastCompletedReprocessFrameNumber, lastCompletedZslFrameNumber); - // Get the callback for this frame ID, if there is one holder = CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId); @@ -619,61 +601,6 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession } } - private void removeCompletedCallbackHolderLocked(long lastCompletedRegularFrameNumber, - long lastCompletedReprocessFrameNumber, long lastCompletedZslStillFrameNumber) { - if (DEBUG) { - Log.v(TAG, String.format("remove completed callback holders for " - + "lastCompletedRegularFrameNumber %d, " - + "lastCompletedReprocessFrameNumber %d, " - + "lastCompletedZslStillFrameNumber %d", - lastCompletedRegularFrameNumber, - lastCompletedReprocessFrameNumber, - lastCompletedZslStillFrameNumber)); - } - - boolean isReprocess = false; - Iterator<RequestLastFrameNumbersHolder> iter = - mOfflineRequestLastFrameNumbersList.iterator(); - while (iter.hasNext()) { - final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next(); - final int requestId = requestLastFrameNumbers.getRequestId(); - final CaptureCallbackHolder holder; - - int index = mCaptureCallbackMap.indexOfKey(requestId); - holder = (index >= 0) ? - mCaptureCallbackMap.valueAt(index) : null; - if (holder != null) { - long lastRegularFrameNumber = - requestLastFrameNumbers.getLastRegularFrameNumber(); - long lastReprocessFrameNumber = - requestLastFrameNumbers.getLastReprocessFrameNumber(); - long lastZslStillFrameNumber = - requestLastFrameNumbers.getLastZslStillFrameNumber(); - if (lastRegularFrameNumber <= lastCompletedRegularFrameNumber - && lastReprocessFrameNumber <= lastCompletedReprocessFrameNumber - && lastZslStillFrameNumber <= lastCompletedZslStillFrameNumber) { - if (requestLastFrameNumbers.isSequenceCompleted()) { - mCaptureCallbackMap.removeAt(index); - if (DEBUG) { - Log.v(TAG, String.format( - "Remove holder for requestId %d, because lastRegularFrame %d " - + "is <= %d, lastReprocessFrame %d is <= %d, " - + "lastZslStillFrame %d is <= %d", requestId, - lastRegularFrameNumber, lastCompletedRegularFrameNumber, - lastReprocessFrameNumber, lastCompletedReprocessFrameNumber, - lastZslStillFrameNumber, lastCompletedZslStillFrameNumber)); - } - - iter.remove(); - } else { - Log.e(TAG, "Sequence not yet completed for request id " + requestId); - continue; - } - } - } - } - } - public void notifyFailedSwitch() { synchronized(mInterfaceLock) { Runnable switchFailDispatch = new Runnable() { diff --git a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java index 5d9da73fd5c0..1ff5bd562f2e 100644 --- a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java +++ b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java @@ -30,9 +30,6 @@ public class CaptureResultExtras implements Parcelable { private int partialResultCount; private int errorStreamId; private String errorPhysicalCameraId; - private long lastCompletedRegularFrameNumber; - private long lastCompletedReprocessFrameNumber; - private long lastCompletedZslFrameNumber; public static final @android.annotation.NonNull Parcelable.Creator<CaptureResultExtras> CREATOR = new Parcelable.Creator<CaptureResultExtras>() { @@ -54,9 +51,7 @@ public class CaptureResultExtras implements Parcelable { public CaptureResultExtras(int requestId, int subsequenceId, int afTriggerId, int precaptureTriggerId, long frameNumber, int partialResultCount, int errorStreamId, - String errorPhysicalCameraId, long lastCompletedRegularFrameNumber, - long lastCompletedReprocessFrameNumber, - long lastCompletedZslFrameNumber) { + String errorPhysicalCameraId) { this.requestId = requestId; this.subsequenceId = subsequenceId; this.afTriggerId = afTriggerId; @@ -65,9 +60,6 @@ public class CaptureResultExtras implements Parcelable { this.partialResultCount = partialResultCount; this.errorStreamId = errorStreamId; this.errorPhysicalCameraId = errorPhysicalCameraId; - this.lastCompletedRegularFrameNumber = lastCompletedRegularFrameNumber; - this.lastCompletedReprocessFrameNumber = lastCompletedReprocessFrameNumber; - this.lastCompletedZslFrameNumber = lastCompletedZslFrameNumber; } @Override @@ -90,9 +82,6 @@ public class CaptureResultExtras implements Parcelable { } else { dest.writeBoolean(false); } - dest.writeLong(lastCompletedRegularFrameNumber); - dest.writeLong(lastCompletedReprocessFrameNumber); - dest.writeLong(lastCompletedZslFrameNumber); } public void readFromParcel(Parcel in) { @@ -107,9 +96,6 @@ public class CaptureResultExtras implements Parcelable { if (errorPhysicalCameraIdPresent) { errorPhysicalCameraId = in.readString(); } - lastCompletedRegularFrameNumber = in.readLong(); - lastCompletedReprocessFrameNumber = in.readLong(); - lastCompletedZslFrameNumber = in.readLong(); } public String getErrorPhysicalCameraId() { @@ -143,16 +129,4 @@ public class CaptureResultExtras implements Parcelable { public int getErrorStreamId() { return errorStreamId; } - - public long getLastCompletedRegularFrameNumber() { - return lastCompletedRegularFrameNumber; - } - - public long getLastCompletedReprocessFrameNumber() { - return lastCompletedReprocessFrameNumber; - } - - public long getLastCompletedZslFrameNumber() { - return lastCompletedZslFrameNumber; - } } diff --git a/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java b/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java index 0ee4ebc1aa87..bd1df9e1ac7d 100644 --- a/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java +++ b/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java @@ -38,10 +38,6 @@ public class RequestLastFrameNumbersHolder { // The last ZSL still capture frame number for this request ID. It's // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no zsl request. private final long mLastZslStillFrameNumber; - // Whether the sequence is completed. (only consider capture result) - private boolean mSequenceCompleted; - // Whether the inflight request is completed. (consider result, buffers, and notifies) - private boolean mInflightCompleted; /** * Create a request-last-frame-numbers holder with a list of requests, request ID, and @@ -93,8 +89,6 @@ public class RequestLastFrameNumbersHolder { mLastReprocessFrameNumber = lastReprocessFrameNumber; mLastZslStillFrameNumber = lastZslStillFrameNumber; mRequestId = requestInfo.getRequestId(); - mSequenceCompleted = false; - mInflightCompleted = false; } /** @@ -143,8 +137,6 @@ public class RequestLastFrameNumbersHolder { mLastZslStillFrameNumber = lastZslStillFrameNumber; mLastReprocessFrameNumber = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED; mRequestId = requestId; - mSequenceCompleted = false; - mInflightCompleted = false; } /** @@ -185,34 +177,5 @@ public class RequestLastFrameNumbersHolder { public int getRequestId() { return mRequestId; } - - /** - * Return whether the capture sequence is completed. - */ - public boolean isSequenceCompleted() { - return mSequenceCompleted; - } - - /** - * Mark the capture sequence as completed. - */ - public void markSequenceCompleted() { - mSequenceCompleted = true; - } - - /** - * Return whether the inflight capture is completed. - */ - public boolean isInflightCompleted() { - return mInflightCompleted; - } - - /** - * Mark the inflight capture as completed. - */ - public void markInflightCompleted() { - mInflightCompleted = true; - } - } diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index fdd578c419d8..fbc9ac3229c3 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -109,12 +109,11 @@ public class LegacyCameraDevice implements AutoCloseable { } if (holder == null) { return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, - ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null, - ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE); + ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null); } return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(), /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(), - /*partialResultCount*/1, errorStreamId, null, holder.getFrameNumber(), -1, -1); + /*partialResultCount*/1, errorStreamId, null); } /** diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index 425218a63cf7..c4d123ca4382 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -195,11 +195,14 @@ class ConversionUtil { public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent( int modelHandle, RecognitionEvent aidlEvent) { + // The API recognition event doesn't allow for a null audio format, even though it doesn't + // always make sense. We thus replace it with a default. + AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.audioConfig); return new SoundTrigger.GenericRecognitionEvent( aidlEvent.status, modelHandle, aidlEvent.captureAvailable, aidlEvent.captureSession, aidlEvent.captureDelayMs, aidlEvent.capturePreambleMs, aidlEvent.triggerInData, - aidl2apiAudioFormat(aidlEvent.audioConfig), aidlEvent.data); + audioFormat, aidlEvent.data); } public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent( @@ -210,11 +213,14 @@ class ConversionUtil { for (int i = 0; i < aidlEvent.phraseExtras.length; ++i) { apiExtras[i] = aidl2apiPhraseRecognitionExtra(aidlEvent.phraseExtras[i]); } + // The API recognition event doesn't allow for a null audio format, even though it doesn't + // always make sense. We thus replace it with a default. + AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.common.audioConfig); return new SoundTrigger.KeyphraseRecognitionEvent(aidlEvent.common.status, modelHandle, aidlEvent.common.captureAvailable, aidlEvent.common.captureSession, aidlEvent.common.captureDelayMs, aidlEvent.common.capturePreambleMs, aidlEvent.common.triggerInData, - aidl2apiAudioFormat(aidlEvent.common.audioConfig), aidlEvent.common.data, + audioFormat, aidlEvent.common.data, apiExtras); } @@ -226,6 +232,14 @@ class ConversionUtil { return apiBuilder.build(); } + // Same as above, but in case of a null input returns a non-null valid output. + public static AudioFormat aidl2apiAudioFormatWithDefault(@Nullable AudioConfig audioConfig) { + if (audioConfig != null) { + return aidl2apiAudioFormat(audioConfig); + } + return new AudioFormat.Builder().build(); + } + public static int aidl2apiEncoding(int aidlFormat) { switch (aidlFormat) { case android.media.audio.common.AudioFormat.PCM diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java index c1df5b6d2e7e..a2a15b30d578 100644 --- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java +++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java @@ -303,7 +303,7 @@ public class SoundTriggerModule { (SoundTrigger.RecognitionEvent) msg.obj); break; case EVENT_SERVICE_STATE_CHANGE: - listener.onServiceStateChange(msg.arg1); + listener.onServiceStateChange((int) msg.obj); break; case EVENT_SERVICE_DIED: listener.onServiceDied(); diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 65e772cb5ebb..482d2d2192b8 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -357,6 +357,7 @@ public abstract class NetworkAgent { final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacyType, config.legacyTypeName, ""); ni.setIsAvailable(true); + ni.setExtraInfo(config.getLegacyExtraInfo()); return ni; } diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java index fee868a93be4..fe1268d79b89 100644 --- a/core/java/android/net/NetworkAgentConfig.java +++ b/core/java/android/net/NetworkAgentConfig.java @@ -185,6 +185,26 @@ public final class NetworkAgentConfig implements Parcelable { return legacyTypeName; } + /** + * The legacy extra info of the agent. The extra info should only be : + * <ul> + * <li>For cellular agents, the APN name.</li> + * <li>For ethernet agents, the interface name.</li> + * </ul> + * @hide + */ + @NonNull + private String mLegacyExtraInfo = ""; + + /** + * The legacy extra info of the agent. + * @hide + */ + @NonNull + public String getLegacyExtraInfo() { + return mLegacyExtraInfo; + } + /** @hide */ public NetworkAgentConfig() { } @@ -201,6 +221,7 @@ public final class NetworkAgentConfig implements Parcelable { skip464xlat = nac.skip464xlat; legacyType = nac.legacyType; legacyTypeName = nac.legacyTypeName; + mLegacyExtraInfo = nac.mLegacyExtraInfo; } } @@ -309,6 +330,18 @@ public final class NetworkAgentConfig implements Parcelable { } /** + * Sets the legacy extra info of the agent. + * @param legacyExtraInfo the legacy extra info. + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + public Builder setLegacyExtraInfo(@NonNull String legacyExtraInfo) { + mConfig.mLegacyExtraInfo = legacyExtraInfo; + return this; + } + + /** * Returns the constructed {@link NetworkAgentConfig} object. */ @NonNull @@ -330,14 +363,15 @@ public final class NetworkAgentConfig implements Parcelable { && skip464xlat == that.skip464xlat && legacyType == that.legacyType && Objects.equals(subscriberId, that.subscriberId) - && Objects.equals(legacyTypeName, that.legacyTypeName); + && Objects.equals(legacyTypeName, that.legacyTypeName) + && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo); } @Override public int hashCode() { return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated, acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId, - skip464xlat, legacyType, legacyTypeName); + skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo); } @Override @@ -353,6 +387,7 @@ public final class NetworkAgentConfig implements Parcelable { + ", legacyType = " + legacyType + ", hasShownBroken = " + hasShownBroken + ", legacyTypeName = '" + legacyTypeName + '\'' + + ", legacyExtraInfo = '" + mLegacyExtraInfo + '\'' + "}"; } @@ -372,6 +407,7 @@ public final class NetworkAgentConfig implements Parcelable { out.writeInt(skip464xlat ? 1 : 0); out.writeInt(legacyType); out.writeString(legacyTypeName); + out.writeString(mLegacyExtraInfo); } public static final @NonNull Creator<NetworkAgentConfig> CREATOR = @@ -388,6 +424,7 @@ public final class NetworkAgentConfig implements Parcelable { networkAgentConfig.skip464xlat = in.readInt() != 0; networkAgentConfig.legacyType = in.readInt(); networkAgentConfig.legacyTypeName = in.readString(); + networkAgentConfig.mLegacyExtraInfo = in.readString(); return networkAgentConfig; } diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index 21738d80f2d3..8ba5c173890c 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -62,9 +62,10 @@ public final class FillCallback { List<Dataset> inlineSuggestions = response.getInlineSuggestions(); Bundle clientState = response.getClientState(); + // We need to report result regardless of whether inline suggestions are returned or not. + mProxy.reportResult(inlineSuggestions, clientState); if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) { mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE); - mProxy.reportResult(inlineSuggestions, clientState); return; } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index e4d53c64a063..9a9396c45b66 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -575,21 +575,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public boolean onStateChanged(InsetsState state) { - boolean localStateChanged = !mState.equals(state, true /* excludingCaptionInsets */) + boolean stateChanged = !mState.equals(state, true /* excludingCaptionInsets */, + false /* excludeInvisibleIme */) || !captionInsetsUnchanged(); - if (!localStateChanged && mLastDispatchedState.equals(state)) { + if (!stateChanged && mLastDispatchedState.equals(state)) { return false; } if (DEBUG) Log.d(TAG, "onStateChanged: " + state); updateState(state); + + boolean localStateChanged = !mState.equals(mLastDispatchedState, + true /* excludingCaptionInsets */, true /* excludeInvisibleIme */); mLastDispatchedState.set(state, true /* copySources */); + applyLocalVisibilityOverride(); if (localStateChanged) { - if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged"); + if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged, send state to WM: " + mState); mHost.notifyInsetsChanged(); - } - if (!mState.equals(mLastDispatchedState, true /* excludingCaptionInsets */)) { - if (DEBUG) Log.d(TAG, "onStateChanged, send state to WM: " + mState); updateRequestedState(); } return true; diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index b0158467a17b..15b9a9330392 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -191,6 +191,14 @@ public class InsetsSource implements Parcelable { @Override public boolean equals(Object o) { + return equals(o, false); + } + + /** + * @param excludeInvisibleImeFrames If {@link InsetsState#ITYPE_IME} frames should be ignored + * when IME is not visible. + */ + public boolean equals(Object o, boolean excludeInvisibleImeFrames) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -198,6 +206,7 @@ public class InsetsSource implements Parcelable { if (mType != that.mType) return false; if (mVisible != that.mVisible) return false; + if (excludeInvisibleImeFrames && !mVisible && mType == ITYPE_IME) return true; if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; return mFrame.equals(that.mFrame); } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index ae70a4971776..3aa246441dbc 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -18,8 +18,8 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.AnimationType; -import static android.view.InsetsState.getDefaultVisibility; import static android.view.InsetsController.DEBUG; +import static android.view.InsetsState.getDefaultVisibility; import static android.view.InsetsState.toPublicType; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; @@ -221,9 +221,10 @@ public class InsetsSourceConsumer { final boolean hasControl = mSourceControl != null; // We still need to let the legacy app know the visibility change even if we don't have the - // control. + // control. If we don't have the source, we don't change the requested visibility for making + // the callback behavior compatible. mController.updateCompatSysUiVisibility( - mType, hasControl ? mRequestedVisible : isVisible, hasControl); + mType, (hasControl || source == null) ? mRequestedVisible : isVisible, hasControl); // If we don't have control, we are not able to change the visibility. if (!hasControl) { diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index f0bca260be38..397b04e9c023 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -555,7 +555,7 @@ public class InsetsState implements Parcelable { @Override public boolean equals(Object o) { - return equals(o, false); + return equals(o, false, false); } /** @@ -564,10 +564,13 @@ public class InsetsState implements Parcelable { * excluded. * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but * ignore the caption insets source value. + * @param excludeInvisibleImeFrames If {@link #ITYPE_IME} frames should be ignored when IME is + * not visible. * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise. */ @VisibleForTesting - public boolean equals(Object o, boolean excludingCaptionInsets) { + public boolean equals(Object o, boolean excludingCaptionInsets, + boolean excludeInvisibleImeFrames) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } @@ -598,7 +601,7 @@ public class InsetsState implements Parcelable { if (otherSource == null) { return false; } - if (!otherSource.equals(source)) { + if (!otherSource.equals(source, excludeInvisibleImeFrames)) { return false; } } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 2d4d9575584d..5af9b81ac032 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -2816,8 +2816,7 @@ public class ChooserActivity extends ResolverActivity implements } // no need to query direct share for work profile when its turned off - UserManager userManager = getSystemService(UserManager.class); - if (userManager.isQuietModeEnabled(chooserListAdapter.getUserHandle())) { + if (isQuietModeEnabled(chooserListAdapter.getUserHandle())) { getChooserActivityLogger().logSharesheetAppLoadComplete(); return; } @@ -2841,6 +2840,12 @@ public class ChooserActivity extends ResolverActivity implements getChooserActivityLogger().logSharesheetAppLoadComplete(); } + @VisibleForTesting + protected boolean isQuietModeEnabled(UserHandle userHandle) { + UserManager userManager = getSystemService(UserManager.class); + return userManager.isQuietModeEnabled(userHandle); + } + private void setupScrollListener() { if (mResolverDrawerLayout == null) { return; @@ -3592,10 +3597,9 @@ public class ChooserActivity extends ResolverActivity implements * Only expand direct share area if there is a minimum number of targets. */ private boolean canExpandDirectShare() { - int orientation = getResources().getConfiguration().orientation; - return mChooserListAdapter.getNumServiceTargetsForExpand() > getMaxTargetsPerRow() - && orientation == Configuration.ORIENTATION_PORTRAIT - && !isInMultiWindowMode(); + // Do not enable until we have confirmed more apps are using sharing shortcuts + // Check git history for enablement logic + return false; } public ChooserListAdapter getListAdapter() { diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index f4fb993fbb93..d6ff7b13c934 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -181,6 +181,7 @@ public class ChooserListAdapter extends ResolverListAdapter { ri.icon = 0; } mCallerTargets.add(new DisplayResolveInfo(ii, ri, ii, makePresentationGetter(ri))); + if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break; } } } @@ -320,7 +321,7 @@ public class ChooserListAdapter extends ResolverListAdapter { public int getCallerTargetCount() { - return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS); + return mCallerTargets.size(); } /** @@ -346,8 +347,9 @@ public class ChooserListAdapter extends ResolverListAdapter { } int getAlphaTargetCount() { - int standardCount = mSortedList.size(); - return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0; + int groupedCount = mSortedList.size(); + int ungroupedCount = mCallerTargets.size() + mDisplayList.size(); + return ungroupedCount > mChooserListCommunicator.getMaxRankedTargets() ? groupedCount : 0; } /** diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index b1e8ed1f943e..d63ebda5117e 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -617,7 +617,8 @@ public class ResolverListAdapter extends BaseAdapter { } } - UserHandle getUserHandle() { + @VisibleForTesting + public UserHandle getUserHandle() { return mResolverListController.getUserHandle(); } diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java index ffe2dbe4ccc0..2d91e64b2e67 100644 --- a/core/java/com/android/internal/app/SimpleIconFactory.java +++ b/core/java/com/android/internal/app/SimpleIconFactory.java @@ -19,6 +19,7 @@ package com.android.internal.app; import static android.content.Context.ACTIVITY_SERVICE; import static android.graphics.Paint.DITHER_FLAG; import static android.graphics.Paint.FILTER_BITMAP_FLAG; +import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction; import android.annotation.AttrRes; import android.annotation.NonNull; @@ -54,6 +55,7 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import java.nio.ByteBuffer; +import java.util.Optional; /** @@ -64,6 +66,7 @@ import java.nio.ByteBuffer; @Deprecated public class SimpleIconFactory { + private static final SynchronizedPool<SimpleIconFactory> sPool = new SynchronizedPool<>(Runtime.getRuntime().availableProcessors()); @@ -251,7 +254,7 @@ public class SimpleIconFactory { } else if (w > h && h > 0) { scale = (float) w / h; } - Bitmap bitmap = createIconBitmap(icon, scale); + Bitmap bitmap = createIconBitmapNoInsetOrMask(icon, scale); bitmap = maskBitmapToCircle(bitmap); icon = new BitmapDrawable(mContext.getResources(), bitmap); @@ -281,15 +284,19 @@ public class SimpleIconFactory { final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(output); - final Paint paint = new Paint(); - paint.setAntiAlias(true); + final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG + | Paint.FILTER_BITMAP_FLAG); + + // Apply an offset to enable shadow to be drawn + final int size = bitmap.getWidth(); + int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), 1); // Draw mask paint.setColor(0xffffffff); canvas.drawARGB(0, 0, 0, 0); canvas.drawCircle(bitmap.getWidth() / 2f, bitmap.getHeight() / 2f, - bitmap.getWidth() / 2f - 1 /* -1 to avoid circles with flat sides */, + bitmap.getWidth() / 2f - offset, paint); // Draw masked bitmap @@ -306,24 +313,61 @@ public class SimpleIconFactory { } private Bitmap createIconBitmap(Drawable icon, float scale) { - return createIconBitmap(icon, scale, mIconBitmapSize); + return createIconBitmap(icon, scale, mIconBitmapSize, true, false); + } + + private Bitmap createIconBitmapNoInsetOrMask(Drawable icon, float scale) { + return createIconBitmap(icon, scale, mIconBitmapSize, false, true); } /** * @param icon drawable that should be flattened to a bitmap * @param scale the scale to apply before drawing {@param icon} on the canvas + * @param insetAdiForShadow when rendering AdaptiveIconDrawables inset to make room for a shadow + * @param ignoreAdiMask when rendering AdaptiveIconDrawables ignore the current system mask */ - private Bitmap createIconBitmap(Drawable icon, float scale, int size) { + private Bitmap createIconBitmap(Drawable icon, float scale, int size, boolean insetAdiForShadow, + boolean ignoreAdiMask) { Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); mCanvas.setBitmap(bitmap); mOldBounds.set(icon.getBounds()); if (icon instanceof AdaptiveIconDrawable) { - int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), - Math.round(size * (1 - scale) / 2)); - icon.setBounds(offset, offset, size - offset, size - offset); - icon.draw(mCanvas); + final AdaptiveIconDrawable adi = (AdaptiveIconDrawable) icon; + + // By default assumes the output bitmap will have a shadow directly applied and makes + // room for it by insetting. If there are intermediate steps before applying the shadow + // insetting is disableable. + int offset = Math.round(size * (1 - scale) / 2); + if (insetAdiForShadow) { + offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), offset); + } + Rect bounds = new Rect(offset, offset, size - offset, size - offset); + + // AdaptiveIconDrawables are by default masked by the user's icon shape selection. + // If further masking is to be done, directly render to avoid the system masking. + if (ignoreAdiMask) { + final int cX = bounds.width() / 2; + final int cY = bounds.height() / 2; + final float portScale = 1f / (1 + 2 * getExtraInsetFraction()); + final int insetWidth = (int) (bounds.width() / (portScale * 2)); + final int insetHeight = (int) (bounds.height() / (portScale * 2)); + + Rect childRect = new Rect(cX - insetWidth, cY - insetHeight, cX + insetWidth, + cY + insetHeight); + Optional.ofNullable(adi.getBackground()).ifPresent(drawable -> { + drawable.setBounds(childRect); + drawable.draw(mCanvas); + }); + Optional.ofNullable(adi.getForeground()).ifPresent(drawable -> { + drawable.setBounds(childRect); + drawable.draw(mCanvas); + }); + } else { + adi.setBounds(bounds); + adi.draw(mCanvas); + } } else { if (icon instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java index 86a9af3db196..fe0e7d012262 100644 --- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java +++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java @@ -40,8 +40,10 @@ import java.util.List; * resolve it to an activity. */ public class DisplayResolveInfo implements TargetInfo { - // Temporary flag for new chooser delegate behavior. - private static final boolean ENABLE_CHOOSER_DELEGATE = true; + // Temporary flag for new chooser delegate behavior. There are occassional token + // permission errors from bouncing through the delegate. Watch out before reenabling: + // b/157272342 is one example but this issue has been reported many times + private static final boolean ENABLE_CHOOSER_DELEGATE = false; private final ResolveInfo mResolveInfo; private CharSequence mDisplayLabel; diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 7c7d9312e2db..075159671b17 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -761,9 +761,13 @@ public class ConversationLayout extends FrameLayout // group : mExpandedGroupMessagePadding; + int iconPadding = mIsOneToOne || mIsCollapsed + ? mConversationIconTopPadding + : mConversationIconTopPaddingExpandedGroup; + mConversationIconContainer.setPaddingRelative( mConversationIconContainer.getPaddingStart(), - mConversationIconTopPadding, + iconPadding, mConversationIconContainer.getPaddingEnd(), mConversationIconContainer.getPaddingBottom()); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 03a7b3da6251..93690cdfc811 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1430,6 +1430,32 @@ public class LockPatternUtils { == StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; } + private static class WrappedCallback extends ICheckCredentialProgressCallback.Stub { + + private Handler mHandler; + private CheckCredentialProgressCallback mCallback; + + WrappedCallback(Handler handler, CheckCredentialProgressCallback callback) { + mHandler = handler; + mCallback = callback; + } + + @Override + public void onCredentialVerified() throws RemoteException { + if (mHandler == null) { + Log.e(TAG, "Handler is null during callback"); + } + // Kill reference immediately to allow early GC at client side independent of + // when system_server decides to lose its reference to the + // ICheckCredentialProgressCallback binder object. + mHandler.post(() -> { + mCallback.onEarlyMatched(); + mCallback = null; + }); + mHandler = null; + } + } + private ICheckCredentialProgressCallback wrapCallback( final CheckCredentialProgressCallback callback) { if (callback == null) { @@ -1439,13 +1465,7 @@ public class LockPatternUtils { throw new IllegalStateException("Must construct LockPatternUtils on a looper thread" + " to use progress callbacks."); } - return new ICheckCredentialProgressCallback.Stub() { - - @Override - public void onCredentialVerified() throws RemoteException { - mHandler.post(callback::onEarlyMatched); - } - }; + return new WrappedCallback(mHandler, callback); } } diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml index d3857941969b..139185f98b69 100644 --- a/core/res/res/layout/notification_template_material_conversation.xml +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -148,7 +148,6 @@ android:layout_weight="1" /> - <TextView android:id="@+id/app_name_divider" android:layout_width="wrap_content" @@ -174,6 +173,7 @@ android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin" android:paddingTop="1sp" android:singleLine="true" + android:visibility="gone" /> <TextView @@ -192,7 +192,7 @@ <DateTimeView android:id="@+id/time" - android:textAppearance="@style/TextAppearance.Material.Notification.Time" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b79c9e804f89..0e11d49c93e5 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4313,10 +4313,6 @@ notifications until they target R --> <string-array name="config_notificationMsgPkgsAllowedAsConvos" translatable="false"/> - <!-- Contains a blacklist of apps that should not get pre-installed carrier app permission - grants, even if the UICC claims that the app should be privileged. See b/138150105 --> - <string-array name="config_restrictedPreinstalledCarrierApps" translatable="false"/> - <!-- Sharesheet: define a max number of targets per application for new shortcuts-based direct share introduced in Q --> <integer name="config_maxShortcutTargetsPerApp">3</integer> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 4ee919289f28..ebaf85c64a14 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -734,7 +734,7 @@ <dimen name="conversation_icon_container_top_padding">12dp</dimen> <!-- The top padding of the conversation icon container when the avatar is small--> - <dimen name="conversation_icon_container_top_padding_small_avatar">17.5dp</dimen> + <dimen name="conversation_icon_container_top_padding_small_avatar">9dp</dimen> <!-- The padding of the conversation header when expanded. This is calculated from the expand button size + notification_content_margin_end --> <dimen name="conversation_header_expanded_padding_end">38dp</dimen> diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml index e9ac679ec39c..ef019ba92769 100644 --- a/core/res/res/values/styles_device_defaults.xml +++ b/core/res/res/values/styles_device_defaults.xml @@ -296,9 +296,12 @@ easier. <style name="TextAppearance.DeviceDefault.Notification.Info" parent="TextAppearance.Material.Notification.Info"> <item name="fontFamily">@string/config_bodyFontFamily</item> </style> + <style name="TextAppearance.DeviceDefault.Notification.Time" parent="TextAppearance.Material.Notification.Time"> + <item name="fontFamily">@string/config_bodyFontFamily</item> + </style> <style name="TextAppearance.DeviceDefault.Notification.Conversation.AppName" - parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title"> - <item name="android:textSize">16sp</item> + parent="TextAppearance.Material.Notification.Conversation.AppName"> + <item name="fontFamily">@string/config_headlineFontFamilyMedium</item> </style> <style name="TextAppearance.DeviceDefault.Widget" parent="TextAppearance.Material.Widget"> <item name="fontFamily">@string/config_bodyFontFamily</item> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 2415837cf826..67536fde9b0b 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -491,6 +491,10 @@ please see styles_device_defaults.xml. <item name="textColor">#66000000</item> </style> + <style name="TextAppearance.Material.Notification.Conversation.AppName" parent="TextAppearance.Material.Notification.Title"> + <item name="android:textSize">16sp</item> + </style> + <style name="TextAppearance.Material.ListItem" parent="TextAppearance.Material.Subhead" /> <style name="TextAppearance.Material.ListItemSecondary" parent="TextAppearance.Material.Body1" /> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 23ae1e7d271e..c56c78a0b6a1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3827,7 +3827,6 @@ <java-symbol type="string" name="config_defaultSupervisionProfileOwnerComponent" /> <java-symbol type="bool" name="config_inflateSignalStrength" /> - <java-symbol type="array" name="config_restrictedPreinstalledCarrierApps" /> <java-symbol type="drawable" name="android_logotype" /> <java-symbol type="layout" name="platlogo_layout" /> @@ -3956,6 +3955,13 @@ <java-symbol type="id" name="conversation_unread_count" /> <java-symbol type="string" name="unread_convo_overflow" /> <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Conversation.AppName" /> + <java-symbol type="drawable" name="conversation_badge_background" /> + <java-symbol type="drawable" name="conversation_badge_ring" /> + <java-symbol type="color" name="conversation_important_highlight" /> + <java-symbol type="dimen" name="importance_ring_stroke_width" /> + <java-symbol type="dimen" name="importance_ring_anim_max_stroke_width" /> + <java-symbol type="dimen" name="importance_ring_size" /> + <java-symbol type="dimen" name="conversation_icon_size_badged" /> <!-- Intent resolver and share sheet --> <java-symbol type="string" name="resolver_personal_tab" /> diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index d4c256972b28..964ae21d6086 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -678,7 +678,8 @@ public class InsetsControllerTest { final InsetsState currentState = new InsetsState(mController.getState()); // The caption bar source should be synced with the info in mAttachInfo. assertEquals(captionFrame, currentState.peekSource(ITYPE_CAPTION_BAR).getFrame()); - assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/)); + assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/, + true /* excludeInvisibleIme */)); mController.setCaptionInsetsHeight(0); mController.onStateChanged(state); // The caption bar source should not be there at all, because we don't add empty diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index cd93eeb857f1..7115acfedcf6 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -276,6 +276,15 @@ public class InsetsStateTest { } @Test + public void testEquals_excludeInvisibleIme() { + mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_IME).setVisible(false); + mState2.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 200)); + mState2.getSource(ITYPE_IME).setVisible(false); + assertTrue(mState2.equals(mState, true, true /* excludeInvisibleIme */)); + } + + @Test public void testParcelUnparcel() { mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100)); mState.getSource(ITYPE_IME).setVisibleFrame(new Rect(0, 0, 50, 10)); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index 547176855f32..49de7c80057f 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -21,6 +21,7 @@ import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.swipeUp; import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.hasSibling; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; @@ -299,9 +300,8 @@ public class ChooserActivityTest { public void fourOptionsStackedIntoOneTarget() throws InterruptedException { Intent sendIntent = createSendTextIntent(); - // create 12 unique app targets to ensure the app ranking row can be filled, otherwise - // targets will not stack - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(12); + // create just enough targets to ensure the a-z list should be shown + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(1); // next create 4 targets in a single app that should be stacked into a single target String packageName = "xxx.yyy"; @@ -328,8 +328,8 @@ public class ChooserActivityTest { .launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - // expect 12 unique targets + 1 group + 4 ranked app targets - assertThat(activity.getAdapter().getCount(), is(17)); + // expect 1 unique targets + 1 group + 4 ranked app targets + assertThat(activity.getAdapter().getCount(), is(6)); ResolveInfo[] chosen = new ResolveInfo[1]; sOverrides.onSafelyStartCallback = targetInfo -> { @@ -337,7 +337,7 @@ public class ChooserActivityTest { return true; }; - onView(withText(appName)).perform(click()); + onView(allOf(withText(appName), hasSibling(withText("")))).perform(click()); waitForIdle(); // clicking will launch a dialog to choose the activity within the app diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index 44a52639bd35..0f6b51f82116 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -222,6 +222,11 @@ public class ChooserWrapperActivity extends ChooserActivity { super.queryTargetServices(adapter); } + @Override + protected boolean isQuietModeEnabled(UserHandle userHandle) { + return sOverrides.isQuietModeEnabled; + } + /** * We cannot directly mock the activity created since instrumentation creates it. * <p> diff --git a/core/tests/overlaytests/remount/Android.bp b/core/tests/overlaytests/remount/Android.bp index 4e79a4574af4..939334c94312 100644 --- a/core/tests/overlaytests/remount/Android.bp +++ b/core/tests/overlaytests/remount/Android.bp @@ -19,6 +19,9 @@ java_test_host { "tradefed", "junit", ], + static_libs: [ + "frameworks-base-hostutils", + ], test_suites: ["general-tests"], java_resources: [ ":com.android.overlaytest.overlaid", diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java index 14b5bf6eacba..1a39e20f9a2b 100644 --- a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java +++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java @@ -18,6 +18,7 @@ package com.android.overlaytest.remounted; import static org.junit.Assert.fail; +import com.android.internal.util.test.SystemPreparer; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java deleted file mode 100644 index bb72d0ee1d03..000000000000 --- a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.overlaytest.remounted; - -import static org.junit.Assert.assertTrue; - -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; - -import org.junit.Assert; -import org.junit.rules.ExternalResource; -import org.junit.rules.TemporaryFolder; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; - -class SystemPreparer extends ExternalResource { - private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000; - - // The paths of the files pushed onto the device through this rule. - private ArrayList<String> mPushedFiles = new ArrayList<>(); - - // The package names of packages installed through this rule. - private ArrayList<String> mInstalledPackages = new ArrayList<>(); - - private final TemporaryFolder mHostTempFolder; - private final DeviceProvider mDeviceProvider; - - SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) { - mHostTempFolder = hostTempFolder; - mDeviceProvider = deviceProvider; - } - - /** Copies a file within the host test jar to a path on device. */ - SystemPreparer pushResourceFile(String resourcePath, - String outputPath) throws DeviceNotAvailableException, IOException { - final ITestDevice device = mDeviceProvider.getDevice(); - remount(); - assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath)); - mPushedFiles.add(outputPath); - return this; - } - - /** Installs an APK within the host test jar onto the device. */ - SystemPreparer installResourceApk(String resourcePath, String packageName) - throws DeviceNotAvailableException, IOException { - final ITestDevice device = mDeviceProvider.getDevice(); - final File tmpFile = copyResourceToTemp(resourcePath); - final String result = device.installPackage(tmpFile, true /* reinstall */); - Assert.assertNull(result); - mInstalledPackages.add(packageName); - return this; - } - - /** Sets the enable state of an overlay package. */ - SystemPreparer setOverlayEnabled(String packageName, boolean enabled) - throws DeviceNotAvailableException { - final ITestDevice device = mDeviceProvider.getDevice(); - final String enable = enabled ? "enable" : "disable"; - - // Wait for the overlay to change its enabled state. - final long endMillis = System.currentTimeMillis() + OVERLAY_ENABLE_TIMEOUT_MS; - String result; - while (System.currentTimeMillis() <= endMillis) { - device.executeShellCommand(String.format("cmd overlay %s %s", enable, packageName)); - result = device.executeShellCommand("cmd overlay dump isenabled " - + packageName); - if (((enabled) ? "true\n" : "false\n").equals(result)) { - return this; - } - - try { - Thread.sleep(200); - } catch (InterruptedException ignore) { - } - } - - throw new IllegalStateException(String.format("Failed to %s overlay %s:\n%s", enable, - packageName, device.executeShellCommand("cmd overlay list"))); - } - - /** Restarts the device and waits until after boot is completed. */ - SystemPreparer reboot() throws DeviceNotAvailableException { - final ITestDevice device = mDeviceProvider.getDevice(); - device.reboot(); - return this; - } - - SystemPreparer remount() throws DeviceNotAvailableException { - mDeviceProvider.getDevice().executeAdbCommand("remount"); - return this; - } - - /** Copies a file within the host test jar to a temporary file on the host machine. */ - private File copyResourceToTemp(String resourcePath) throws IOException { - final File tempFile = mHostTempFolder.newFile(resourcePath); - final ClassLoader classLoader = getClass().getClassLoader(); - try (InputStream assetIs = classLoader.getResource(resourcePath).openStream(); - FileOutputStream assetOs = new FileOutputStream(tempFile)) { - if (assetIs == null) { - throw new IllegalStateException("Failed to find resource " + resourcePath); - } - - int b; - while ((b = assetIs.read()) >= 0) { - assetOs.write(b); - } - } - - return tempFile; - } - - /** Removes installed packages and files that were pushed to the device. */ - @Override - protected void after() { - final ITestDevice device = mDeviceProvider.getDevice(); - try { - remount(); - for (final String file : mPushedFiles) { - device.deleteFile(file); - } - for (final String packageName : mInstalledPackages) { - device.uninstallPackage(packageName); - } - device.reboot(); - } catch (DeviceNotAvailableException e) { - Assert.fail(e.toString()); - } - } - - interface DeviceProvider { - ITestDevice getDevice(); - } -} diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index e5ad569bb24f..54c0bc94c2d0 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -317,9 +317,10 @@ public final class MediaRoute2Info implements Parcelable { @ConnectionState final int mConnectionState; final String mClientPackageName; - final int mVolume; - final int mVolumeMax; final int mVolumeHandling; + final int mVolumeMax; + final int mVolume; + final String mAddress; final Bundle mExtras; final String mProviderId; @@ -336,6 +337,7 @@ public final class MediaRoute2Info implements Parcelable { mVolumeHandling = builder.mVolumeHandling; mVolumeMax = builder.mVolumeMax; mVolume = builder.mVolume; + mAddress = builder.mAddress; mExtras = builder.mExtras; mProviderId = builder.mProviderId; } @@ -353,6 +355,7 @@ public final class MediaRoute2Info implements Parcelable { mVolumeHandling = in.readInt(); mVolumeMax = in.readInt(); mVolume = in.readInt(); + mAddress = in.readString(); mExtras = in.readBundle(); mProviderId = in.readString(); } @@ -483,6 +486,15 @@ public final class MediaRoute2Info implements Parcelable { return mVolume; } + /** + * Gets the hardware address of the route if available. + * @hide + */ + @Nullable + public String getAddress() { + return mAddress; + } + @Nullable public Bundle getExtras() { return mExtras == null ? null : new Bundle(mExtras); @@ -564,6 +576,7 @@ public final class MediaRoute2Info implements Parcelable { && (mVolumeHandling == other.mVolumeHandling) && (mVolumeMax == other.mVolumeMax) && (mVolume == other.mVolume) + && Objects.equals(mAddress, other.mAddress) && Objects.equals(mProviderId, other.mProviderId); } @@ -572,7 +585,7 @@ public final class MediaRoute2Info implements Parcelable { // Note: mExtras is not included. return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription, mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume, - mProviderId); + mAddress, mProviderId); } @Override @@ -614,6 +627,7 @@ public final class MediaRoute2Info implements Parcelable { dest.writeInt(mVolumeHandling); dest.writeInt(mVolumeMax); dest.writeInt(mVolume); + dest.writeString(mAddress); dest.writeBundle(mExtras); dest.writeString(mProviderId); } @@ -637,6 +651,7 @@ public final class MediaRoute2Info implements Parcelable { int mVolumeHandling = PLAYBACK_VOLUME_FIXED; int mVolumeMax; int mVolume; + String mAddress; Bundle mExtras; String mProviderId; @@ -669,24 +684,7 @@ public final class MediaRoute2Info implements Parcelable { * @param routeInfo the existing instance to copy data from. */ public Builder(@NonNull MediaRoute2Info routeInfo) { - Objects.requireNonNull(routeInfo, "routeInfo must not be null"); - - mId = routeInfo.mId; - mName = routeInfo.mName; - mFeatures = new ArrayList<>(routeInfo.mFeatures); - mType = routeInfo.mType; - mIsSystem = routeInfo.mIsSystem; - mIconUri = routeInfo.mIconUri; - mDescription = routeInfo.mDescription; - mConnectionState = routeInfo.mConnectionState; - mClientPackageName = routeInfo.mClientPackageName; - mVolumeHandling = routeInfo.mVolumeHandling; - mVolumeMax = routeInfo.mVolumeMax; - mVolume = routeInfo.mVolume; - if (routeInfo.mExtras != null) { - mExtras = new Bundle(routeInfo.mExtras); - } - mProviderId = routeInfo.mProviderId; + this(routeInfo.mId, routeInfo); } /** @@ -715,6 +713,7 @@ public final class MediaRoute2Info implements Parcelable { mVolumeHandling = routeInfo.mVolumeHandling; mVolumeMax = routeInfo.mVolumeMax; mVolume = routeInfo.mVolume; + mAddress = routeInfo.mAddress; if (routeInfo.mExtras != null) { mExtras = new Bundle(routeInfo.mExtras); } @@ -865,6 +864,16 @@ public final class MediaRoute2Info implements Parcelable { } /** + * Sets the hardware address of the route. + * @hide + */ + @NonNull + public Builder setAddress(String address) { + mAddress = address; + return this; + } + + /** * Sets a bundle of extras for the route. * <p> * Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}. diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 05c6e3ad9392..908fd820d047 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -40,8 +40,10 @@ import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Deque; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -132,15 +134,21 @@ public abstract class MediaRoute2ProviderService extends Service { @Retention(RetentionPolicy.SOURCE) public @interface Reason {} + private static final int MAX_REQUEST_IDS_SIZE = 500; + private final Handler mHandler; private final Object mSessionLock = new Object(); + private final Object mRequestIdsLock = new Object(); private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false); private MediaRoute2ProviderServiceStub mStub; private IMediaRoute2ProviderServiceCallback mRemoteCallback; private volatile MediaRoute2ProviderInfo mProviderInfo; + @GuardedBy("mRequestIdsLock") + private final Deque<Long> mRequestIds = new ArrayDeque<>(MAX_REQUEST_IDS_SIZE); + @GuardedBy("mSessionLock") - private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>(); + private final ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>(); public MediaRoute2ProviderService() { mHandler = new Handler(Looper.getMainLooper()); @@ -230,6 +238,11 @@ public abstract class MediaRoute2ProviderService extends Service { @NonNull RoutingSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + if (requestId != REQUEST_ID_NONE && !removeRequestId(requestId)) { + Log.w(TAG, "notifySessionCreated: The requestId doesn't exist. requestId=" + requestId); + return; + } + String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { @@ -322,6 +335,13 @@ public abstract class MediaRoute2ProviderService extends Service { if (mRemoteCallback == null) { return; } + + if (!removeRequestId(requestId)) { + Log.w(TAG, "notifyRequestFailed: The requestId doesn't exist. requestId=" + + requestId); + return; + } + try { mRemoteCallback.notifyRequestFailed(requestId, reason); } catch (RemoteException ex) { @@ -469,6 +489,36 @@ public abstract class MediaRoute2ProviderService extends Service { } } + /** + * Adds a requestId in the request ID list whose max size is {@link #MAX_REQUEST_IDS_SIZE}. + * When the max size is reached, the first element is removed (FIFO). + */ + private void addRequestId(long requestId) { + synchronized (mRequestIdsLock) { + if (mRequestIds.size() >= MAX_REQUEST_IDS_SIZE) { + mRequestIds.removeFirst(); + } + mRequestIds.addLast(requestId); + } + } + + /** + * Removes the given {@code requestId} from received request ID list. + * <p> + * Returns whether the list contains the {@code requestId}. These are the cases when the list + * doesn't contain the given {@code requestId}: + * <ul> + * <li>This service has never received a request with the requestId. </li> + * <li>{@link #notifyRequestFailed} or {@link #notifySessionCreated} already has been called + * for the requestId. </li> + * </ul> + */ + private boolean removeRequestId(long requestId) { + synchronized (mRequestIdsLock) { + return mRequestIds.removeFirstOccurrence(requestId); + } + } + final class MediaRoute2ProviderServiceStub extends IMediaRoute2ProviderService.Stub { MediaRoute2ProviderServiceStub() { } @@ -529,6 +579,7 @@ public abstract class MediaRoute2ProviderService extends Service { if (!checkRouteIdIsValid(routeId, "setRouteVolume")) { return; } + addRequestId(requestId); mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetRouteVolume, MediaRoute2ProviderService.this, requestId, routeId, volume)); } @@ -542,6 +593,7 @@ public abstract class MediaRoute2ProviderService extends Service { if (!checkRouteIdIsValid(routeId, "requestCreateSession")) { return; } + addRequestId(requestId); mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession, MediaRoute2ProviderService.this, requestId, packageName, routeId, requestCreateSession)); @@ -556,6 +608,7 @@ public abstract class MediaRoute2ProviderService extends Service { || !checkRouteIdIsValid(routeId, "selectRoute")) { return; } + addRequestId(requestId); mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute, MediaRoute2ProviderService.this, requestId, sessionId, routeId)); } @@ -569,6 +622,7 @@ public abstract class MediaRoute2ProviderService extends Service { || !checkRouteIdIsValid(routeId, "deselectRoute")) { return; } + addRequestId(requestId); mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute, MediaRoute2ProviderService.this, requestId, sessionId, routeId)); } @@ -582,6 +636,7 @@ public abstract class MediaRoute2ProviderService extends Service { || !checkRouteIdIsValid(routeId, "transferToRoute")) { return; } + addRequestId(requestId); mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute, MediaRoute2ProviderService.this, requestId, sessionId, routeId)); } @@ -594,6 +649,7 @@ public abstract class MediaRoute2ProviderService extends Service { if (!checkSessionIdIsValid(sessionId, "setSessionVolume")) { return; } + addRequestId(requestId); mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetSessionVolume, MediaRoute2ProviderService.this, requestId, sessionId, volume)); } @@ -606,6 +662,7 @@ public abstract class MediaRoute2ProviderService extends Service { if (!checkSessionIdIsValid(sessionId, "releaseSession")) { return; } + addRequestId(requestId); mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession, MediaRoute2ProviderService.this, requestId, sessionId)); } diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl index de4d060ce484..a237ec1aa3b3 100644 --- a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl +++ b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl @@ -43,9 +43,9 @@ parcelable RecognitionEvent { boolean triggerInData; /** * Audio format of either the trigger in event data or to use for capture of the rest of the - * utterance. + * utterance. May be null when no audio is available for this event type. */ - AudioConfig audioConfig; + @nullable AudioConfig audioConfig; /** Additional data. */ byte[] data; } diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java index 957558107de9..638a842e4e5f 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java @@ -593,11 +593,16 @@ public class MediaRouter2ManagerTest { final int failureReason = REASON_REJECTED; final CountDownLatch onRequestFailedLatch = new CountDownLatch(1); + final CountDownLatch onRequestFailedSecondCallLatch = new CountDownLatch(1); addManagerCallback(new MediaRouter2Manager.Callback() { @Override public void onRequestFailed(int reason) { if (reason == failureReason) { - onRequestFailedLatch.countDown(); + if (onRequestFailedLatch.getCount() > 0) { + onRequestFailedLatch.countDown(); + } else { + onRequestFailedSecondCallLatch.countDown(); + } } } }); @@ -609,6 +614,11 @@ public class MediaRouter2ManagerTest { final long validRequestId = requestIds.get(0); instance.notifyRequestFailed(validRequestId, failureReason); assertTrue(onRequestFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + + // Test calling notifyRequestFailed() multiple times with the same valid requestId. + // onRequestFailed() shouldn't be called since the requestId has been already handled. + instance.notifyRequestFailed(validRequestId, failureReason); + assertFalse(onRequestFailedSecondCallLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } @Test diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java index 2dad5f872e73..69766cc6c0d0 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java @@ -103,6 +103,7 @@ public class CarKeyguardViewController extends OverlayViewController implements private KeyguardBouncer mBouncer; private OnKeyguardCancelClickedListener mKeyguardCancelClickedListener; private boolean mShowing; + private boolean mIsOccluded; @Inject public CarKeyguardViewController( @@ -220,6 +221,7 @@ public class CarKeyguardViewController extends OverlayViewController implements @Override public void setOccluded(boolean occluded, boolean animate) { + mIsOccluded = occluded; getOverlayViewGlobalStateController().setOccluded(occluded); if (!occluded) { reset(/* hideBouncerWhenShowing= */ false); @@ -244,6 +246,12 @@ public class CarKeyguardViewController extends OverlayViewController implements @Override public void dismissAndCollapse() { + // If dismissing and collapsing Keyguard is requested (e.g. by a Keyguard-dismissing + // Activity) while Keyguard is occluded, unocclude Keyguard so the user can authenticate to + // dismiss Keyguard. + if (mIsOccluded) { + setOccluded(/* occluded= */ false, /* animate= */ false); + } if (!mBouncer.isSecure()) { hide(/* startTime= */ 0, /* fadeoutDuration= */ 0); } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 360b14d97158..f42bf1982b36 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -521,9 +521,11 @@ public class ExternalStorageProvider extends FileSystemProvider { final RootInfo root = resolvedDocId.first; File child = resolvedDocId.second; + final File rootFile = root.visiblePath != null ? root.visiblePath + : root.path; final File parent = TextUtils.isEmpty(parentDocId) - ? root.path - : getFileForDocId(parentDocId); + ? rootFile + : getFileForDocId(parentDocId); return new Path(parentDocId == null ? root.rootId : null, findDocumentPath(parent, child)); } diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS index a28ba8584054..d7bd6a49d75b 100644 --- a/packages/SettingsLib/OWNERS +++ b/packages/SettingsLib/OWNERS @@ -3,6 +3,7 @@ dsandler@android.com edgarwang@google.com emilychuang@google.com evanlaird@google.com +juliacr@google.com leifhendrik@google.com rafftsai@google.com tmfang@google.com diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 2b51b54a2fe3..69f7fb086b64 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -557,5 +557,5 @@ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"غير مفعّل"</string> <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"مفعّل"</string> <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"يجب إعادة تشغيل جهازك ليتم تطبيق هذا التغيير. يمكنك إعادة التشغيل الآن أو إلغاء التغيير."</string> - <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"سمّاعة رأس سلكية"</string> + <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"سمّاعة سلكية"</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index 0ef8e0677d6d..0def17e1c21a 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -553,5 +553,5 @@ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string> <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string> <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string> - <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string> + <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index 0ef8e0677d6d..0def17e1c21a 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -553,5 +553,5 @@ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string> <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string> <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string> - <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string> + <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index 0ef8e0677d6d..0def17e1c21a 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -553,5 +553,5 @@ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string> <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string> <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string> - <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string> + <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index 0ef8e0677d6d..0def17e1c21a 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -553,5 +553,5 @@ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string> <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string> <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string> - <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string> + <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string> </resources> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index efc186aa24bb..24711ff92d05 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -64,10 +64,10 @@ <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Scaduta"</string> <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string> <string name="bluetooth_disconnected" msgid="7739366554710388701">"Disconnesso"</string> - <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Disconnessione..."</string> - <string name="bluetooth_connecting" msgid="5871702668260192755">"Connessione..."</string> + <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Disconnessione…"</string> + <string name="bluetooth_connecting" msgid="5871702668260192755">"Connessione…"</string> <string name="bluetooth_connected" msgid="8065345572198502293">"<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> connesso"</string> - <string name="bluetooth_pairing" msgid="4269046942588193600">"Accoppiamento..."</string> + <string name="bluetooth_pairing" msgid="4269046942588193600">"Accoppiamento…"</string> <string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> connesso (telefono escluso)"</string> <string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> connesso (contenuti multimediali esclusi)"</string> <string name="bluetooth_connected_no_map" msgid="3381860077002724689">"<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> connesso (nessun accesso ai messaggi)"</string> @@ -406,11 +406,11 @@ <string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Imposta l\'implementazione di WebView"</string> <string name="select_webview_provider_toast_text" msgid="8512254949169359848">"La selezione non è più valida. Riprova."</string> <string name="convert_to_file_encryption" msgid="2828976934129751818">"Converti in crittografia basata su file"</string> - <string name="convert_to_file_encryption_enabled" msgid="840757431284311754">"Converti..."</string> + <string name="convert_to_file_encryption_enabled" msgid="840757431284311754">"Converti…"</string> <string name="convert_to_file_encryption_done" msgid="8965831011811180627">"Crittografia su base file già eseguita"</string> <string name="title_convert_fbe" msgid="5780013350366495149">"Conversione in crittografia basata su file"</string> - <string name="convert_to_fbe_warning" msgid="34294381569282109">"Converti la partizione di dati in crittografia basata su file.\n Attenzione. Questa operazione eliminerà tutti i tuoi dati.\n Questa funzione è in versione alpha, pertanto potrebbe non funzionare correttamente.\n Premi \"Cancella e converti...\" per continuare."</string> - <string name="button_convert_fbe" msgid="1159861795137727671">"Cancella e converti..."</string> + <string name="convert_to_fbe_warning" msgid="34294381569282109">"Converti la partizione di dati in crittografia basata su file.\n Attenzione. Questa operazione eliminerà tutti i tuoi dati.\n Questa funzione è in versione alpha, pertanto potrebbe non funzionare correttamente.\n Premi \"Cancella e converti…\" per continuare."</string> + <string name="button_convert_fbe" msgid="1159861795137727671">"Cancella e converti…"</string> <string name="picture_color_mode" msgid="1013807330552931903">"Modalità colori immagini"</string> <string name="picture_color_mode_desc" msgid="151780973768136200">"Use sRGB"</string> <string name="daltonizer_mode_disabled" msgid="403424372812399228">"Disattivato"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index b501c656a680..7e1b476b7a0c 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -195,7 +195,7 @@ </string-array> <string name="choose_profile" msgid="343803890897657450">"Chọn hồ sơ"</string> <string name="category_personal" msgid="6236798763159385225">"Cá nhân"</string> - <string name="category_work" msgid="4014193632325996115">"Cơ quan"</string> + <string name="category_work" msgid="4014193632325996115">"Công việc"</string> <string name="development_settings_title" msgid="140296922921597393">"Tùy chọn cho nhà phát triển"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Bật tùy chọn nhà phát triển"</string> <string name="development_settings_summary" msgid="8718917813868735095">"Đặt tùy chọn cho phát triển ứng dụng"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index b83a9c4835e0..6c7e03f104dd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -56,7 +56,7 @@ import java.util.concurrent.Executors; public class InfoMediaManager extends MediaManager { private static final String TAG = "InfoMediaManager"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @VisibleForTesting final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback(); @VisibleForTesting @@ -430,7 +430,7 @@ public class InfoMediaManager extends MediaManager { case TYPE_HEARING_AID: case TYPE_BLUETOOTH_A2DP: final BluetoothDevice device = - BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getOriginalId()); + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getAddress()); final CachedBluetoothDevice cachedDevice = mBluetoothManager.getCachedDeviceManager().findDevice(device); if (cachedDevice != null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index f381cde31f83..9d06c8467e41 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -20,7 +20,6 @@ import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; import android.app.Notification; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; import android.content.Context; import android.media.RoutingSessionInfo; import android.text.TextUtils; @@ -409,7 +408,8 @@ public class LocalMediaManager implements BluetoothCallback { synchronized (mMediaDevicesLock) { for (MediaDevice device : mMediaDevices) { if (device instanceof BluetoothMediaDevice) { - if (isActiveDevice(((BluetoothMediaDevice) device).getCachedDevice())) { + if (isActiveDevice(((BluetoothMediaDevice) device).getCachedDevice()) + && device.isConnected()) { return device; } } else if (device instanceof PhoneMediaDevice) { @@ -422,8 +422,22 @@ public class LocalMediaManager implements BluetoothCallback { } private boolean isActiveDevice(CachedBluetoothDevice device) { - return device.isActiveDevice(BluetoothProfile.A2DP) - || device.isActiveDevice(BluetoothProfile.HEARING_AID); + boolean isActiveDeviceA2dp = false; + boolean isActiveDeviceHearingAid = false; + final A2dpProfile a2dpProfile = mLocalBluetoothManager.getProfileManager().getA2dpProfile(); + if (a2dpProfile != null) { + isActiveDeviceA2dp = device.getDevice().equals(a2dpProfile.getActiveDevice()); + } + if (!isActiveDeviceA2dp) { + final HearingAidProfile hearingAidProfile = mLocalBluetoothManager.getProfileManager() + .getHearingAidProfile(); + if (hearingAidProfile != null) { + isActiveDeviceHearingAid = + hearingAidProfile.getActiveDevices().contains(device.getDevice()); + } + } + + return isActiveDeviceA2dp || isActiveDeviceHearingAid; } private Collection<DeviceCallback> getCallbacks() { diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java index d5f1ece5f83f..549bc8a455cf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java @@ -86,7 +86,7 @@ public class ConversationIconFactory extends BaseIconFactory { /** * Returns the conversation info drawable */ - private Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) { + public Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) { return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi); } @@ -94,7 +94,7 @@ public class ConversationIconFactory extends BaseIconFactory { * Get the {@link Drawable} that represents the app icon, badged with the work profile icon * if appropriate. */ - private Drawable getAppBadge(String packageName, int userId) { + public Drawable getAppBadge(String packageName, int userId) { Drawable badge = null; try { final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser( diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 248eb5b96b92..94d95f06050d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -681,7 +681,7 @@ public class InfoMediaManagerTest { assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue(); when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP); - when(route2Info.getOriginalId()).thenReturn("00:00:00:00:00:00"); + when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00"); when(mLocalBluetoothManager.getCachedDeviceManager()) .thenReturn(cachedBluetoothDeviceManager); when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class))) @@ -703,7 +703,7 @@ public class InfoMediaManagerTest { mock(CachedBluetoothDeviceManager.class); when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP); - when(route2Info.getOriginalId()).thenReturn("00:00:00:00:00:00"); + when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00"); when(mLocalBluetoothManager.getCachedDeviceManager()) .thenReturn(cachedBluetoothDeviceManager); when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class))) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index 368245f2002d..a654fd47ea12 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -578,6 +578,9 @@ public class LocalMediaManagerTest { when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); when(cachedDevice.isConnected()).thenReturn(false); when(cachedDevice.getConnectableProfiles()).thenReturn(profiles); + when(cachedDevice.getDevice()).thenReturn(bluetoothDevice); + when(mA2dpProfile.getActiveDevice()).thenReturn(bluetoothDevice); + when(mHapProfile.getActiveDevices()).thenReturn(new ArrayList<>()); when(device1.getId()).thenReturn(TEST_DEVICE_ID_1); when(device2.getId()).thenReturn(TEST_DEVICE_ID_2); @@ -734,11 +737,19 @@ public class LocalMediaManagerTest { final PhoneMediaDevice phoneDevice = mock(PhoneMediaDevice.class); final CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class); final CachedBluetoothDevice cachedDevice2 = mock(CachedBluetoothDevice.class); + final BluetoothDevice bluetoothDevice1 = mock(BluetoothDevice.class); + final BluetoothDevice bluetoothDevice2 = mock(BluetoothDevice.class); + when(mA2dpProfile.getActiveDevice()).thenReturn(bluetoothDevice2); + when(mHapProfile.getActiveDevices()).thenReturn(new ArrayList<>()); when(device1.getCachedDevice()).thenReturn(cachedDevice1); when(device2.getCachedDevice()).thenReturn(cachedDevice2); + when(cachedDevice1.getDevice()).thenReturn(bluetoothDevice1); + when(cachedDevice2.getDevice()).thenReturn(bluetoothDevice2); when(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(false); when(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(true); + when(device1.isConnected()).thenReturn(true); + when(device2.isConnected()).thenReturn(true); mLocalMediaManager.mMediaDevices.add(device1); mLocalMediaManager.mMediaDevices.add(phoneDevice); @@ -754,11 +765,19 @@ public class LocalMediaManagerTest { final PhoneMediaDevice phoneDevice = mock(PhoneMediaDevice.class); final CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class); final CachedBluetoothDevice cachedDevice2 = mock(CachedBluetoothDevice.class); + final BluetoothDevice bluetoothDevice1 = mock(BluetoothDevice.class); + final BluetoothDevice bluetoothDevice2 = mock(BluetoothDevice.class); + when(mA2dpProfile.getActiveDevice()).thenReturn(null); + when(mHapProfile.getActiveDevices()).thenReturn(new ArrayList<>()); when(device1.getCachedDevice()).thenReturn(cachedDevice1); when(device2.getCachedDevice()).thenReturn(cachedDevice2); + when(cachedDevice1.getDevice()).thenReturn(bluetoothDevice1); + when(cachedDevice2.getDevice()).thenReturn(bluetoothDevice2); when(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(false); when(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(false); + when(device1.isConnected()).thenReturn(true); + when(device2.isConnected()).thenReturn(true); mLocalMediaManager.mMediaDevices.add(device1); mLocalMediaManager.mMediaDevices.add(phoneDevice); diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml index 0cabc3245152..7313d54c599a 100644 --- a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml +++ b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml @@ -17,30 +17,72 @@ <com.android.systemui.bubbles.BubbleManageEducationView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent"> - + android:layout_height="match_parent" + > <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:id="@+id/manage_education_view" + android:clickable="true" + android:paddingTop="28dp" + android:paddingBottom="16dp" + android:paddingStart="@dimen/bubble_expanded_view_padding" + android:paddingEnd="48dp" + android:layout_marginEnd="24dp" android:orientation="vertical" - android:layout_height="wrap_content" - android:layout_width="@dimen/bubbles_manage_education_width"> + android:background="@drawable/bubble_stack_user_education_bg" + android:alpha="0.9" + > + + <TextView + android:id="@+id/user_education_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingStart="16dp" + android:paddingBottom="16dp" + android:fontFamily="@*android:string/config_bodyFontFamilyMedium" + android:maxLines="1" + android:text="@string/bubbles_user_education_manage_title" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/> <TextView android:id="@+id/user_education_description" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:padding="8dp" + android:paddingStart="16dp" + android:paddingBottom="24dp" android:text="@string/bubbles_user_education_manage" android:fontFamily="@*android:string/config_bodyFontFamily" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2" - android:background="@drawable/bubble_manage_user_education_bg" - /> + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/> + + <LinearLayout + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:orientation="horizontal" > - <View - android:id="@+id/user_education_pointer" - android:layout_width="@dimen/bubble_pointer_width" - android:layout_height="@dimen/bubble_pointer_height" - /> + <com.android.systemui.statusbar.AlphaOptimizedButton + style="@android:style/Widget.Material.Button.Borderless" + android:id="@+id/manage" + android:layout_gravity="start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:focusable="true" + android:clickable="false" + android:text="@string/manage_bubbles_text" + android:textColor="?attr/wallpaperTextColor" + android:alpha="0.89" + /> + <com.android.systemui.statusbar.AlphaOptimizedButton + style="@android:style/Widget.Material.Button.Borderless" + android:id="@+id/got_it" + android:layout_gravity="start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:focusable="true" + android:text="@string/bubbles_user_education_got_it" + android:textColor="?attr/wallpaperTextColor" + /> + </LinearLayout> </LinearLayout> </com.android.systemui.bubbles.BubbleManageEducationView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index fce4610b934b..6a235218b32f 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -30,11 +30,10 @@ android:id="@+id/global_screenshot_animated_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center" + android:layout_gravity="top|start" android:visibility="gone" android:elevation="@dimen/screenshot_preview_elevation" - android:background="@drawable/screenshot_rounded_corners" - android:adjustViewBounds="true"/> + android:background="@drawable/screenshot_rounded_corners" /> <ImageView android:id="@+id/global_screenshot_flash" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml index 49d525fc189a..2c4b937ce95b 100644 --- a/packages/SystemUI/res/layout/notification_conversation_info.xml +++ b/packages/SystemUI/res/layout/notification_conversation_info.xml @@ -34,7 +34,7 @@ android:layout_height="wrap_content" android:gravity="center_vertical" android:clipChildren="false" - android:paddingTop="8dp" + android:paddingTop="11dp" android:clipToPadding="true"> <ImageView android:id="@+id/conversation_icon" @@ -85,20 +85,20 @@ style="@style/TextAppearance.NotificationImportanceChannel"/> </LinearLayout> <TextView - android:id="@+id/pkg_name" + android:id="@+id/group_name" android:layout_width="match_parent" android:layout_height="wrap_content" - style="@style/TextAppearance.NotificationImportanceChannelGroup" android:ellipsize="end" android:textDirection="locale" - android:maxLines="1"/> + style="@style/TextAppearance.NotificationImportanceChannelGroup"/> <TextView - android:id="@+id/group_name" + android:id="@+id/pkg_name" android:layout_width="match_parent" android:layout_height="wrap_content" + style="@style/TextAppearance.NotificationImportanceApp" android:ellipsize="end" android:textDirection="locale" - style="@style/TextAppearance.NotificationImportanceChannelGroup"/> + android:maxLines="1"/> <TextView android:id="@+id/delegate_name" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 870deacd87ae..1c7c22653f9f 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -59,20 +59,20 @@ android:textDirection="locale" style="@style/TextAppearance.NotificationImportanceChannel"/> <TextView - android:id="@+id/pkg_name" + android:id="@+id/group_name" android:layout_width="match_parent" android:layout_height="wrap_content" - style="@style/TextAppearance.NotificationImportanceChannelGroup" - android:ellipsize="end" android:textDirection="locale" - android:maxLines="1"/> + android:ellipsize="end" + style="@style/TextAppearance.NotificationImportanceChannelGroup"/> <TextView - android:id="@+id/group_name" + android:id="@+id/pkg_name" android:layout_width="match_parent" android:layout_height="wrap_content" - android:textDirection="locale" + style="@style/TextAppearance.NotificationImportanceApp" android:ellipsize="end" - style="@style/TextAppearance.NotificationImportanceChannelGroup"/> + android:textDirection="locale" + android:maxLines="1"/> <TextView android:id="@+id/delegate_name" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml index ecc311893497..07951705664a 100644 --- a/packages/SystemUI/res/layout/partial_conversation_info.xml +++ b/packages/SystemUI/res/layout/partial_conversation_info.xml @@ -113,18 +113,21 @@ android:layout_height="wrap_content" android:clipChildren="false" android:clipToPadding="false" + android:layout_centerVertical="true" android:orientation="horizontal"> <ImageView - android:layout_height="wrap_content" + android:layout_height="match_parent" android:layout_width="wrap_content" android:contentDescription="@null" android:src="@drawable/ic_info" + android:layout_centerVertical="true" android:tint="?android:attr/textColorPrimary" android:layout_marginEnd="8dp"/> <TextView android:id="@+id/non_configurable_text" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_centerVertical="true" style="@style/TextAppearance.NotificationImportanceChannelGroup" /> </LinearLayout> </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> diff --git a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml index bf2eac3c8ff3..3f0e514a9af2 100644 --- a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml +++ b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml @@ -38,11 +38,61 @@ android:background="@drawable/rounded_bg_full" > + <FrameLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" + android:padding="12dp" + android:layout_gravity="center_horizontal" + > + + <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right --> <ImageView android:id="@+id/conversation_icon" - android:layout_width="@dimen/notification_guts_conversation_icon_size" - android:layout_height="@dimen/notification_guts_conversation_icon_size" - android:layout_gravity="center_horizontal" /> + android:layout_width="@*android:dimen/conversation_avatar_size" + android:layout_height="@*android:dimen/conversation_avatar_size" + android:scaleType="centerCrop" + android:importantForAccessibility="no" + /> + + <FrameLayout + android:id="@+id/conversation_icon_badge" + android:layout_width="@*android:dimen/conversation_icon_size_badged" + android:layout_height="@*android:dimen/conversation_icon_size_badged" + android:layout_marginLeft="@*android:dimen/conversation_badge_side_margin" + android:layout_marginTop="@*android:dimen/conversation_badge_side_margin" + android:clipChildren="false" + android:clipToPadding="false" + > + <ImageView + android:id="@+id/conversation_icon_badge_bg" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + android:src="@*android:drawable/conversation_badge_background" + android:forceHasOverlappingRendering="false" + /> + <ImageView + android:id="@+id/icon" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="4dp" + android:layout_gravity="center" + android:forceHasOverlappingRendering="false" + /> + <ImageView + android:id="@+id/conversation_icon_badge_ring" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:src="@*android:drawable/conversation_badge_ring" + android:forceHasOverlappingRendering="false" + android:clipToPadding="false" + android:scaleType="center" + /> + </FrameLayout> + </FrameLayout> <TextView android:id="@+id/title" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 837627c02638..4fc904b21741 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -202,6 +202,10 @@ <color name="global_screenshot_dismiss_foreground">@color/GM2_grey_500</color> <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black --> + <!-- Bubbles --> + <color name="bubbles_pointer_light">#FFFFFF</color> + <color name="bubbles_pointer_dark">@color/GM2_grey_800</color> + <!-- GM2 colors --> <color name="GM2_grey_50">#F8F9FA</color> <color name="GM2_grey_100">#F1F3F4</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 73d8e9a0d8a7..abc560bed595 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1222,10 +1222,6 @@ <dimen name="bubble_dismiss_slop">16dp</dimen> <!-- Height of button allowing users to adjust settings for bubbles. --> <dimen name="bubble_manage_button_height">48dp</dimen> - <!-- How far, horizontally, to animate the expanded view over when animating in/out. --> - <dimen name="bubble_expanded_animate_x_distance">100dp</dimen> - <!-- How far, vertically, to animate the expanded view over when animating in/out. --> - <dimen name="bubble_expanded_animate_y_distance">500dp</dimen> <!-- Max width of the message bubble--> <dimen name="bubble_message_max_width">144dp</dimen> <!-- Min width of the message bubble --> @@ -1252,7 +1248,7 @@ <!-- Bubbles user education views --> <dimen name="bubbles_manage_education_width">160dp</dimen> <!-- The inset from the top bound of the manage button to place the user education. --> - <dimen name="bubbles_manage_education_top_inset">10dp</dimen> + <dimen name="bubbles_manage_education_top_inset">65dp</dimen> <!-- Size of padding for the user education cling, this should at minimum be larger than individual_bubble_size + some padding. --> <dimen name="bubble_stack_user_education_side_inset">72dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 39f78bf46028..8097c01cb042 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -549,6 +549,13 @@ <style name="TextAppearance.NotificationImportanceChannelGroup"> <item name="android:textSize">@dimen/notification_importance_channel_group_text</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:textColor">@color/notification_guts_header_text_color</item> + <item name="android:textSize">@dimen/notification_importance_channel_group_text</item> + </style> + + <style name="TextAppearance.NotificationImportanceApp"> + <item name="android:textSize">@dimen/notification_importance_channel_group_text</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textColor">@color/notification_guts_sub_text_color</item> <item name="android:textSize">@dimen/notification_importance_channel_group_text</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java index ebed1fcdb48b..27e4c85e1e02 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java @@ -63,8 +63,9 @@ public class InputConsumerController { */ private final class InputEventReceiver extends BatchedInputEventReceiver { - public InputEventReceiver(InputChannel inputChannel, Looper looper) { - super(inputChannel, looper, Choreographer.getInstance()); + InputEventReceiver(InputChannel inputChannel, Looper looper, + Choreographer choreographer) { + super(inputChannel, looper, choreographer); } @Override @@ -143,6 +144,14 @@ public class InputConsumerController { * Registers the input consumer. */ public void registerInputConsumer() { + registerInputConsumer(false); + } + + /** + * Registers the input consumer. + * @param withSfVsync the flag set using sf vsync signal or no + */ + public void registerInputConsumer(boolean withSfVsync) { if (mInputEventReceiver == null) { final InputChannel inputChannel = new InputChannel(); try { @@ -152,7 +161,8 @@ public class InputConsumerController { } catch (RemoteException e) { Log.e(TAG, "Failed to create input consumer", e); } - mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper()); + mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper(), + withSfVsync ? Choreographer.getSfInstance() : Choreographer.getInstance()); if (mRegistrationListener != null) { mRegistrationListener.onRegistrationChanged(true /* isRegistered */); } diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index ccb506de6d8b..0218cd237037 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -124,7 +124,7 @@ public final class Prefs { String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding"; String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount"; /** Tracks whether the user has seen the onboarding screen for priority conversations */ - String HAS_SEEN_PRIORITY_ONBOARDING = "HaveShownPriorityOnboarding"; + String HAS_SEEN_PRIORITY_ONBOARDING = "HasUserSeenPriorityOnboarding"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt index 4269605cef12..d61de0623fe8 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt @@ -28,12 +28,12 @@ import android.text.TextUtils import android.util.SparseArray import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Dumpable +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import java.io.FileDescriptor import java.io.PrintWriter -import java.lang.IllegalStateException import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton @@ -67,7 +67,8 @@ open class BroadcastDispatcher @Inject constructor ( private val context: Context, @Main private val mainHandler: Handler, @Background private val bgLooper: Looper, - dumpManager: DumpManager + dumpManager: DumpManager, + private val logger: BroadcastDispatcherLogger ) : Dumpable { // Only modify in BG thread @@ -156,7 +157,7 @@ open class BroadcastDispatcher @Inject constructor ( /** * Unregister receiver for a particular user. * - * @param receiver The receiver to unregister. It will be unregistered for all users. + * @param receiver The receiver to unregister. * @param user The user associated to the registered [receiver]. It can be [UserHandle.ALL]. */ open fun unregisterReceiverForUser(receiver: BroadcastReceiver, user: UserHandle) { @@ -166,7 +167,7 @@ open class BroadcastDispatcher @Inject constructor ( @VisibleForTesting protected open fun createUBRForUser(userId: Int) = - UserBroadcastDispatcher(context, userId, bgLooper) + UserBroadcastDispatcher(context, userId, bgLooper, logger) override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("Broadcast dispatcher:") diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt index 3272fb7545e2..96f5a1f6fbe8 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt @@ -30,6 +30,7 @@ import android.util.Log import androidx.annotation.VisibleForTesting import com.android.internal.util.Preconditions import com.android.systemui.Dumpable +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import java.io.FileDescriptor import java.io.PrintWriter import java.lang.IllegalArgumentException @@ -54,7 +55,8 @@ private const val DEBUG = false class UserBroadcastDispatcher( private val context: Context, private val userId: Int, - private val bgLooper: Looper + private val bgLooper: Looper, + private val logger: BroadcastDispatcherLogger ) : BroadcastReceiver(), Dumpable { companion object { @@ -109,10 +111,12 @@ class UserBroadcastDispatcher( } override fun onReceive(context: Context, intent: Intent) { - val id = if (DEBUG) index.getAndIncrement() else 0 + val id = index.getAndIncrement() if (DEBUG) Log.w(TAG, "[$id] Received $intent") + logger.logBroadcastReceived(id, userId, intent) bgHandler.post( - HandleBroadcastRunnable(actionsToReceivers, context, intent, pendingResult, id)) + HandleBroadcastRunnable( + actionsToReceivers, context, intent, pendingResult, id, logger)) } /** @@ -143,6 +147,7 @@ class UserBroadcastDispatcher( ArraySet() }.add(receiverData) } + logger.logReceiverRegistered(userId, receiverData.receiver) if (changed) { createFilterAndRegisterReceiverBG() } @@ -163,6 +168,7 @@ class UserBroadcastDispatcher( actionsToReceivers.remove(action) } } + logger.logReceiverUnregistered(userId, receiver) if (changed) { createFilterAndRegisterReceiverBG() } @@ -187,7 +193,8 @@ class UserBroadcastDispatcher( val context: Context, val intent: Intent, val pendingResult: PendingResult, - val index: Int + val index: Int, + val logger: BroadcastDispatcherLogger ) : Runnable { override fun run() { if (DEBUG) Log.w(TAG, "[$index] Dispatching $intent") @@ -199,6 +206,7 @@ class UserBroadcastDispatcher( it.executor.execute { if (DEBUG) Log.w(TAG, "[$index] Dispatching ${intent.action} to ${it.receiver}") + logger.logBroadcastDispatched(index, intent.action, it.receiver) it.receiver.pendingResult = pendingResult it.receiver.onReceive(context, intent) } @@ -215,6 +223,7 @@ class UserBroadcastDispatcher( if (registered.get()) { try { context.unregisterReceiver(this@UserBroadcastDispatcher) + logger.logContextReceiverUnregistered(userId) } catch (e: IllegalArgumentException) { Log.e(TAG, "Trying to unregister unregistered receiver for user $userId", IllegalStateException(e)) @@ -230,6 +239,7 @@ class UserBroadcastDispatcher( null, bgHandler) registered.set(true) + logger.logContextReceiverRegistered(userId, intentFilter) } } } diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt new file mode 100644 index 000000000000..123a8ae6307d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.broadcast.logging + +import android.content.BroadcastReceiver +import android.content.Intent +import android.content.IntentFilter +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.LogMessage +import com.android.systemui.log.dagger.BroadcastDispatcherLog +import javax.inject.Inject + +private const val TAG = "BroadcastDispatcherLog" + +class BroadcastDispatcherLogger @Inject constructor( + @BroadcastDispatcherLog private val buffer: LogBuffer +) { + + fun logBroadcastReceived(broadcastId: Int, user: Int, intent: Intent) { + val intentString = intent.toString() + log(INFO, { + int1 = broadcastId + int2 = user + str1 = intentString + }, { + "[$int1] Broadcast received for user $int2: $str1" + }) + } + + fun logBroadcastDispatched(broadcastId: Int, action: String?, receiver: BroadcastReceiver) { + val receiverString = receiver.toString() + log(DEBUG, { + int1 = broadcastId + str1 = action + str2 = receiverString + }, { + "Broadcast $int1 ($str1) dispatched to $str2" + }) + } + + fun logReceiverRegistered(user: Int, receiver: BroadcastReceiver) { + val receiverString = receiver.toString() + log(INFO, { + int1 = user + str1 = receiverString + }, { + "Receiver $str1 registered for user $int1" + }) + } + + fun logReceiverUnregistered(user: Int, receiver: BroadcastReceiver) { + val receiverString = receiver.toString() + log(INFO, { + int1 = user + str1 = receiverString + }, { + "Receiver $str1 unregistered for user $int1" + }) + } + + fun logContextReceiverRegistered(user: Int, filter: IntentFilter) { + val actions = filter.actionsIterator().asSequence() + .joinToString(separator = ",", prefix = "Actions(", postfix = ")") + val categories = if (filter.countCategories() != 0) { + filter.categoriesIterator().asSequence() + .joinToString(separator = ",", prefix = "Categories(", postfix = ")") + } else { + "" + } + log(INFO, { + int1 = user + str1 = if (categories != "") { + "${actions}\n$categories" + } else { + actions + } + }, { + """ + Receiver registered with Context for user $int1. + $str1 + """.trimIndent() + }) + } + + fun logContextReceiverUnregistered(user: Int) { + log(INFO, { + int1 = user + }, { + "Receiver unregistered with Context for user $int1." + }) + } + + private inline fun log( + logLevel: LogLevel, + initializer: LogMessage.() -> Unit, + noinline printer: LogMessage.() -> String + ) { + buffer.log(TAG, logLevel, initializer, printer) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 35cac4d9d63d..873b0785bd7f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -55,7 +55,6 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.PixelFormat; -import android.graphics.Rect; import android.os.Binder; import android.os.Handler; import android.os.RemoteException; @@ -180,6 +179,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Callback that updates BubbleOverflowActivity on data change. @Nullable private Runnable mOverflowCallback = null; + // Only load overflow data from disk once + private boolean mOverflowDataLoaded = false; + private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private IStatusBarService mBarService; private WindowManager mWindowManager; @@ -193,9 +195,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi /** Whether or not the BubbleStackView has been added to the WindowManager. */ private boolean mAddedToWindowManager = false; - // Used for determining view rect for touch interaction - private Rect mTempRect = new Rect(); - // Listens to user switch so bubbles can be saved and restored. private final NotificationLockscreenUserManager mNotifUserManager; @@ -419,6 +418,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mCallbacks.add(callback); } + /** + * Dispatches a back press into the expanded Bubble's ActivityView if its IME is visible, + * causing it to hide. + */ + public void hideImeFromExpandedBubble() { + if (mStackView != null) { + mStackView.hideImeFromExpandedBubble(); + } + } + private void setupNEM() { mNotificationEntryManager.addNotificationEntryListener( new NotificationEntryListener() { @@ -952,13 +961,14 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * Fills the overflow bubbles by loading them from disk. */ void loadOverflowBubblesFromDisk() { - if (!mBubbleData.getOverflowBubbles().isEmpty()) { + if (!mBubbleData.getOverflowBubbles().isEmpty() || mOverflowDataLoaded) { // we don't need to load overflow bubbles from disk if it is already in memory return; } + mOverflowDataLoaded = true; mDataRepository.loadBubbles((bubbles) -> { bubbles.forEach(bubble -> { - if (mBubbleData.getBubbles().contains(bubble)) { + if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) { // if the bubble is already active, there's no need to push it to overflow return; } @@ -1156,7 +1166,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (b.getEntry() != null) { // Updating the entry to be a bubble will trigger our normal update flow setIsBubble(b.getEntry(), isBubble, b.shouldAutoExpand()); - } else { + } else if (isBubble) { // If we have no entry to update, it's a persisted bubble so // we need to add it to the stack ourselves Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index db7980251daf..47e6645d347f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -22,6 +22,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.graphics.PixelFormat.TRANSPARENT; import static android.view.Display.INVALID_DISPLAY; import static android.view.InsetsState.ITYPE_IME; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.sNewInsetsMode; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; @@ -33,7 +34,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPAND import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; -import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityOptions; import android.app.ActivityTaskManager; @@ -42,10 +42,12 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Insets; +import android.graphics.Outline; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.ShapeDrawable; @@ -55,12 +57,19 @@ import android.os.RemoteException; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; +import android.view.SurfaceControl; +import android.view.SurfaceView; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.FrameLayout; import android.widget.LinearLayout; +import androidx.annotation.Nullable; + import com.android.internal.policy.ScreenDecorationsUtils; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -88,6 +97,7 @@ public class BubbleExpandedView extends LinearLayout { // The triangle pointing to the expanded view private View mPointerView; private int mPointerMargin; + @Nullable private int[] mExpandedViewContainerLocation; private AlphaOptimizedButton mSettingsIcon; @@ -121,6 +131,16 @@ public class BubbleExpandedView extends LinearLayout { private View mVirtualImeView; private WindowManager mVirtualDisplayWindowManager; private boolean mImeShowing = false; + private float mCornerRadius = 0f; + + /** + * Container for the ActivityView that has a solid, round-rect background that shows if the + * ActivityView hasn't loaded. + */ + private FrameLayout mActivityViewContainer = new FrameLayout(getContext()); + + /** The SurfaceView that the ActivityView draws to. */ + @Nullable private SurfaceView mActivitySurface; private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() { @Override @@ -255,8 +275,6 @@ public class BubbleExpandedView extends LinearLayout { mPointerDrawable = new ShapeDrawable(TriangleShape.create( mPointerWidth, mPointerHeight, true /* pointUp */)); - mPointerDrawable.setTint(Color.WHITE); - mPointerView.setBackground(mPointerDrawable); mPointerView.setVisibility(INVISIBLE); mSettingsIconHeight = getContext().getResources().getDimensionPixelSize( @@ -269,7 +287,28 @@ public class BubbleExpandedView extends LinearLayout { // Set ActivityView's alpha value as zero, since there is no view content to be shown. setContentVisibility(false); - addView(mActivityView); + + mActivityViewContainer.setBackgroundColor(Color.WHITE); + mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius); + } + }); + mActivityViewContainer.setClipToOutline(true); + mActivityViewContainer.addView(mActivityView); + mActivityViewContainer.setLayoutParams( + new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + addView(mActivityViewContainer); + + if (mActivityView != null + && mActivityView.getChildCount() > 0 + && mActivityView.getChildAt(0) instanceof SurfaceView) { + // Retrieve the surface from the ActivityView so we can screenshot it and change its + // z-ordering. This should always be possible, since ActivityView's constructor adds the + // SurfaceView as its first child. + mActivitySurface = (SurfaceView) mActivityView.getChildAt(0); + } // Expanded stack layout, top to bottom: // Expanded view container @@ -311,7 +350,10 @@ public class BubbleExpandedView extends LinearLayout { // ActivityView's vertical bounds. These events are part of a back gesture, and so they // should not collapse the stack (which all other touches on areas around the AV would // do). - if (motionEvent.getRawY() >= avBounds.top && motionEvent.getRawY() <= avBounds.bottom) { + if (motionEvent.getRawY() >= avBounds.top + && motionEvent.getRawY() <= avBounds.bottom + && (motionEvent.getRawX() < avBounds.left + || motionEvent.getRawX() > avBounds.right)) { return true; } @@ -327,6 +369,39 @@ public class BubbleExpandedView extends LinearLayout { return mBubble != null ? mBubble.getKey() : "null"; } + /** + * Asks the ActivityView's surface to draw on top of all other views in the window. This is + * useful for ordering surfaces during animations, but should otherwise be set to false so that + * bubbles and menus can draw over the ActivityView. + */ + void setSurfaceZOrderedOnTop(boolean onTop) { + if (mActivitySurface == null) { + return; + } + + mActivitySurface.setZOrderedOnTop(onTop, true); + } + + /** Return a GraphicBuffer with the contents of the ActivityView's underlying surface. */ + @Nullable SurfaceControl.ScreenshotGraphicBuffer snapshotActivitySurface() { + if (mActivitySurface == null) { + return null; + } + + return SurfaceControl.captureLayers( + mActivitySurface.getSurfaceControl(), + new Rect(0, 0, mActivityView.getWidth(), mActivityView.getHeight()), + 1 /* scale */); + } + + int[] getActivityViewLocationOnScreen() { + if (mActivityView != null) { + return mActivityView.getLocationOnScreen(); + } else { + return new int[]{0, 0}; + } + } + void setManageClickListener(OnClickListener manageClickListener) { findViewById(R.id.settings_button).setOnClickListener(manageClickListener); } @@ -345,13 +420,25 @@ public class BubbleExpandedView extends LinearLayout { void applyThemeAttrs() { final TypedArray ta = mContext.obtainStyledAttributes( new int[] {android.R.attr.dialogCornerRadius}); - float cornerRadius = ta.getDimensionPixelSize(0, 0); + mCornerRadius = ta.getDimensionPixelSize(0, 0); ta.recycle(); if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows( mContext.getResources())) { - mActivityView.setCornerRadius(cornerRadius); + mActivityView.setCornerRadius(mCornerRadius); + } + + final int mode = + getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + switch (mode) { + case Configuration.UI_MODE_NIGHT_NO: + mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_pointer_light)); + break; + case Configuration.UI_MODE_NIGHT_YES: + mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_pointer_dark)); + break; } + mPointerView.setBackground(mPointerDrawable); } /** @@ -398,6 +485,7 @@ public class BubbleExpandedView extends LinearLayout { mPointerView.setAlpha(alpha); if (mActivityView != null) { mActivityView.setAlpha(alpha); + mActivityView.bringToFront(); } } @@ -557,6 +645,11 @@ public class BubbleExpandedView extends LinearLayout { if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()); } + + if (mExpandedViewContainerLocation == null) { + return; + } + if (usingActivityView()) { float desiredHeight = mOverflowHeight; if (!mIsOverflow) { @@ -564,7 +657,7 @@ public class BubbleExpandedView extends LinearLayout { } float height = Math.min(desiredHeight, getMaxExpandedHeight()); height = Math.max(height, mIsOverflow? mOverflowHeight : mMinHeight); - LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams(); + ViewGroup.LayoutParams lp = mActivityView.getLayoutParams(); mNeedsNewHeight = lp.height != height; if (!mKeyboardVisible) { // If the keyboard is visible... don't adjust the height because that will cause @@ -574,7 +667,8 @@ public class BubbleExpandedView extends LinearLayout { mNeedsNewHeight = false; } if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + " height=" + height + Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + + " height=" + height + " mNeedsNewHeight=" + mNeedsNewHeight); } } @@ -582,28 +676,40 @@ public class BubbleExpandedView extends LinearLayout { private int getMaxExpandedHeight() { mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize); - int[] windowLocation = mActivityView.getLocationOnScreen(); int bottomInset = getRootWindowInsets() != null ? getRootWindowInsets().getStableInsetBottom() : 0; - return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight + + return mDisplaySize.y + - mExpandedViewContainerLocation[1] + - getPaddingTop() + - getPaddingBottom() + - mSettingsIconHeight + - mPointerHeight - mPointerMargin - bottomInset; } /** * Update appearance of the expanded view being displayed. + * + * @param containerLocationOnScreen The location on-screen of the container the expanded view is + * added to. This allows us to calculate max height without + * waiting for layout. */ - public void updateView() { + public void updateView(int[] containerLocationOnScreen) { if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "updateView: bubble=" + getBubbleKey()); } + + mExpandedViewContainerLocation = containerLocationOnScreen; + if (usingActivityView() && mActivityView.getVisibility() == VISIBLE && mActivityView.isAttachedToWindow()) { mActivityView.onLocationChanged(); + updateHeight(); } - updateHeight(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java index f4d64322c7ff..47120124a55f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java @@ -17,10 +17,8 @@ package com.android.systemui.bubbles; import android.content.Context; -import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; -import android.graphics.drawable.ShapeDrawable; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; @@ -28,7 +26,6 @@ import android.widget.TextView; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.R; -import com.android.systemui.recents.TriangleShape; /** * Educational view to highlight the manage button that allows a user to configure the settings @@ -36,7 +33,6 @@ import com.android.systemui.recents.TriangleShape; */ public class BubbleManageEducationView extends LinearLayout { - private View mPointerView; private View mManageView; public BubbleManageEducationView(Context context) { @@ -70,26 +66,8 @@ public class BubbleManageEducationView extends LinearLayout { ta.recycle(); textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true); + ((TextView) findViewById(R.id.user_education_title)).setTextColor(textColor); ((TextView) findViewById(R.id.user_education_description)).setTextColor(textColor); - - final Resources res = getResources(); - final int pointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width); - final int pointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); - - ShapeDrawable triangleShape = - new ShapeDrawable(TriangleShape.create( - pointerWidth, pointerHeight, false /* isPointingUp */)); - triangleShape.setTint(bgColor); - - mPointerView = findViewById(R.id.user_education_pointer); - mPointerView.setBackground(triangleShape); - } - - /** - * Specifies the x value this pointer should point to. - */ - public void setPointerPosition(int x) { - mPointerView.setTranslationX(x - (mPointerView.getWidth() / 2)); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java index b77e2261e39b..0c62e9f9f548 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java @@ -62,13 +62,12 @@ public class BubbleOverflow implements BubbleViewProvider { void setUpOverflow(ViewGroup parentViewGroup, BubbleStackView stackView) { updateDimensions(); - mExpandedView = (BubbleExpandedView) mInflater.inflate( R.layout.bubble_expanded_view, parentViewGroup /* root */, false /* attachToRoot */); mExpandedView.setOverflow(true); mExpandedView.setStackView(stackView); - + mExpandedView.applyThemeAttrs(); updateIcon(mContext, parentViewGroup); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index ba563466ca4e..1437501a9ea1 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -56,6 +56,8 @@ import android.view.DisplayCutout; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; @@ -82,6 +84,7 @@ import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; +import com.android.systemui.bubbles.animation.AnimatableScaleMatrix; import com.android.systemui.bubbles.animation.ExpandedAnimationController; import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; import com.android.systemui.bubbles.animation.StackAnimationController; @@ -147,6 +150,16 @@ public class BubbleStackView extends FrameLayout StackAnimationController.IME_ANIMATION_STIFFNESS, StackAnimationController.DEFAULT_BOUNCINESS); + private final PhysicsAnimator.SpringConfig mScaleInSpringConfig = + new PhysicsAnimator.SpringConfig(300f, 0.9f); + + private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig = + new PhysicsAnimator.SpringConfig(900f, 1f); + + private final PhysicsAnimator.SpringConfig mTranslateSpringConfig = + new PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY); + /** * Interface to synchronize {@link View} state and the screen. * @@ -186,8 +199,6 @@ public class BubbleStackView extends FrameLayout private Point mDisplaySize; - private final SpringAnimation mExpandedViewXAnim; - private final SpringAnimation mExpandedViewYAnim; private final BubbleData mBubbleData; private final ValueAnimator mDesaturateAndDarkenAnimator; @@ -199,6 +210,24 @@ public class BubbleStackView extends FrameLayout private FrameLayout mExpandedViewContainer; + /** Matrix used to scale the expanded view container with a given pivot point. */ + private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix(); + + /** + * SurfaceView that we draw screenshots of animating-out bubbles into. This allows us to animate + * between bubble activities without needing both to be alive at the same time. + */ + private SurfaceView mAnimatingOutSurfaceView; + + /** Container for the animating-out SurfaceView. */ + private FrameLayout mAnimatingOutSurfaceContainer; + + /** + * Buffer containing a screenshot of the animating-out bubble. This is drawn into the + * SurfaceView during animations. + */ + private SurfaceControl.ScreenshotGraphicBuffer mAnimatingOutBubbleBuffer; + private BubbleFlyoutView mFlyout; /** Runnable that fades out the flyout and then sets it to GONE. */ private Runnable mHideFlyout = () -> animateFlyoutCollapsed(true, 0 /* velX */); @@ -230,8 +259,7 @@ public class BubbleStackView extends FrameLayout private int mBubblePaddingTop; private int mBubbleTouchPadding; private int mExpandedViewPadding; - private int mExpandedAnimateXDistance; - private int mExpandedAnimateYDistance; + private int mCornerRadius; private int mPointerHeight; private int mStatusBarHeight; private int mImeOffset; @@ -680,10 +708,6 @@ public class BubbleStackView extends FrameLayout mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding); - mExpandedAnimateXDistance = - res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance); - mExpandedAnimateYDistance = - res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance); mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); mStatusBarHeight = @@ -698,6 +722,11 @@ public class BubbleStackView extends FrameLayout mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); + final TypedArray ta = mContext.obtainStyledAttributes( + new int[] {android.R.attr.dialogCornerRadius}); + mCornerRadius = ta.getDimensionPixelSize(0, 0); + ta.recycle(); + final Runnable onBubbleAnimatedOut = () -> { if (getBubbleCount() == 0) { allBubblesAnimatedOutAction.run(); @@ -731,6 +760,24 @@ public class BubbleStackView extends FrameLayout mExpandedViewContainer.setClipChildren(false); addView(mExpandedViewContainer); + mAnimatingOutSurfaceContainer = new FrameLayout(getContext()); + mAnimatingOutSurfaceContainer.setLayoutParams( + new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + addView(mAnimatingOutSurfaceContainer); + + mAnimatingOutSurfaceView = new SurfaceView(getContext()); + mAnimatingOutSurfaceView.setUseAlpha(); + mAnimatingOutSurfaceView.setZOrderOnTop(true); + mAnimatingOutSurfaceView.setCornerRadius(mCornerRadius); + mAnimatingOutSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(0, 0)); + mAnimatingOutSurfaceContainer.addView(mAnimatingOutSurfaceView); + + mAnimatingOutSurfaceContainer.setPadding( + mExpandedViewPadding, + mExpandedViewPadding, + mExpandedViewPadding, + mExpandedViewPadding); + setUpManageMenu(); setUpFlyout(); @@ -776,26 +823,6 @@ public class BubbleStackView extends FrameLayout // MagnetizedObjects. mMagneticTarget = new MagnetizedObject.MagneticTarget(mDismissTargetCircle, dismissRadius); - mExpandedViewXAnim = - new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X); - mExpandedViewXAnim.setSpring( - new SpringForce() - .setStiffness(SpringForce.STIFFNESS_LOW) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); - - mExpandedViewYAnim = - new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_Y); - mExpandedViewYAnim.setSpring( - new SpringForce() - .setStiffness(SpringForce.STIFFNESS_LOW) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); - mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> { - if (mIsExpanded && mExpandedBubble != null - && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().updateView(); - } - }); - setClipChildren(false); setFocusable(true); mBubbleContainer.bringToFront(); @@ -830,7 +857,7 @@ public class BubbleStackView extends FrameLayout if (mIsExpanded) { mExpandedViewContainer.setTranslationY(getExpandedViewY()); if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().updateView(); + mExpandedBubble.getExpandedView().updateView(getLocationOnScreen()); } } @@ -954,15 +981,10 @@ public class BubbleStackView extends FrameLayout PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig); - final TypedArray ta = mContext.obtainStyledAttributes( - new int[] {android.R.attr.dialogCornerRadius}); - final int menuCornerRadius = ta.getDimensionPixelSize(0, 0); - ta.recycle(); - mManageMenu.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), menuCornerRadius); + outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius); } }); mManageMenu.setClipToOutline(true); @@ -1078,13 +1100,11 @@ public class BubbleStackView extends FrameLayout mBubbleOverflow.setUpOverflow(mBubbleContainer, this); } else { mBubbleContainer.removeView(mBubbleOverflow.getBtn()); - mBubbleOverflow.updateDimensions(); - mBubbleOverflow.updateIcon(mContext,this); + mBubbleOverflow.setUpOverflow(mBubbleContainer, this); overflowBtnIndex = mBubbleContainer.getChildCount(); } mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - mBubbleOverflow.getBtn().setOnClickListener(v -> setSelectedBubble(mBubbleOverflow)); } /** @@ -1095,6 +1115,7 @@ public class BubbleStackView extends FrameLayout setUpOverflow(); setUpUserEducation(); setUpManageMenu(); + updateExpandedViewTheme(); } /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */ @@ -1196,6 +1217,18 @@ public class BubbleStackView extends FrameLayout setupLocalMenu(info); } + void updateExpandedViewTheme() { + final List<Bubble> bubbles = mBubbleData.getBubbles(); + if (bubbles.isEmpty()) { + return; + } + bubbles.forEach(bubble -> { + if (bubble.getExpandedView() != null) { + bubble.getExpandedView().applyThemeAttrs(); + } + }); + } + void setupLocalMenu(AccessibilityNodeInfo info) { Resources res = mContext.getResources(); @@ -1447,6 +1480,31 @@ public class BubbleStackView extends FrameLayout mBubbleData.setShowingOverflow(true); } + // If we're expanded, screenshot the currently expanded bubble (before expanding the newly + // selected bubble) so we can animate it out. + if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + // Before screenshotting, have the real ActivityView show on top of other surfaces + // so that the screenshot doesn't flicker on top of it. + mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true); + } + + try { + screenshotAnimatingOutBubbleIntoSurface((success) -> { + mAnimatingOutSurfaceContainer.setVisibility( + success ? View.VISIBLE : View.INVISIBLE); + showNewlySelectedBubble(bubbleToSelect); + }); + } catch (Exception e) { + showNewlySelectedBubble(bubbleToSelect); + e.printStackTrace(); + } + } else { + showNewlySelectedBubble(bubbleToSelect); + } + } + + private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) { final BubbleViewProvider previouslySelected = mExpandedBubble; mExpandedBubble = bubbleToSelect; updatePointerPosition(); @@ -1574,12 +1632,19 @@ public class BubbleStackView extends FrameLayout final int inset = getResources().getDimensionPixelSize( R.dimen.bubbles_manage_education_top_inset); mManageEducationView.bringToFront(); - mManageEducationView.setManageViewPosition(mTempRect.left, - mTempRect.top - viewHeight + inset); - mManageEducationView.setPointerPosition(mTempRect.centerX() - mTempRect.left); + mManageEducationView.setManageViewPosition(0, mTempRect.top - viewHeight + inset); mManageEducationView.animate() .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION) .setInterpolator(FAST_OUT_SLOW_IN).alpha(1); + mManageEducationView.findViewById(R.id.manage).setOnClickListener(view -> { + mExpandedBubble.getExpandedView().findViewById(R.id.settings_button) + .performClick(); + maybeShowManageEducation(false); + }); + mManageEducationView.findViewById(R.id.got_it).setOnClickListener(view -> + maybeShowManageEducation(false)); + mManageEducationView.setOnClickListener(view -> + maybeShowManageEducation(false)); }); Prefs.putBoolean(getContext(), HAS_SEEN_BUBBLES_MANAGE_EDUCATION, true); } else if (!show @@ -1636,84 +1701,224 @@ public class BubbleStackView extends FrameLayout } } + void hideImeFromExpandedBubble() { + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + // Hide the currently expanded bubble's IME if it's visible before switching to a new + // bubble. + mExpandedBubble.getExpandedView().hideImeIfVisible(); + } + } + private void beforeExpandedViewAnimation() { + mIsExpansionAnimating = true; hideFlyoutImmediate(); updateExpandedBubble(); updateExpandedView(); - mIsExpansionAnimating = true; } private void afterExpandedViewAnimation() { - updateExpandedView(); mIsExpansionAnimating = false; + updateExpandedView(); requestUpdate(); } + private void animateExpansion() { + mIsExpanded = true; + hideStackUserEducation(true /* fromExpansion */); + beforeExpandedViewAnimation(); + + mBubbleContainer.setActiveController(mExpandedAnimationController); + updateOverflowVisibility(); + updatePointerPosition(); + mExpandedAnimationController.expandFromStack(() -> { + afterExpandedViewAnimation(); + maybeShowManageEducation(true); + } /* after */); + + mExpandedViewContainer.setTranslationX(0); + mExpandedViewContainer.setTranslationY(getExpandedViewY()); + mExpandedViewContainer.setAlpha(1f); + + // X-value of the bubble we're expanding, once it's settled in its row. + final float bubbleWillBeAtX = + mExpandedAnimationController.getBubbleLeft( + mBubbleData.getBubbles().indexOf(mExpandedBubble)); + + // How far horizontally the bubble will be animating. We'll wait a bit longer for bubbles + // that are animating farther, so that the expanded view doesn't move as much. + final float horizontalDistanceAnimated = + Math.abs(bubbleWillBeAtX + - mStackAnimationController.getStackPosition().x); + + // Wait for the path animation target to reach its end, and add a small amount of extra time + // if the bubble is moving a lot horizontally. + long startDelay = 0L; + + // Should not happen since we lay out before expanding, but just in case... + if (getWidth() > 0) { + startDelay = (long) + (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION + + (horizontalDistanceAnimated / getWidth()) * 30); + } + + // Set the pivot point for the scale, so the expanded view animates out from the bubble. + mExpandedViewContainerMatrix.setScale( + 0f, 0f, + bubbleWillBeAtX + mBubbleSize / 2f, getExpandedViewY()); + mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); + + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); + } + + postDelayed(() -> PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .addUpdateListener((target, values) -> { + mExpandedViewContainerMatrix.postTranslate( + mExpandedBubble.getIconView().getTranslationX() + - bubbleWillBeAtX, + 0); + mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); + }) + .withEndActions(() -> { + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); + } + }) + .start(), startDelay); + + } + private void animateCollapse() { // Hide the menu if it's visible. showManageMenu(false); mIsExpanded = false; - final BubbleViewProvider previouslySelected = mExpandedBubble; - beforeExpandedViewAnimation(); - maybeShowManageEducation(false); - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "animateCollapse"); - Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(), - mExpandedBubble)); - } - updateOverflowVisibility(); mBubbleContainer.cancelAllAnimations(); - mExpandedAnimationController.collapseBackToStack( + + // If we were in the middle of swapping, the animating-out surface would have been scaling + // to zero - finish it off. + PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel(); + mAnimatingOutSurfaceContainer.setScaleX(0f); + mAnimatingOutSurfaceContainer.setScaleY(0f); + + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView().hideImeIfVisible(); + } + + final long startDelay = + (long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 0.6f); + postDelayed(() -> mExpandedAnimationController.collapseBackToStack( mStackAnimationController.getStackPositionAlongNearestHorizontalEdge() /* collapseTo */, () -> { mBubbleContainer.setActiveController(mStackAnimationController); + }), startDelay); + + // We want to visually collapse into this bubble during the animation. + final View expandingFromBubble = mExpandedBubble.getIconView(); + + // X-value the bubble is animating from (back into the stack). + final float expandingFromBubbleAtX = + mExpandedAnimationController.getBubbleLeft( + mBubbleData.getBubbles().indexOf(mExpandedBubble)); + + // Set the pivot point. + mExpandedViewContainerMatrix.setScale( + 1f, 1f, + expandingFromBubbleAtX + mBubbleSize / 2f, + getExpandedViewY()); + + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, 0f, mScaleOutSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, 0f, mScaleOutSpringConfig) + .addUpdateListener((target, values) -> { + if (expandingFromBubble != null) { + // Follow the bubble as it translates! + mExpandedViewContainerMatrix.postTranslate( + expandingFromBubble.getTranslationX() + - expandingFromBubbleAtX, 0f); + } + + mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); + + // Hide early so we don't have a tiny little expanded view still visible at the + // end of the scale animation. + if (mExpandedViewContainerMatrix.getScaleX() < 0.05f) { + mExpandedViewContainer.setVisibility(View.INVISIBLE); + } + }) + .withEndActions(() -> { + final BubbleViewProvider previouslySelected = mExpandedBubble; + beforeExpandedViewAnimation(); + maybeShowManageEducation(false); + + if (DEBUG_BUBBLE_STACK_VIEW) { + Log.d(TAG, "animateCollapse"); + Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(), + mExpandedBubble)); + } + updateOverflowVisibility(); + afterExpandedViewAnimation(); previouslySelected.setContentVisibility(false); - }); - - mExpandedViewXAnim.animateToFinalPosition(getCollapsedX()); - mExpandedViewYAnim.animateToFinalPosition(getCollapsedY()); - mExpandedViewContainer.animate() - .setDuration(100) - .alpha(0f); + }) + .start(); } - private void animateExpansion() { - mIsExpanded = true; - hideStackUserEducation(true /* fromExpansion */); - beforeExpandedViewAnimation(); + private void animateSwitchBubbles() { + // The surface contains a screenshot of the animating out bubble, so we just need to animate + // it out (and then release the GraphicBuffer). + PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel(); + PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer) + .spring(DynamicAnimation.SCALE_X, 0f, mScaleOutSpringConfig) + .spring(DynamicAnimation.SCALE_Y, 0f, mScaleOutSpringConfig) + .spring(DynamicAnimation.TRANSLATION_Y, + mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize * 2, + mTranslateSpringConfig) + .withEndActions(this::releaseAnimatingOutBubbleBuffer) + .start(); - mBubbleContainer.setActiveController(mExpandedAnimationController); - updateOverflowVisibility(); - mExpandedAnimationController.expandFromStack(() -> { - updatePointerPosition(); - afterExpandedViewAnimation(); - maybeShowManageEducation(true); - } /* after */); + float expandingFromBubbleDestinationX = + mExpandedAnimationController.getBubbleLeft( + mBubbleData.getBubbles().indexOf(mExpandedBubble)); - mExpandedViewContainer.setTranslationX(getCollapsedX()); - mExpandedViewContainer.setTranslationY(getCollapsedY()); - mExpandedViewContainer.setAlpha(0f); + mExpandedViewContainer.setAlpha(1f); + mExpandedViewContainer.setVisibility(View.VISIBLE); - mExpandedViewXAnim.animateToFinalPosition(0f); - mExpandedViewYAnim.animateToFinalPosition(getExpandedViewY()); - mExpandedViewContainer.animate() - .setDuration(100) - .alpha(1f); - } + mExpandedViewContainerMatrix.setScale( + 0f, 0f, expandingFromBubbleDestinationX + mBubbleSize / 2f, getExpandedViewY()); + mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); - private float getCollapsedX() { - return mStackAnimationController.getStackPosition().x < getWidth() / 2 - ? -mExpandedAnimateXDistance - : mExpandedAnimateXDistance; - } + mExpandedViewContainer.postDelayed(() -> { + if (!mIsExpanded) { + return; + } - private float getCollapsedY() { - return Math.min(mStackAnimationController.getStackPosition().y, - mExpandedAnimateYDistance); + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .addUpdateListener((target, values) -> { + mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); + }) + .withEndActions(() -> { + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); + } + }) + .start(); + }, 25); } private void notifyExpansionChanged(BubbleViewProvider bubble, boolean expanded) { @@ -1780,9 +1985,13 @@ public class BubbleStackView extends FrameLayout */ @Override public void subtractObscuredTouchableRegion(Region touchableRegion, View view) { - // If the notification shade is expanded, or the manage menu is open, we shouldn't let the - // ActivityView steal any touch events from any location. - if (!mIsExpanded || mShowingManage) { + // If the notification shade is expanded, or the manage menu is open, or we are showing + // manage bubbles user education, we shouldn't let the ActivityView steal any touch events + // from any location. + if (!mIsExpanded + || mShowingManage + || (mManageEducationView != null + && mManageEducationView.getVisibility() == VISIBLE)) { touchableRegion.setEmpty(); } } @@ -2218,21 +2427,120 @@ public class BubbleStackView extends FrameLayout Log.d(TAG, "updateExpandedBubble()"); } - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - // Hide the currently expanded bubble's IME if it's visible before switching to a new - // bubble. - mExpandedBubble.getExpandedView().hideImeIfVisible(); - } + hideImeFromExpandedBubble(); mExpandedViewContainer.removeAllViews(); if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { BubbleExpandedView bev = mExpandedBubble.getExpandedView(); + bev.setContentVisibility(false); + mExpandedViewContainerMatrix.setScaleX(0f); + mExpandedViewContainerMatrix.setScaleY(0f); + mExpandedViewContainer.setVisibility(View.INVISIBLE); + mExpandedViewContainer.setAlpha(0f); mExpandedViewContainer.addView(bev); bev.setManageClickListener((view) -> showManageMenu(!mShowingManage)); bev.populateExpandedView(); - mExpandedViewContainer.setVisibility(VISIBLE); - mExpandedViewContainer.setAlpha(1.0f); + + if (!mIsExpansionAnimating) { + mSurfaceSynchronizer.syncSurfaceAndRun(() -> { + post(this::animateSwitchBubbles); + }); + } + } + } + + /** + * Requests a snapshot from the currently expanded bubble's ActivityView and displays it in a + * SurfaceView. This allows us to load a newly expanded bubble's Activity into the ActivityView, + * while animating the (screenshot of the) previously selected bubble's content away. + * + * @param onComplete Callback to run once we're done here - called with 'false' if something + * went wrong, or 'true' if the SurfaceView is now showing a screenshot of the + * expanded bubble. + */ + private void screenshotAnimatingOutBubbleIntoSurface(Consumer<Boolean> onComplete) { + if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { + // You can't animate null. + onComplete.accept(false); + return; + } + + final BubbleExpandedView animatingOutExpandedView = mExpandedBubble.getExpandedView(); + + // Release the previous screenshot if it hasn't been released already. + if (mAnimatingOutBubbleBuffer != null) { + releaseAnimatingOutBubbleBuffer(); + } + + try { + mAnimatingOutBubbleBuffer = animatingOutExpandedView.snapshotActivitySurface(); + } catch (Exception e) { + // If we fail for any reason, print the stack trace and then notify the callback of our + // failure. This is not expected to occur, but it's not worth crashing over. + Log.wtf(TAG, e); + onComplete.accept(false); + } + + if (mAnimatingOutBubbleBuffer == null + || mAnimatingOutBubbleBuffer.getGraphicBuffer() == null) { + // While no exception was thrown, we were unable to get a snapshot. + onComplete.accept(false); + return; + } + + // Make sure the surface container's properties have been reset. + PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel(); + mAnimatingOutSurfaceContainer.setScaleX(1f); + mAnimatingOutSurfaceContainer.setScaleY(1f); + mAnimatingOutSurfaceContainer.setTranslationX(0); + mAnimatingOutSurfaceContainer.setTranslationY(0); + + final int[] activityViewLocation = + mExpandedBubble.getExpandedView().getActivityViewLocationOnScreen(); + final int[] surfaceViewLocation = mAnimatingOutSurfaceView.getLocationOnScreen(); + + // Translate the surface to overlap the real ActivityView. + mAnimatingOutSurfaceContainer.setTranslationY( + activityViewLocation[1] - surfaceViewLocation[1]); + + // Set the width/height of the SurfaceView to match the snapshot. + mAnimatingOutSurfaceView.getLayoutParams().width = + mAnimatingOutBubbleBuffer.getGraphicBuffer().getWidth(); + mAnimatingOutSurfaceView.getLayoutParams().height = + mAnimatingOutBubbleBuffer.getGraphicBuffer().getHeight(); + mAnimatingOutSurfaceView.requestLayout(); + + // Post to wait for layout. + post(() -> { + // The buffer might have been destroyed if the user is mashing on bubbles, that's okay. + if (mAnimatingOutBubbleBuffer.getGraphicBuffer().isDestroyed()) { + onComplete.accept(false); + return; + } + + if (!mIsExpanded) { + onComplete.accept(false); + return; + } + + // Attach the buffer! We're now displaying the snapshot. + mAnimatingOutSurfaceView.getHolder().getSurface().attachAndQueueBufferWithColorSpace( + mAnimatingOutBubbleBuffer.getGraphicBuffer(), + mAnimatingOutBubbleBuffer.getColorSpace()); + + mSurfaceSynchronizer.syncSurfaceAndRun(() -> post(() -> onComplete.accept(true))); + }); + } + + /** + * Releases the buffer containing the screenshot of the animating-out bubble, if it exists and + * isn't yet destroyed. + */ + private void releaseAnimatingOutBubbleBuffer() { + if (mAnimatingOutBubbleBuffer != null + && !mAnimatingOutBubbleBuffer.getGraphicBuffer().isDestroyed()) { + mAnimatingOutBubbleBuffer.getGraphicBuffer().destroy(); } } @@ -2242,19 +2550,10 @@ public class BubbleStackView extends FrameLayout } mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); - if (mIsExpanded) { - final float y = getExpandedViewY(); - if (!mExpandedViewYAnim.isRunning()) { - // We're not animating so set the value - mExpandedViewContainer.setTranslationY(y); - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().updateView(); - } - } else { - // We are animating so update the value; there is an end listener on the animator - // that will ensure expandedeView.updateView gets called. - mExpandedViewYAnim.animateToFinalPosition(y); - } + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + mExpandedViewContainer.setTranslationY(getExpandedViewY()); + mExpandedBubble.getExpandedView().updateView( + mExpandedViewContainer.getLocationOnScreen()); } mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java new file mode 100644 index 000000000000..ae7833634794 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.bubbles.animation; + +import android.graphics.Matrix; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.FloatPropertyCompat; + +/** + * Matrix whose scale properties can be animated using physics animations, via the {@link #SCALE_X} + * and {@link #SCALE_Y} FloatProperties. + * + * This is useful when you need to perform a scale animation with a pivot point, since pivot points + * are not supported by standard View scale operations but are supported by matrices. + * + * NOTE: DynamicAnimation assumes that all custom properties are denominated in pixels, and thus + * considers 1 to be the smallest user-visible change for custom properties. This means that if you + * animate {@link #SCALE_X} and {@link #SCALE_Y} to 3f, for example, the animation would have only + * three frames. + * + * To work around this, whenever animating to a desired scale value, animate to the value returned + * by {@link #getAnimatableValueForScaleFactor} instead. The SCALE_X and SCALE_Y properties will + * convert that (larger) value into the appropriate scale factor when scaling the matrix. + */ +public class AnimatableScaleMatrix extends Matrix { + + /** + * The X value of the scale. + * + * NOTE: This must be set or animated to the value returned by + * {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself. + */ + public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_X = + new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleX") { + @Override + public float getValue(AnimatableScaleMatrix object) { + return getAnimatableValueForScaleFactor(object.mScaleX); + } + + @Override + public void setValue(AnimatableScaleMatrix object, float value) { + object.setScaleX(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE); + } + }; + + /** + * The Y value of the scale. + * + * NOTE: This must be set or animated to the value returned by + * {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself. + */ + public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_Y = + new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleY") { + @Override + public float getValue(AnimatableScaleMatrix object) { + return getAnimatableValueForScaleFactor(object.mScaleY); + } + + @Override + public void setValue(AnimatableScaleMatrix object, float value) { + object.setScaleY(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE); + } + }; + + private float mScaleX = 1f; + private float mScaleY = 1f; + + private float mPivotX = 0f; + private float mPivotY = 0f; + + /** + * Return the value to animate SCALE_X or SCALE_Y to in order to achieve the desired scale + * factor. + */ + public static float getAnimatableValueForScaleFactor(float scale) { + return scale * (1f / DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE); + } + + @Override + public void setScale(float sx, float sy, float px, float py) { + mScaleX = sx; + mScaleY = sy; + mPivotX = px; + mPivotY = py; + super.setScale(mScaleX, mScaleY, mPivotX, mPivotY); + } + + public void setScaleX(float scaleX) { + mScaleX = scaleX; + super.setScale(mScaleX, mScaleY, mPivotX, mPivotY); + } + + public void setScaleY(float scaleY) { + mScaleY = scaleY; + super.setScale(mScaleX, mScaleY, mPivotX, mPivotY); + } + + public void setPivotX(float pivotX) { + mPivotX = pivotX; + super.setScale(mScaleX, mScaleY, mPivotX, mPivotY); + } + + public void setPivotY(float pivotY) { + mPivotY = pivotY; + super.setScale(mScaleX, mScaleY, mPivotX, mPivotY); + } + + public float getScaleX() { + return mScaleX; + } + + public float getScaleY() { + return mScaleY; + } + + public float getPivotX() { + return mPivotX; + } + + public float getPivotY() { + return mPivotY; + } +} 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 76ff1afef3f7..86fe10dddc2c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -56,7 +56,7 @@ public class ExpandedAnimationController private static final int ANIMATE_TRANSLATION_FACTOR = 4; /** Duration of the expand/collapse target path animation. */ - private static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175; + public static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175; /** Stiffness for the expand/collapse path-following animation. */ private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000; diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index 91f032d86a94..28bcf3a35117 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -24,6 +24,8 @@ import com.android.systemui.keyguard.WorkLockActivity; import com.android.systemui.screenrecord.ScreenRecordDialog; import com.android.systemui.settings.BrightnessDialog; import com.android.systemui.tuner.TunerActivity; +import com.android.systemui.usb.UsbDebuggingActivity; +import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity; import dagger.Binds; import dagger.Module; @@ -70,4 +72,17 @@ public abstract class DefaultActivityBinder { @IntoMap @ClassKey(BubbleOverflowActivity.class) public abstract Activity bindBubbleOverflowActivity(BubbleOverflowActivity activity); + + /** Inject into UsbDebuggingActivity. */ + @Binds + @IntoMap + @ClassKey(UsbDebuggingActivity.class) + public abstract Activity bindUsbDebuggingActivity(UsbDebuggingActivity activity); + + /** Inject into UsbDebuggingSecondaryUserActivity. */ + @Binds + @IntoMap + @ClassKey(UsbDebuggingSecondaryUserActivity.class) + public abstract Activity bindUsbDebuggingSecondaryUserActivity( + UsbDebuggingSecondaryUserActivity activity); } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java new file mode 100644 index 000000000000..7d1f1c2709fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for BroadcastDispatcher-related messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface BroadcastDispatcherLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 9c89fee5cba1..a2086e8d3ac6 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -97,6 +97,18 @@ public class LogModule { return buffer; } + /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */ + @Provides + @Singleton + @BroadcastDispatcherLog + public static LogBuffer provideBroadcastDispatcherLogBuffer( + LogcatEchoTracker bufferFilter, + DumpManager dumpManager) { + LogBuffer buffer = new LogBuffer("BroadcastDispatcherLog", 500, 10, bufferFilter); + buffer.attach(dumpManager); + return buffer; + } + /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */ @Provides @Singleton diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index 2f521ea39242..de0af16bc2fa 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -34,7 +34,6 @@ class MediaDeviceManager @Inject constructor( private val context: Context, private val localMediaManagerFactory: LocalMediaManagerFactory, private val mr2manager: MediaRouter2Manager, - private val featureFlag: MediaFeatureFlag, @Main private val fgExecutor: Executor, private val mediaDataManager: MediaDataManager ) : MediaDataManager.Listener { @@ -56,20 +55,19 @@ class MediaDeviceManager @Inject constructor( fun removeListener(listener: Listener) = listeners.remove(listener) override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { - if (featureFlag.enabled) { - if (oldKey != null && oldKey != key) { - val oldToken = entries.remove(oldKey) - oldToken?.stop() - } - var tok = entries[key] - if (tok == null && data.token != null) { - val controller = MediaController(context, data.token!!) - tok = Token(key, controller, localMediaManagerFactory.create(data.packageName)) - entries[key] = tok - tok.start() + if (oldKey != null && oldKey != key) { + val oldEntry = entries.remove(oldKey) + oldEntry?.stop() + } + var entry = entries[key] + if (entry == null || entry?.token != data.token) { + entry?.stop() + val controller = data.token?.let { + MediaController(context, it) } - } else { - onMediaDataRemoved(key) + entry = Token(key, controller, localMediaManagerFactory.create(data.packageName)) + entries[key] = entry + entry.start() } } @@ -100,9 +98,11 @@ class MediaDeviceManager @Inject constructor( private inner class Token( val key: String, - val controller: MediaController, + val controller: MediaController?, val localMediaManager: LocalMediaManager ) : LocalMediaManager.DeviceCallback { + val token + get() = controller?.sessionToken private var started = false private var current: MediaDevice? = null set(value) { @@ -132,10 +132,14 @@ class MediaDeviceManager @Inject constructor( } private fun updateCurrent() { val device = localMediaManager.getCurrentConnectedDevice() - val route = mr2manager.getRoutingSessionForMediaController(controller) - // If we get a null route, then don't trust the device. Just set to null to disable the - // output switcher chip. - current = if (route != null) device else null + controller?.let { + val route = mr2manager.getRoutingSessionForMediaController(it) + // If we get a null route, then don't trust the device. Just set to null to disable the + // output switcher chip. + current = if (route != null) device else null + } ?: run { + current = device + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java index 7f7e1085d497..2980f11b3cbc 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java @@ -16,6 +16,7 @@ package com.android.systemui.pip; +import android.animation.AnimationHandler; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.IntDef; @@ -26,6 +27,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -79,6 +81,13 @@ public class PipAnimationController { private PipTransitionAnimator mCurrentAnimator; + private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = + ThreadLocal.withInitial(() -> { + AnimationHandler handler = new AnimationHandler(); + handler.setProvider(new SfVsyncFrameCallbackProvider()); + return handler; + }); + @Inject PipAnimationController(Context context, PipSurfaceTransactionHelper helper) { mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, @@ -135,6 +144,7 @@ public class PipAnimationController { animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); animator.setInterpolator(mFastOutSlowInInterpolator); animator.setFloatValues(FRACTION_START, FRACTION_END); + animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get()); return animator; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index c6f144aa57a1..b93e07e65c73 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -504,8 +504,18 @@ public class PipTaskOrganizer extends TaskOrganizer implements // this could happen if rotation finishes before the animation mLastReportedBounds.set(destinationBoundsOut); scheduleFinishResizePip(mLastReportedBounds); - } else if (!mLastReportedBounds.isEmpty()) { - destinationBoundsOut.set(mLastReportedBounds); + } else { + // There could be an animation on-going. If there is one on-going, last-reported + // bounds isn't yet updated. We'll use the animator's bounds instead. + if (animator != null && animator.isRunning()) { + if (!animator.getDestinationBounds().isEmpty()) { + destinationBoundsOut.set(animator.getDestinationBounds()); + } + } else { + if (!mLastReportedBounds.isEmpty()) { + destinationBoundsOut.set(mLastReportedBounds); + } + } } return; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index b714bff15d8a..e38bfb441a0b 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -269,7 +269,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio if (stackInfo != null) { // If SystemUI restart, and it already existed a pinned stack, // register the pip input consumer to ensure touch can send to it. - mInputConsumerController.registerInputConsumer(); + mInputConsumerController.registerInputConsumer(true /* withSfVsync */); } } catch (RemoteException | UnsupportedOperationException e) { e.printStackTrace(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index a3185a2ad796..6ab73fcce399 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -295,6 +295,14 @@ public class PipMenuActivity extends Activity { } @Override + public void onTopResumedActivityChanged(boolean isTopResumedActivity) { + super.onTopResumedActivityChanged(isTopResumedActivity); + if (!isTopResumedActivity && mMenuState != MENU_STATE_NONE) { + hideMenu(); + } + } + + @Override protected void onStop() { super.onStop(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index 8b4d932619a9..31d292fa3fd7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -197,7 +197,7 @@ public class PipMenuActivityController { } public void onActivityPinned() { - mInputConsumerController.registerInputConsumer(); + mInputConsumerController.registerInputConsumer(true /* withSfVsync */); } public void onActivityUnpinned() { 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 2e75bab7ae32..d077666f8184 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -23,9 +23,11 @@ import android.content.Context; import android.graphics.Rect; import android.os.Debug; import android.util.Log; +import android.view.Choreographer; import androidx.dynamicanimation.animation.SpringForce; +import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.pip.PipSnapAlgorithm; import com.android.systemui.pip.PipTaskOrganizer; import com.android.systemui.util.FloatingContentCoordinator; @@ -68,6 +70,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** The region that all of PIP must stay within. */ private final Rect mFloatingAllowedArea = new Rect(); + private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider = + new SfVsyncFrameCallbackProvider(); + /** * Bounds that are animated using the physics animator. */ @@ -79,6 +84,10 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** Coordinator instance for resolving conflicts with other floating content. */ private FloatingContentCoordinator mFloatingContentCoordinator; + /** Callback that re-sizes PIP to the animated bounds. */ + private final Choreographer.FrameCallback mResizePipVsyncCallback = + l -> resizePipUnchecked(mAnimatedBounds); + /** * PhysicsAnimator instance for animating {@link #mAnimatedBounds} using physics animations. */ @@ -89,7 +98,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Update listener that resizes the PIP to {@link #mAnimatedBounds}. */ final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener = - (target, values) -> resizePipUnchecked(mAnimatedBounds); + (target, values) -> mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback); /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ private PhysicsAnimator.FlingConfig mFlingConfigX; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java index bafbd216b1a2..f6b212c6f19f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java @@ -32,6 +32,8 @@ import android.hardware.input.InputManager; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; +import android.view.BatchedInputEventReceiver; +import android.view.Choreographer; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; @@ -323,9 +325,9 @@ public class PipResizeGestureHandler { mMinSize.set(minX, minY); } - class SysUiInputEventReceiver extends InputEventReceiver { + class SysUiInputEventReceiver extends BatchedInputEventReceiver { SysUiInputEventReceiver(InputChannel channel, Looper looper) { - super(channel, looper); + super(channel, looper, Choreographer.getSfInstance()); } public void onInputEvent(InputEvent event) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index a624479fa63c..57436bc9e675 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -41,13 +41,19 @@ import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Color; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.graphics.drawable.InsetDrawable; +import android.graphics.drawable.LayerDrawable; import android.media.MediaActionSound; import android.net.Uri; import android.os.Handler; @@ -208,6 +214,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private Animator mScreenshotAnimation; private Runnable mOnCompleteRunnable; private Animator mDismissAnimation; + private SavedImageData mImageData; private boolean mInDarkMode = false; private boolean mDirectionLTR = true; private boolean mOrientationPortrait = true; @@ -226,6 +233,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset switch (msg.what) { case MESSAGE_CORNER_TIMEOUT: mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT); + if (mImageData != null) { + mNotificationsController.showSilentScreenshotNotification(mImageData); + } GlobalScreenshot.this.dismissScreenshot("timeout", false); mOnCompleteRunnable.run(); break; @@ -396,6 +406,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button); mDismissButton.setOnClickListener(view -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL); + if (mImageData != null) { + mNotificationsController.showSilentScreenshotNotification(mImageData); + } dismissScreenshot("dismiss_button", false); mOnCompleteRunnable.run(); }); @@ -436,6 +449,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); } + mImageData = null; // make sure we clear the current stored data + mNotificationsController.reset(); + mNotificationsController.setImage(mScreenBitmap); + mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); mSaveInBgTask.execute(); } @@ -449,10 +466,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset int rot = mDisplay.getRotation(); int width = crop.width(); int height = crop.height(); - takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect); + takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect, + Insets.NONE, true); } - private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect) { + private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, + Insets screenInsets, boolean showFlash) { dismissScreenshot("new screenshot requested", true); mScreenBitmap = screenshot; @@ -482,7 +501,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mDismissAnimation.cancel(); } // Start the post-screenshot animation - startAnimation(finisher, mScreenBitmap.getWidth(), mScreenBitmap.getHeight(), screenRect); + startAnimation(finisher, screenRect, screenInsets, showFlash); } void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) { @@ -498,9 +517,15 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset Insets visibleInsets, int taskId, int userId, ComponentName topComponent, Consumer<Uri> finisher, Runnable onComplete) { // TODO: use task Id, userId, topComponent for smart handler - // TODO: use visibleInsets for animation + mOnCompleteRunnable = onComplete; - takeScreenshot(screenshot, finisher, screenshotScreenBounds); + if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) { + takeScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false); + } else { + takeScreenshot(screenshot, finisher, + new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE, + true); + } } /** @@ -621,8 +646,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } // Clear any references to the bitmap - mScreenshotPreview.setImageBitmap(null); - mScreenshotAnimatedView.setImageBitmap(null); + mScreenshotPreview.setImageDrawable(null); + mScreenshotAnimatedView.setImageDrawable(null); + mScreenshotAnimatedView.setVisibility(View.GONE); mActionsContainerBackground.setVisibility(View.GONE); mActionsContainer.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); @@ -643,6 +669,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset */ private void showUiOnActionsReady(SavedImageData imageData) { logSuccessOnActionsReady(imageData); + mImageData = imageData; AccessibilityManager accessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); @@ -688,8 +715,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset /** * Starts the animation after taking the screenshot */ - private void startAnimation( - final Consumer<Uri> finisher, int bitmapWidth, int bitmapHeight, Rect screenRect) { + private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, + boolean showFlash) { + // If power save is on, show a toast so there is some visual indication that a // screenshot has been taken. PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -701,9 +729,13 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset if (!mScreenshotLayout.isAttachedToWindow()) { mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); } - mScreenshotAnimatedView.setImageBitmap(mScreenBitmap); - mScreenshotPreview.setImageBitmap(mScreenBitmap); + mScreenshotAnimatedView.setImageDrawable( + createScreenDrawable(mScreenBitmap, screenInsets)); + setAnimatedViewSize(screenRect.width(), screenRect.height()); + // Show when the animation starts + mScreenshotAnimatedView.setVisibility(View.GONE); + mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets)); // make static preview invisible (from gone) so we can query its location on screen mScreenshotPreview.setVisibility(View.INVISIBLE); @@ -711,14 +743,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); mScreenshotAnimation = - createScreenshotDropInAnimation(bitmapWidth, bitmapHeight, screenRect); + createScreenshotDropInAnimation(screenRect, showFlash); saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { - @Override - void onActionsReady(SavedImageData imageData) { - showUiOnActionsReady(imageData); - } - }); + @Override + void onActionsReady(SavedImageData imageData) { + showUiOnActionsReady(imageData); + } + }); // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); @@ -730,20 +762,17 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); } - private AnimatorSet createScreenshotDropInAnimation( - int bitmapWidth, int bitmapHeight, Rect bounds) { + private AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) { Rect previewBounds = new Rect(); mScreenshotPreview.getBoundsOnScreen(previewBounds); - float cornerScale = mCornerSizeX / (mOrientationPortrait ? bitmapWidth : bitmapHeight); - float currentScale = bounds.height() / (float) bitmapHeight; + float cornerScale = + mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height()); + final float currentScale = 1f; mScreenshotAnimatedView.setScaleX(currentScale); mScreenshotAnimatedView.setScaleY(currentScale); - mScreenshotAnimatedView.setPivotX(0); - mScreenshotAnimatedView.setPivotY(0); - AnimatorSet dropInAnimation = new AnimatorSet(); ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1); flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS); @@ -785,13 +814,13 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset if (t < xPositionPct) { float xCenter = MathUtils.lerp(startPos.x, finalPos.x, mFastOutSlowIn.getInterpolation(t / xPositionPct)); - mScreenshotAnimatedView.setX(xCenter - bitmapWidth * currentScaleX / 2f); + mScreenshotAnimatedView.setX(xCenter - bounds.width() * currentScaleX / 2f); } else { - mScreenshotAnimatedView.setX(finalPos.x - bitmapWidth * currentScaleX / 2f); + mScreenshotAnimatedView.setX(finalPos.x - bounds.width() * currentScaleX / 2f); } float yCenter = MathUtils.lerp( startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t)); - mScreenshotAnimatedView.setY(yCenter - bitmapHeight * currentScaleY / 2f); + mScreenshotAnimatedView.setY(yCenter - bounds.height() * currentScaleY / 2f); }); toCorner.addListener(new AnimatorListenerAdapter() { @@ -805,8 +834,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotFlash.setAlpha(0f); mScreenshotFlash.setVisibility(View.VISIBLE); - dropInAnimation.play(flashOutAnimator).after(flashInAnimator); - dropInAnimation.play(flashOutAnimator).with(toCorner); + if (showFlash) { + dropInAnimation.play(flashOutAnimator).after(flashInAnimator); + dropInAnimation.play(flashOutAnimator).with(toCorner); + } else { + dropInAnimation.play(toCorner); + } dropInAnimation.addListener(new AnimatorListenerAdapter() { @Override @@ -972,6 +1005,71 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return animSet; } + private void setAnimatedViewSize(int width, int height) { + ViewGroup.LayoutParams layoutParams = mScreenshotAnimatedView.getLayoutParams(); + layoutParams.width = width; + layoutParams.height = height; + mScreenshotAnimatedView.setLayoutParams(layoutParams); + } + + /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ + private boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) { + int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right; + int insettedHeight = bitmap.getHeight() - bitmapInsets.top - bitmapInsets.bottom; + + if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0 + || bitmap.getHeight() == 0) { + Log.e(TAG, String.format( + "Provided bitmap and insets create degenerate region: %dx%d %s", + bitmap.getWidth(), bitmap.getHeight(), bitmapInsets)); + return false; + } + + float insettedBitmapAspect = ((float) insettedWidth) / insettedHeight; + float boundsAspect = ((float) screenBounds.width()) / screenBounds.height(); + + boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f; + if (!matchWithinTolerance) { + Log.d(TAG, String.format("aspectRatiosMatch: don't match bitmap: %f, bounds: %f", + insettedBitmapAspect, boundsAspect)); + } + + return matchWithinTolerance; + } + + /** + * Create a drawable using the size of the bitmap and insets as the fractional inset parameters. + */ + private Drawable createScreenDrawable(Bitmap bitmap, Insets insets) { + int insettedWidth = bitmap.getWidth() - insets.left - insets.right; + int insettedHeight = bitmap.getHeight() - insets.top - insets.bottom; + + BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap); + if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0 + || bitmap.getHeight() == 0) { + Log.e(TAG, String.format( + "Can't create insetted drawable, using 0 insets " + + "bitmap and insets create degenerate region: %dx%d %s", + bitmap.getWidth(), bitmap.getHeight(), insets)); + return bitmapDrawable; + } + + InsetDrawable insetDrawable = new InsetDrawable(bitmapDrawable, + -1f * insets.left / insettedWidth, + -1f * insets.top / insettedHeight, + -1f * insets.right / insettedWidth, + -1f * insets.bottom / insettedHeight); + + if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) { + // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need + // to fill in the background of the drawable. + return new LayerDrawable(new Drawable[] { + new ColorDrawable(Color.BLACK), insetDrawable}); + } else { + return insetDrawable; + } + } + /** * Receiver to proxy the share or edit intent, used to clean up the notification and send * appropriate signals to the system (ie. to dismiss the keyguard if necessary). diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java index fbcd6ba0ff47..46fe7f4630bd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java @@ -226,6 +226,66 @@ public class ScreenshotNotificationsController { } /** + * Shows a silent notification with the saved screenshot and actions that can be taken with it. + * + * @param actionData SavedImageData struct with image URI and actions + */ + public void showSilentScreenshotNotification( + GlobalScreenshot.SavedImageData actionData) { + mNotificationBuilder.addAction(actionData.shareAction); + mNotificationBuilder.addAction(actionData.editAction); + mNotificationBuilder.addAction(actionData.deleteAction); + for (Notification.Action smartAction : actionData.smartActions) { + mNotificationBuilder.addAction(smartAction); + } + + // Create the intent to show the screenshot in gallery + Intent launchIntent = new Intent(Intent.ACTION_VIEW); + launchIntent.setDataAndType(actionData.uri, "image/png"); + launchIntent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); + + final long now = System.currentTimeMillis(); + + // Update the text and the icon for the existing notification + mPublicNotificationBuilder + .setContentTitle(mResources.getString(R.string.screenshot_saved_title)) + .setContentText(mResources.getString(R.string.screenshot_saved_text)) + .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0)) + .setSmallIcon(R.drawable.stat_notify_image) + .setCategory(Notification.CATEGORY_PROGRESS) + .setWhen(now) + .setShowWhen(true) + .setAutoCancel(true) + .setColor(mContext.getColor( + com.android.internal.R.color.system_notification_accent_color)) + .setGroup("silent") + .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY); + mNotificationBuilder + .setContentTitle(mResources.getString(R.string.screenshot_saved_title)) + .setContentText(mResources.getString(R.string.screenshot_saved_text)) + .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0)) + .setSmallIcon(R.drawable.stat_notify_image) + .setCategory(Notification.CATEGORY_PROGRESS) + .setWhen(now) + .setShowWhen(true) + .setAutoCancel(true) + .setColor(mContext.getColor( + com.android.internal.R.color.system_notification_accent_color)) + .setPublicVersion(mPublicNotificationBuilder.build()) + .setStyle(mNotificationStyle) + .setFlag(Notification.FLAG_NO_CLEAR, false) + .setGroup("silent") + .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY); + + SystemUI.overrideNotificationAppName(mContext, mPublicNotificationBuilder, true); + SystemUI.overrideNotificationAppName(mContext, mNotificationBuilder, true); + + mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, + mNotificationBuilder.build()); + } + + /** * Sends a notification that the screenshot capture has failed. */ public void notifyScreenshotError(int msgResId) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java index 6aef6b407f37..6a3302473e63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -41,6 +41,8 @@ import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController; +import java.util.concurrent.Executor; + /** * A class that allows activities to be launched in a seamless way where the notification * transforms nicely into the starting window. @@ -59,6 +61,7 @@ public class ActivityLaunchAnimator { private final float mWindowCornerRadius; private final NotificationShadeWindowViewController mNotificationShadeWindowViewController; private final NotificationShadeDepthController mDepthController; + private final Executor mMainExecutor; private Callback mCallback; private final Runnable mTimeoutRunnable = () -> { setAnimationPending(false); @@ -73,12 +76,14 @@ public class ActivityLaunchAnimator { Callback callback, NotificationPanelViewController notificationPanel, NotificationShadeDepthController depthController, - NotificationListContainer container) { + NotificationListContainer container, + Executor mainExecutor) { mNotificationPanel = notificationPanel; mNotificationContainer = container; mDepthController = depthController; mNotificationShadeWindowViewController = notificationShadeWindowViewController; mCallback = callback; + mMainExecutor = mainExecutor; mWindowCornerRadius = ScreenDecorationsUtils .getWindowCornerRadius(mNotificationShadeWindowViewController.getView() .getResources()); @@ -155,7 +160,7 @@ public class ActivityLaunchAnimator { RemoteAnimationTarget[] remoteAnimationWallpaperTargets, IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback) throws RemoteException { - mSourceNotification.post(() -> { + mMainExecutor.execute(() -> { RemoteAnimationTarget primary = getPrimaryRemoteAnimationTarget( remoteAnimationTargets); if (primary == null) { @@ -191,8 +196,9 @@ public class ActivityLaunchAnimator { } } int targetWidth = primary.sourceContainerBounds.width(); - int notificationHeight = mSourceNotification.getActualHeight() - - mSourceNotification.getClipBottomAmount(); + // If the notification panel is collapsed, the clip may be larger than the height. + int notificationHeight = Math.max(mSourceNotification.getActualHeight() + - mSourceNotification.getClipBottomAmount(), 0); int notificationWidth = mSourceNotification.getWidth(); anim.setDuration(ANIMATION_DURATION); anim.setInterpolator(Interpolators.LINEAR); @@ -292,7 +298,7 @@ public class ActivityLaunchAnimator { @Override public void onAnimationCancelled() throws RemoteException { - mSourceNotification.post(() -> { + mMainExecutor.execute(() -> { setAnimationPending(false); mCallback.onLaunchAnimationCancelled(); }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index f4afb91396b5..bee2f7002823 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -44,6 +44,7 @@ import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.os.Handler; import android.os.RemoteException; +import android.os.UserHandle; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.text.TextUtils; @@ -540,7 +541,9 @@ public class NotificationConversationInfo extends LinearLayout implements .setView(onboardingView) .setIgnoresDnd(ignoreDnd) .setShowsAsBubble(showAsBubble) - .setIcon(((ImageView) findViewById(R.id.conversation_icon)).getDrawable()) + .setIcon(mIconFactory.getBaseIconDrawable(mShortcutInfo)) + .setBadge(mIconFactory.getAppBadge( + mPackageName, UserHandle.getUserId(mSbn.getUid()))) .setOnSettingsClick(mOnConversationSettingsClickListener) .build(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt index c88f0bdc2acb..fab367df8fde 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt @@ -16,41 +16,57 @@ package com.android.systemui.statusbar.notification.row +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet +import android.animation.ValueAnimator import android.app.Dialog import android.content.Context import android.graphics.Color import android.graphics.PixelFormat import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable import android.text.SpannableStringBuilder import android.text.style.BulletSpan import android.view.Gravity import android.view.View +import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.Window import android.view.WindowInsets.Type.statusBars import android.view.WindowManager +import android.view.animation.Interpolator +import android.view.animation.PathInterpolator import android.widget.ImageView import android.widget.TextView +import com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN import com.android.systemui.Prefs import com.android.systemui.R import com.android.systemui.statusbar.notification.row.NotificationConversationInfo.OnConversationSettingsClickListener import javax.inject.Inject + /** * Controller to handle presenting the priority conversations onboarding dialog */ class PriorityOnboardingDialogController @Inject constructor( - val view: View, - val context: Context, - val ignoresDnd: Boolean, - val showsAsBubble: Boolean, - val icon : Drawable, - val onConversationSettingsClickListener : OnConversationSettingsClickListener + val view: View, + val context: Context, + private val ignoresDnd: Boolean, + private val showsAsBubble: Boolean, + val icon : Drawable, + private val onConversationSettingsClickListener : OnConversationSettingsClickListener, + val badge : Drawable ) { private lateinit var dialog: Dialog + private val OVERSHOOT: Interpolator = PathInterpolator(0.4f, 0f, 0.2f, 1.4f) + private val IMPORTANCE_ANIM_DELAY = 150L + private val IMPORTANCE_ANIM_GROW_DURATION = 250L + private val IMPORTANCE_ANIM_SHRINK_DURATION = 200L + private val IMPORTANCE_ANIM_SHRINK_DELAY = 25L fun init() { initDialog() @@ -81,6 +97,7 @@ class PriorityOnboardingDialogController @Inject constructor( private lateinit var icon: Drawable private lateinit var onConversationSettingsClickListener : OnConversationSettingsClickListener + private lateinit var badge : Drawable fun setView(v: View): Builder { view = v @@ -106,6 +123,10 @@ class PriorityOnboardingDialogController @Inject constructor( icon = draw return this } + fun setBadge(badge : Drawable) : Builder { + this.badge = badge + return this + } fun setOnSettingsClick(onClick : OnConversationSettingsClickListener) : Builder { onConversationSettingsClickListener = onClick @@ -115,7 +136,7 @@ class PriorityOnboardingDialogController @Inject constructor( fun build(): PriorityOnboardingDialogController { val controller = PriorityOnboardingDialogController( view, context, ignoresDnd, showAsBubble, icon, - onConversationSettingsClickListener) + onConversationSettingsClickListener, badge) return controller } } @@ -143,6 +164,65 @@ class PriorityOnboardingDialogController @Inject constructor( } findViewById<ImageView>(R.id.conversation_icon)?.setImageDrawable(icon) + findViewById<ImageView>(R.id.icon)?.setImageDrawable(badge) + val mImportanceRingView = findViewById<ImageView>(R.id.conversation_icon_badge_ring) + val conversationIconBadgeBg = findViewById<ImageView>(R.id.conversation_icon_badge_bg) + + val ring: GradientDrawable = mImportanceRingView.drawable as GradientDrawable + ring.mutate() + val bg = conversationIconBadgeBg.drawable as GradientDrawable + bg.mutate() + val ringColor = context.getResources() + .getColor(com.android.internal.R.color.conversation_important_highlight) + val standardThickness = context.resources.getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_stroke_width) + val largeThickness = context.resources.getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_anim_max_stroke_width) + val standardSize = context.resources.getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_size) + val baseSize = standardSize - standardThickness * 2 + val largeSize = baseSize + largeThickness * 2 + val bgSize = context.resources.getDimensionPixelSize( + com.android.internal.R.dimen.conversation_icon_size_badged) + + val animatorUpdateListener: ValueAnimator.AnimatorUpdateListener + = ValueAnimator.AnimatorUpdateListener { animation -> + val strokeWidth = animation.animatedValue as Int + ring.setStroke(strokeWidth, ringColor) + val newSize = baseSize + strokeWidth * 2 + ring.setSize(newSize, newSize) + mImportanceRingView.invalidate() + } + + val growAnimation: ValueAnimator = ValueAnimator.ofInt(0, largeThickness) + growAnimation.interpolator = LINEAR_OUT_SLOW_IN + growAnimation.duration = IMPORTANCE_ANIM_GROW_DURATION + growAnimation.addUpdateListener(animatorUpdateListener) + + val shrinkAnimation: ValueAnimator + = ValueAnimator.ofInt(largeThickness, standardThickness) + shrinkAnimation.duration = IMPORTANCE_ANIM_SHRINK_DURATION + shrinkAnimation.startDelay = IMPORTANCE_ANIM_SHRINK_DELAY + shrinkAnimation.interpolator = OVERSHOOT + shrinkAnimation.addUpdateListener(animatorUpdateListener) + shrinkAnimation.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + // Shrink the badge bg so that it doesn't peek behind the animation + bg.setSize(baseSize, baseSize); + conversationIconBadgeBg.invalidate(); + } + + override fun onAnimationEnd(animation: Animator?) { + // Reset bg back to normal size + bg.setSize(bgSize, bgSize); + conversationIconBadgeBg.invalidate(); + + } + }) + + val anims = AnimatorSet() + anims.startDelay = IMPORTANCE_ANIM_DELAY + anims.playSequentially(growAnimation, shrinkAnimation) val gapWidth = dialog.context.getResources().getDimensionPixelSize( R.dimen.conversation_onboarding_bullet_gap_width) @@ -180,6 +260,7 @@ class PriorityOnboardingDialogController @Inject constructor( height = WRAP_CONTENT } } + anims.start() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index b11ed23c3aeb..684bf1958154 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -6539,7 +6539,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd return row.canViewBeDismissed(); } if (v instanceof PeopleHubView) { - return true; + return ((PeopleHubView) v).getCanSwipe(); } return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt index a1d898fb84b0..8f77a1d776e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt @@ -72,7 +72,7 @@ class PeopleHubView(context: Context, attrs: AttributeSet) : } } - var canSwipe: Boolean = true + var canSwipe: Boolean = false set(value) { if (field != value) { if (field) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 1bc42d1a169d..e2714af33247 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1275,7 +1275,7 @@ public class StatusBar extends SystemUI implements DemoMode, mActivityLaunchAnimator = new ActivityLaunchAnimator( mNotificationShadeWindowViewController, this, mNotificationPanelViewController, mNotificationShadeDepthControllerLazy.get(), - (NotificationListContainer) mStackScroller); + (NotificationListContainer) mStackScroller, mContext.getMainExecutor()); // TODO: inject this. mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 270f248e1a34..ce5bb0508c0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -367,8 +367,14 @@ public class UserSwitcherController implements Dumpable { int id; if (record.isGuest && record.info == null) { // No guest user. Create one. - UserInfo guest = mUserManager.createGuest( - mContext, mContext.getString(com.android.settingslib.R.string.guest_nickname)); + UserInfo guest; + try { + guest = mUserManager.createGuest(mContext, + mContext.getString(com.android.settingslib.R.string.guest_nickname)); + } catch (UserManager.UserOperationException e) { + Log.e(TAG, "Couldn't create guest user", e); + return; + } if (guest == null) { // Couldn't create guest, most likely because there already exists one, we just // haven't reloaded the user list yet. diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java index 2973e0aedd43..bc2a55c8d5c4 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java @@ -16,13 +16,19 @@ package com.android.systemui.usb; +import android.app.Activity; import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; import android.debug.IAdbManager; +import android.hardware.usb.UsbManager; import android.os.Bundle; import android.os.IBinder; import android.os.ServiceManager; +import android.os.SystemProperties; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -33,13 +39,25 @@ import android.widget.CheckBox; import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertController; import com.android.systemui.R; +import com.android.systemui.broadcast.BroadcastDispatcher; + +import javax.inject.Inject; public class UsbDebuggingActivity extends AlertActivity implements DialogInterface.OnClickListener { private static final String TAG = "UsbDebuggingActivity"; private CheckBox mAlwaysAllow; + private UsbDisconnectedReceiver mDisconnectedReceiver; + private final BroadcastDispatcher mBroadcastDispatcher; private String mKey; + private boolean mServiceNotified; + + @Inject + public UsbDebuggingActivity(BroadcastDispatcher broadcastDispatcher) { + super(); + mBroadcastDispatcher = broadcastDispatcher; + } @Override public void onCreate(Bundle icicle) { @@ -50,6 +68,10 @@ public class UsbDebuggingActivity extends AlertActivity super.onCreate(icicle); + if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) { + mDisconnectedReceiver = new UsbDisconnectedReceiver(this); + } + Intent intent = getIntent(); String fingerprints = intent.getStringExtra("fingerprints"); mKey = intent.getStringExtra("key"); @@ -83,10 +105,77 @@ public class UsbDebuggingActivity extends AlertActivity super.onWindowAttributesChanged(params); } + private class UsbDisconnectedReceiver extends BroadcastReceiver { + private final Activity mActivity; + UsbDisconnectedReceiver(Activity activity) { + mActivity = activity; + } + + @Override + public void onReceive(Context content, Intent intent) { + String action = intent.getAction(); + if (!UsbManager.ACTION_USB_STATE.equals(action)) { + return; + } + boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); + if (!connected) { + notifyService(false); + mActivity.finish(); + } + } + } + + @Override + public void onStart() { + super.onStart(); + if (mDisconnectedReceiver != null) { + IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE); + mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter); + } + } + + @Override + protected void onStop() { + if (mDisconnectedReceiver != null) { + mBroadcastDispatcher.unregisterReceiver(mDisconnectedReceiver); + } + // If the ADB service has not yet been notified due to this dialog being closed in some + // other way then notify the service to deny the connection to ensure system_server sends + // a response to adbd. + if (!mServiceNotified) { + notifyService(false); + } + super.onStop(); + } + @Override public void onClick(DialogInterface dialog, int which) { boolean allow = (which == AlertDialog.BUTTON_POSITIVE); boolean alwaysAllow = allow && mAlwaysAllow.isChecked(); + notifyService(allow, alwaysAllow); + finish(); + } + + /** + * Notifies the ADB service as to whether the current ADB request should be allowed; if the + * request is allowed it is only allowed for this session, and the user should be prompted again + * on subsequent requests from this key. + * + * @param allow whether the connection should be allowed for this session + */ + private void notifyService(boolean allow) { + notifyService(allow, false); + } + + /** + * Notifies the ADB service as to whether the current ADB request should be allowed, and if + * subsequent requests from this key should be allowed without user consent. + * + * @param allow whether the connection should be allowed + * @param alwaysAllow whether subsequent requests from this key should be allowed without user + * consent + */ + private void notifyService(boolean allow, boolean alwaysAllow) { try { IBinder b = ServiceManager.getService(ADB_SERVICE); IAdbManager service = IAdbManager.Stub.asInterface(b); @@ -95,9 +184,9 @@ public class UsbDebuggingActivity extends AlertActivity } else { service.denyDebugging(); } + mServiceNotified = true; } catch (Exception e) { Log.e(TAG, "Unable to notify Usb service", e); } - finish(); } } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java index 421424206370..4850a025b684 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java @@ -16,19 +16,47 @@ package com.android.systemui.usb; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.debug.IAdbManager; +import android.hardware.usb.UsbManager; import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.util.Log; import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertController; import com.android.systemui.R; +import com.android.systemui.broadcast.BroadcastDispatcher; + +import javax.inject.Inject; public class UsbDebuggingSecondaryUserActivity extends AlertActivity implements DialogInterface.OnClickListener { + private static final String TAG = "UsbDebuggingSecondaryUserActivity"; + private UsbDisconnectedReceiver mDisconnectedReceiver; + private final BroadcastDispatcher mBroadcastDispatcher; + + @Inject + public UsbDebuggingSecondaryUserActivity(BroadcastDispatcher broadcastDispatcher) { + mBroadcastDispatcher = broadcastDispatcher; + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); + if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) { + mDisconnectedReceiver = new UsbDisconnectedReceiver(this); + } + final AlertController.AlertParams ap = mAlertParams; ap.mTitle = getString(R.string.usb_debugging_secondary_user_title); ap.mMessage = getString(R.string.usb_debugging_secondary_user_message); @@ -38,6 +66,48 @@ public class UsbDebuggingSecondaryUserActivity extends AlertActivity setupAlert(); } + private class UsbDisconnectedReceiver extends BroadcastReceiver { + private final Activity mActivity; + UsbDisconnectedReceiver(Activity activity) { + mActivity = activity; + } + + @Override + public void onReceive(Context content, Intent intent) { + String action = intent.getAction(); + if (UsbManager.ACTION_USB_STATE.equals(action)) { + boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); + if (!connected) { + mActivity.finish(); + } + } + } + } + + @Override + public void onStart() { + super.onStart(); + if (mDisconnectedReceiver != null) { + IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE); + mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter); + } + } + + @Override + protected void onStop() { + if (mDisconnectedReceiver != null) { + mBroadcastDispatcher.unregisterReceiver(mDisconnectedReceiver); + } + try { + IBinder b = ServiceManager.getService(ADB_SERVICE); + IAdbManager service = IAdbManager.Stub.asInterface(b); + service.denyDebugging(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to notify Usb service", e); + } + super.onStop(); + } + @Override public void onClick(DialogInterface dialog, int which) { finish(); diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt index 318a6d727e5c..016f4de724b6 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt @@ -644,6 +644,12 @@ class PhysicsAnimator<T> private constructor (target: T) { it.onInternalAnimationEnd( property, canceled, value, velocity, anim is FlingAnimation) } + if (springAnimations[property] == anim) { + springAnimations.remove(property) + } + if (flingAnimations[property] == anim) { + flingAnimations.remove(property) + } } return anim } diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java index b2c35867e789..aa3f91a22208 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java @@ -35,6 +35,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.FakeBroadcastDispatcher; +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.FalsingManager; @@ -73,7 +74,7 @@ public abstract class SysuiTestCase { SystemUIFactory.createFromConfig(mContext); mDependency = new TestableDependency(mContext); mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Handler.class), - mock(Looper.class), mock(DumpManager.class)); + mock(Looper.class), mock(DumpManager.class), mock(BroadcastDispatcherLogger.class)); mRealInstrumentation = InstrumentationRegistry.getInstrumentation(); Instrumentation inst = spy(mRealInstrumentation); diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt index 3357c5863d46..86ddb209f321 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt @@ -27,6 +27,7 @@ import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.dump.DumpManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -76,6 +77,8 @@ class BroadcastDispatcherTest : SysuiTestCase() { private lateinit var intentFilterOther: IntentFilter @Mock private lateinit var mockHandler: Handler + @Mock + private lateinit var logger: BroadcastDispatcherLogger private lateinit var executor: Executor @@ -96,6 +99,7 @@ class BroadcastDispatcherTest : SysuiTestCase() { Handler(testableLooper.looper), testableLooper.looper, mock(DumpManager::class.java), + logger, mapOf(0 to mockUBRUser0, 1 to mockUBRUser1)) // These should be valid filters @@ -239,8 +243,9 @@ class BroadcastDispatcherTest : SysuiTestCase() { mainHandler: Handler, bgLooper: Looper, dumpManager: DumpManager, + logger: BroadcastDispatcherLogger, var mockUBRMap: Map<Int, UserBroadcastDispatcher> - ) : BroadcastDispatcher(context, mainHandler, bgLooper, dumpManager) { + ) : BroadcastDispatcher(context, mainHandler, bgLooper, dumpManager, logger) { override fun createUBRForUser(userId: Int): UserBroadcastDispatcher { return mockUBRMap.getOrDefault(userId, mock(UserBroadcastDispatcher::class.java)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt index 9a5773a7a3b4..6e982e26b8cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt @@ -24,6 +24,7 @@ import android.os.UserHandle import android.util.ArraySet import android.util.Log import com.android.systemui.SysuiTestableContext +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.dump.DumpManager import java.util.concurrent.Executor @@ -31,8 +32,9 @@ class FakeBroadcastDispatcher( context: SysuiTestableContext, handler: Handler, looper: Looper, - dumpManager: DumpManager -) : BroadcastDispatcher(context, handler, looper, dumpManager) { + dumpManager: DumpManager, + logger: BroadcastDispatcherLogger +) : BroadcastDispatcher(context, handler, looper, dumpManager, logger) { private val registeredReceivers = ArraySet<BroadcastReceiver>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt index 847e442f1a49..443357694f4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt @@ -26,6 +26,7 @@ import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import junit.framework.Assert.assertEquals @@ -40,6 +41,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.anyString import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.never @@ -62,6 +64,8 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { private val USER_HANDLE = UserHandle.of(USER_ID) fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() + fun <T> any(): T = Mockito.any() + fun <T> eq(v: T) = Mockito.eq(v) ?: v } @Mock @@ -72,6 +76,8 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { private lateinit var mockContext: Context @Mock private lateinit var mPendingResult: BroadcastReceiver.PendingResult + @Mock + private lateinit var logger: BroadcastDispatcherLogger @Captor private lateinit var argumentCaptor: ArgumentCaptor<IntentFilter> @@ -91,7 +97,7 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { fakeExecutor = FakeExecutor(FakeSystemClock()) userBroadcastDispatcher = UserBroadcastDispatcher( - mockContext, USER_ID, testableLooper.looper) + mockContext, USER_ID, testableLooper.looper, logger) userBroadcastDispatcher.pendingResult = mPendingResult } @@ -106,6 +112,13 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { } @Test + fun testNotRegisteredOnStart_logging() { + testableLooper.processAllMessages() + + verify(logger, never()).logContextReceiverRegistered(anyInt(), any()) + } + + @Test fun testSingleReceiverRegistered() { intentFilter = IntentFilter(ACTION_1) @@ -126,6 +139,18 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { } @Test + fun testSingleReceiverRegistered_logging() { + intentFilter = IntentFilter(ACTION_1) + + userBroadcastDispatcher.registerReceiver( + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) + testableLooper.processAllMessages() + + verify(logger).logReceiverRegistered(USER_HANDLE.identifier, broadcastReceiver) + verify(logger).logContextReceiverRegistered(eq(USER_HANDLE.identifier), any()) + } + + @Test fun testSingleReceiverUnregistered() { intentFilter = IntentFilter(ACTION_1) @@ -145,6 +170,21 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { } @Test + fun testSingleReceiverUnregistered_logger() { + intentFilter = IntentFilter(ACTION_1) + + userBroadcastDispatcher.registerReceiver( + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) + testableLooper.processAllMessages() + + userBroadcastDispatcher.unregisterReceiver(broadcastReceiver) + testableLooper.processAllMessages() + + verify(logger).logReceiverUnregistered(USER_HANDLE.identifier, broadcastReceiver) + verify(logger).logContextReceiverUnregistered(USER_HANDLE.identifier) + } + + @Test fun testFilterHasAllActionsAndCategories_twoReceivers() { intentFilter = IntentFilter(ACTION_1) intentFilterOther = IntentFilter(ACTION_2).apply { @@ -196,6 +236,30 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { } @Test + fun testDispatch_logger() { + intentFilter = IntentFilter(ACTION_1) + intentFilterOther = IntentFilter(ACTION_2) + + userBroadcastDispatcher.registerReceiver( + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) + userBroadcastDispatcher.registerReceiver( + ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE)) + + val intent = Intent(ACTION_2) + + userBroadcastDispatcher.onReceive(mockContext, intent) + testableLooper.processAllMessages() + fakeExecutor.runAllReady() + + val captor = ArgumentCaptor.forClass(Int::class.java) + verify(logger) + .logBroadcastReceived(captor.capture(), eq(USER_HANDLE.identifier), eq(intent)) + verify(logger).logBroadcastDispatched(captor.value, ACTION_2, broadcastReceiverOther) + verify(logger, never()) + .logBroadcastDispatched(eq(captor.value), any(), eq(broadcastReceiver)) + } + + @Test fun testDispatchToCorrectReceiver_differentFiltersSameReceiver() { intentFilter = IntentFilter(ACTION_1) intentFilterOther = IntentFilter(ACTION_2) 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 472b121f7b95..1e48b990b19d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -425,7 +425,8 @@ public class BubbleControllerTest extends SysuiTestCase { BubbleStackView stackView = mBubbleController.getStackView(); mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + true, mRow2.getEntry().getKey()); assertTrue(mSysUiStateBubblesExpanded); @@ -443,9 +444,11 @@ public class BubbleControllerTest extends SysuiTestCase { mRow.getEntry())); // collapse for previous bubble - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + false, mRow2.getEntry().getKey()); // expand for selected bubble - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + true, mRow.getEntry().getKey()); // Collapse mBubbleController.collapseStack(); 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 47cd9bca2861..0be24729dff9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -382,7 +382,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { BubbleStackView stackView = mBubbleController.getStackView(); mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + true, mRow.getEntry().getKey()); // Last added is the one that is expanded assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); @@ -397,9 +398,11 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mRow.getEntry())); // collapse for previous bubble - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + false, mRow2.getEntry().getKey()); // expand for selected bubble - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + true, mRow.getEntry().getKey()); // Collapse mBubbleController.collapseStack(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index 3a3140f2ff53..6fcf6e37572b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -51,6 +51,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit private const val KEY = "TEST_KEY" +private const val KEY_OLD = "TEST_KEY_OLD" private const val PACKAGE = "PKG" private const val SESSION_KEY = "SESSION_KEY" private const val SESSION_ARTIST = "SESSION_ARTIST" @@ -69,7 +70,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Mock private lateinit var lmmFactory: LocalMediaManagerFactory @Mock private lateinit var lmm: LocalMediaManager @Mock private lateinit var mr2: MediaRouter2Manager - @Mock private lateinit var featureFlag: MediaFeatureFlag private lateinit var fakeExecutor: FakeExecutor @Mock private lateinit var listener: MediaDeviceManager.Listener @Mock private lateinit var device: MediaDevice @@ -85,8 +85,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Before fun setUp() { fakeExecutor = FakeExecutor(FakeSystemClock()) - manager = MediaDeviceManager(context, lmmFactory, mr2, featureFlag, fakeExecutor, - mediaDataManager) + manager = MediaDeviceManager(context, lmmFactory, mr2, fakeExecutor, mediaDataManager) manager.addListener(listener) // Configure mocks. @@ -95,7 +94,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm) whenever(lmm.getCurrentConnectedDevice()).thenReturn(device) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route) - whenever(featureFlag.enabled).thenReturn(true) // Create a media sesssion and notification for testing. metadataBuilder = MediaMetadata.Builder().apply { @@ -132,23 +130,74 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun addNotification() { + fun loadMediaData() { manager.onMediaDataLoaded(KEY, null, mediaData) verify(lmmFactory).create(PACKAGE) } @Test - fun featureDisabled() { - whenever(featureFlag.enabled).thenReturn(false) + fun loadAndRemoveMediaData() { manager.onMediaDataLoaded(KEY, null, mediaData) - verify(lmmFactory, never()).create(PACKAGE) + manager.onMediaDataRemoved(KEY) + verify(lmm).unregisterCallback(any()) } @Test - fun addAndRemoveNotification() { - manager.onMediaDataLoaded(KEY, null, mediaData) - manager.onMediaDataRemoved(KEY) + fun loadMediaDataWithNullToken() { + manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null)) + fakeExecutor.runAllReady() + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun loadWithNewKey() { + // GIVEN that media data has been loaded with an old key + manager.onMediaDataLoaded(KEY_OLD, null, mediaData) + reset(listener) + // WHEN data is loaded with a new key + manager.onMediaDataLoaded(KEY, KEY_OLD, mediaData) + // THEN the listener for the old key should removed. verify(lmm).unregisterCallback(any()) + // AND a new device event emitted + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun newKeySameAsOldKey() { + // GIVEN that media data has been loaded + manager.onMediaDataLoaded(KEY, null, mediaData) + reset(listener) + // WHEN the new key is the same as the old key + manager.onMediaDataLoaded(KEY, KEY, mediaData) + // THEN no event should be emitted + verify(listener, never()).onMediaDeviceChanged(eq(KEY), any()) + } + + @Test + fun unknownOldKey() { + manager.onMediaDataLoaded(KEY, "unknown", mediaData) + verify(listener).onMediaDeviceChanged(eq(KEY), any()) + } + + @Test + fun updateToSessionTokenWithNullRoute() { + // GIVEN that media data has been loaded with a null token + manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null)) + // WHEN media data is loaded with a different token + // AND that token results in a null route + reset(listener) + whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) + manager.onMediaDataLoaded(KEY, null, mediaData) + // THEN the device should be disabled + fakeExecutor.runAllReady() + val data = captureDeviceData(KEY) + assertThat(data.enabled).isFalse() + assertThat(data.name).isNull() + assertThat(data.icon).isNull() } @Test @@ -164,6 +213,15 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test + fun removeListener() { + // WHEN a listener is removed + manager.removeListener(listener) + // THEN it doesn't receive device events + manager.onMediaDataLoaded(KEY, null, mediaData) + verify(listener, never()).onMediaDeviceChanged(eq(KEY), any()) + } + + @Test fun deviceListUpdate() { manager.onMediaDataLoaded(KEY, null, mediaData) val deviceCallback = captureCallback() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java index cdef49d6c94d..2fa6cf02d8b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java @@ -36,6 +36,8 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Assert; import org.junit.Before; @@ -51,6 +53,7 @@ import org.mockito.junit.MockitoRule; @TestableLooper.RunWithLooper public class ActivityLaunchAnimatorTest extends SysuiTestCase { + private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private ActivityLaunchAnimator mLaunchAnimator; @Mock private ActivityLaunchAnimator.Callback mCallback; @@ -80,8 +83,8 @@ public class ActivityLaunchAnimatorTest extends SysuiTestCase { mCallback, mNotificationPanelViewController, mNotificationShadeDepthController, - mNotificationContainer); - + mNotificationContainer, + mExecutor); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt index 661fad2d6d5c..bd596800e86d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt @@ -38,6 +38,7 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner::class) @SmallTest +@Ignore("Blocking presubmits - investigating in b/158697054") class PhysicsAnimatorTest : SysuiTestCase() { private lateinit var viewGroup: ViewGroup private lateinit var testView: View diff --git a/packages/Tethering/TEST_MAPPING b/packages/Tethering/TEST_MAPPING index 73254cdc79a9..5617b0c13c1c 100644 --- a/packages/Tethering/TEST_MAPPING +++ b/packages/Tethering/TEST_MAPPING @@ -1,7 +1,12 @@ { - "postsubmit": [ + "presubmit": [ { "name": "TetheringTests" } + ], + "postsubmit": [ + { + "name": "TetheringIntegrationTests" + } ] } diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java index d03deda37fdf..593d04a06b93 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java @@ -24,8 +24,10 @@ import android.app.Notification.Action; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.net.NetworkCapabilities; @@ -253,6 +255,14 @@ public class TetheringNotificationUpdater { } @VisibleForTesting + static String getSettingsPackageName(@NonNull final PackageManager pm) { + final Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS); + final ComponentName settingsComponent = settingsIntent.resolveActivity(pm); + return settingsComponent != null + ? settingsComponent.getPackageName() : "com.android.settings"; + } + + @VisibleForTesting void notifyTetheringDisabledByRestriction() { final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); final String title = res.getString(R.string.disable_tether_notification_title); @@ -262,8 +272,9 @@ public class TetheringNotificationUpdater { final PendingIntent pi = PendingIntent.getActivity( mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), 0 /* requestCode */, - new Intent(Settings.ACTION_TETHER_SETTINGS), - Intent.FLAG_ACTIVITY_NEW_TASK, + new Intent(Settings.ACTION_TETHER_SETTINGS) + .setPackage(getSettingsPackageName(mContext.getPackageManager())), + Intent.FLAG_ACTIVITY_NEW_TASK | PendingIntent.FLAG_IMMUTABLE, null /* options */); showNotification(R.drawable.stat_sys_tether_general, title, message, @@ -284,7 +295,7 @@ public class TetheringNotificationUpdater { mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), 0 /* requestCode */, intent, - 0 /* flags */); + PendingIntent.FLAG_IMMUTABLE); final Action action = new Action.Builder(NO_ICON_ID, disableButton, pi).build(); showNotification(R.drawable.stat_sys_tether_general, title, message, @@ -305,8 +316,9 @@ public class TetheringNotificationUpdater { final PendingIntent pi = PendingIntent.getActivity( mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), 0 /* requestCode */, - new Intent(Settings.ACTION_TETHER_SETTINGS), - Intent.FLAG_ACTIVITY_NEW_TASK, + new Intent(Settings.ACTION_TETHER_SETTINGS) + .setPackage(getSettingsPackageName(mContext.getPackageManager())), + Intent.FLAG_ACTIVITY_NEW_TASK | PendingIntent.FLAG_IMMUTABLE, null /* options */); showNotification(R.drawable.stat_sys_tether_general, title, message, diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt index 7d5471f7703d..4b6bbac051e0 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt @@ -19,6 +19,10 @@ package com.android.networkstack.tethering import android.app.Notification import android.app.NotificationManager import android.content.Context +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo import android.content.res.Resources import android.net.ConnectivityManager.TETHERING_WIFI import android.os.Handler @@ -51,6 +55,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.times @@ -351,4 +356,26 @@ class TetheringNotificationUpdaterTest { notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES) verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID)) } + + @Test + fun testGetSettingsPackageName() { + val defaultSettingsPackageName = "com.android.settings" + val testSettingsPackageName = "com.android.test.settings" + val pm = mock(PackageManager::class.java) + doReturn(null).`when`(pm).resolveActivity(any(), anyInt()) + assertEquals(defaultSettingsPackageName, + TetheringNotificationUpdater.getSettingsPackageName(pm)) + + val resolveInfo = ResolveInfo().apply { + activityInfo = ActivityInfo().apply { + name = "test" + applicationInfo = ApplicationInfo().apply { + packageName = testSettingsPackageName + } + } + } + doReturn(resolveInfo).`when`(pm).resolveActivity(any(), anyInt()) + assertEquals(testSettingsPackageName, + TetheringNotificationUpdater.getSettingsPackageName(pm)) + } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java index e7a43b75f9d5..48895ad42e99 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java @@ -93,7 +93,7 @@ final class AutofillInlineSuggestionsRequestSession { @Nullable private InlineFillUi mInlineFillUi; @GuardedBy("mLock") - private boolean mPreviousResponseIsNotEmpty; + private Boolean mPreviousResponseIsNotEmpty = null; @GuardedBy("mLock") private boolean mDestroyed = false; @@ -213,7 +213,7 @@ final class AutofillInlineSuggestionsRequestSession { // if IME is visible, and response is not null, send the response InlineSuggestionsResponse response = mInlineFillUi.getInlineSuggestionsResponse(); boolean isEmptyResponse = response.getInlineSuggestions().isEmpty(); - if (isEmptyResponse && !mPreviousResponseIsNotEmpty) { + if (isEmptyResponse && Boolean.FALSE.equals(mPreviousResponseIsNotEmpty)) { // No-op if both the previous response and current response are empty. return; } diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 851e4cc0bfd1..a7d0061cc043 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -245,6 +245,11 @@ final class RemoteAugmentedAutofillService if (inlineSuggestionsData == null || inlineSuggestionsData.isEmpty() || inlineSuggestionsCallback == null || request == null || remoteRenderService == null) { + // If it was an inline request and the response doesn't have any inline suggestions, + // we will send an empty response to IME. + if (inlineSuggestionsCallback != null && request != null) { + inlineSuggestionsCallback.apply(InlineFillUi.emptyUi(focusedId)); + } return; } mCallbacks.setLastResponse(sessionId); diff --git a/services/core/Android.bp b/services/core/Android.bp index 9e8a8727dc36..65e98ac8e684 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -100,7 +100,7 @@ java_library_static { "android.net.ipsec.ike.stubs.module_lib", "app-compat-annotations", "framework-tethering.stubs.module_lib", - "service-permission-stubs", + "service-permission.stubs.system_server", ], required: [ diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 81de29c4ee4d..b241bd16d3ee 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -988,4 +988,14 @@ public abstract class PackageManagerInternal { * Unblocks uninstall for all packages for the user. */ public abstract void clearBlockUninstallForUser(@UserIdInt int userId); + + /** + * Unsuspends all packages suspended by the given package for the user. + */ + public abstract void unsuspendForSuspendingPackage(String suspendingPackage, int userId); + + /** + * Returns {@code true} if the package is suspending any packages for the user. + */ + public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId); } diff --git a/services/core/java/com/android/server/AppStateTracker.java b/services/core/java/com/android/server/AppStateTracker.java index b765d81d42b7..74f79e0d40f5 100644 --- a/services/core/java/com/android/server/AppStateTracker.java +++ b/services/core/java/com/android/server/AppStateTracker.java @@ -1120,7 +1120,8 @@ public class AppStateTracker { return false; } final int userId = UserHandle.getUserId(uid); - if (mExemptedPackages.contains(userId, packageName)) { + if (mAppStandbyInternal.isAppIdleEnabled() && !mAppStandbyInternal.isInParole() + && mExemptedPackages.contains(userId, packageName)) { return false; } return mForceAllAppsStandby; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index ea7059894f38..f2c4e4428af2 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -2515,7 +2515,7 @@ class UserController implements Handler.Callback { showUserSwitchDialog(fromToUserPair); break; case CLEAR_USER_JOURNEY_SESSION_MSG: - clearSessionId(msg.arg1); + logAndClearSessionId(msg.arg1); break; } return false; @@ -2630,6 +2630,21 @@ class UserController implements Handler.Callback { } /** + * Log a final event of the {@link UserJourneySession} and clear it. + */ + private void logAndClearSessionId(@UserIdInt int userId) { + synchronized (mUserIdToUserJourneyMap) { + final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId); + if (userJourneySession != null) { + FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, + userJourneySession.mSessionId, userId, USER_LIFECYCLE_EVENT_UNKNOWN, + USER_LIFECYCLE_EVENT_STATE_NONE); + } + clearSessionId(userId); + } + } + + /** * Helper class to store user journey and session id. * * <p> User journey tracks a chain of user lifecycle events occurring during different user diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 067bdcb111fb..bfcbe465a271 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -231,7 +231,7 @@ public class LocationManagerService extends ILocationManager.Stub { private final AppForegroundHelper mAppForegroundHelper; private final LocationUsageLogger mLocationUsageLogger; - @Nullable private GnssManagerService mGnssManagerService = null; + @Nullable private volatile GnssManagerService mGnssManagerService = null; private final PassiveLocationProviderManager mPassiveManager; @@ -381,6 +381,10 @@ public class LocationManagerService extends ILocationManager.Stub { // prepare providers initializeProvidersLocked(); } + + // initialize gnss last because it has no awareness of boot phases and blindly assumes that + // all other location providers are loaded at initialization + initializeGnss(); } private void onAppOpChanged(String packageName) { @@ -602,16 +606,19 @@ public class LocationManagerService extends ILocationManager.Stub { } manager.setMockProvider(new MockProvider(properties)); } + } - // initialize gnss last because it has no awareness of boot phases and blindly assumes that - // all other location providers are loaded at initialization + private void initializeGnss() { + // Do not hold mLock when calling GnssManagerService#isGnssSupported() which calls into HAL. if (GnssManagerService.isGnssSupported()) { mGnssManagerService = new GnssManagerService(mContext, mAppOpsHelper, mSettingsHelper, mAppForegroundHelper, mLocationUsageLogger); mGnssManagerService.onSystemReady(); LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER); - mProviderManagers.add(gnssManager); + synchronized (mLock) { + mProviderManagers.add(gnssManager); + } gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider()); // bind to geofence proxy diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java index 2461b0ce93a5..30a636d4240e 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -247,6 +247,7 @@ class BluetoothRouteProvider { .setType(type) .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) + .setAddress(device.getAddress()) .build(); return newBtRoute; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a9139adff4f9..b64e99168445 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2048,12 +2048,15 @@ public class NotificationManagerService extends SystemService { mStripRemoteViewsSizeBytes = getContext().getResources().getInteger( com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes); - mMsgPkgsAllowedAsConvos = Set.of( - getContext().getResources().getStringArray( - com.android.internal.R.array.config_notificationMsgPkgsAllowedAsConvos)); + mMsgPkgsAllowedAsConvos = Set.of(getStringArrayResource( + com.android.internal.R.array.config_notificationMsgPkgsAllowedAsConvos)); mStatsManager = statsManager; } + protected String[] getStringArrayResource(int key) { + return getContext().getResources().getStringArray(key); + } + @Override public void onStart() { SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> { @@ -2741,10 +2744,7 @@ public class NotificationManagerService extends SystemService { } protected void maybeRegisterMessageSent(NotificationRecord r) { - Context appContext = r.getSbn().getPackageContext(getContext()); - Notification.Builder nb = - Notification.Builder.recoverBuilder(appContext, r.getNotification()); - if (nb.getStyle() instanceof Notification.MessagingStyle) { + if (r.isConversation()) { if (r.getShortcutInfo() != null) { if (mPreferencesHelper.setValidMessageSent( r.getSbn().getPackageName(), r.getUid())) { diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index ba60f179e661..4b6ee71803a7 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -19,8 +19,6 @@ package com.android.server.pm; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; -import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -111,25 +109,12 @@ public class AppsFilter { private final boolean mSystemAppsQueryable; private final FeatureConfig mFeatureConfig; - private final OverlayReferenceMapper mOverlayReferenceMapper; - private final StateProvider mStateProvider; + private final OverlayReferenceMapper mOverlayReferenceMapper; private PackageParser.SigningDetails mSystemSigningDetails; private Set<String> mProtectedBroadcasts = new ArraySet<>(); - /** - * This structure maps uid -> uid and indicates whether access from the first should be - * filtered to the second. It's essentially a cache of the - * {@link #shouldFilterApplicationInternal(int, SettingBase, PackageSetting, int)} call. - * NOTE: It can only be relied upon after the system is ready to avoid unnecessary update on - * initial scan and is null until {@link #onSystemReady()} is called. - */ - private volatile SparseArray<SparseBooleanArray> mShouldFilterCache; - - @VisibleForTesting(visibility = PRIVATE) - AppsFilter(StateProvider stateProvider, - FeatureConfig featureConfig, - String[] forceQueryableWhitelist, + AppsFilter(FeatureConfig featureConfig, String[] forceQueryableWhitelist, boolean systemAppsQueryable, @Nullable OverlayReferenceMapper.Provider overlayProvider) { mFeatureConfig = featureConfig; @@ -137,23 +122,8 @@ public class AppsFilter { mSystemAppsQueryable = systemAppsQueryable; mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/, overlayProvider); - mStateProvider = stateProvider; - } - - /** - * Provides system state to AppsFilter via {@link CurrentStateCallback} after properly guarding - * the data with the package lock. - */ - @VisibleForTesting(visibility = PRIVATE) - public interface StateProvider { - void runWithState(CurrentStateCallback callback); - - interface CurrentStateCallback { - void currentState(ArrayMap<String, PackageSetting> settings, int[] users); - } } - @VisibleForTesting(visibility = PRIVATE) public interface FeatureConfig { /** Called when the system is ready and components can be queried. */ @@ -170,7 +140,6 @@ public class AppsFilter { /** * Turns on logging for the given appId - * * @param enable true if logging should be enabled, false if disabled. */ void enableLogging(int appId, boolean enable); @@ -178,7 +147,6 @@ public class AppsFilter { /** * Initializes the package enablement state for the given package. This gives opportunity * to do any expensive operations ahead of the actual checks. - * * @param removed true if adding, false if removing */ void updatePackageState(PackageSetting setting, boolean removed); @@ -194,7 +162,6 @@ public class AppsFilter { @Nullable private SparseBooleanArray mLoggingEnabled = null; - private AppsFilter mAppsFilter; private FeatureConfigImpl( PackageManagerInternal pmInternal, PackageManagerService.Injector injector) { @@ -202,10 +169,6 @@ public class AppsFilter { mInjector = injector; } - public void setAppsFilter(AppsFilter filter) { - mAppsFilter = filter; - } - @Override public void onSystemReady() { mFeatureEnabled = DeviceConfig.getBoolean( @@ -272,28 +235,27 @@ public class AppsFilter { @Override public void onCompatChange(String packageName) { + updateEnabledState(mPmInternal.getPackage(packageName)); + } + + private void updateEnabledState(AndroidPackage pkg) { final long token = Binder.clearCallingIdentity(); try { - updateEnabledState(mPmInternal.getPackage(packageName)); - mAppsFilter.updateShouldFilterCacheForPackage(packageName); + // TODO(b/135203078): Do not use toAppInfo + final boolean enabled = + mInjector.getCompatibility().isChangeEnabled( + PackageManager.FILTER_APPLICATION_QUERY, + pkg.toAppInfoWithoutState()); + if (enabled) { + mDisabledPackages.remove(pkg.getPackageName()); + } else { + mDisabledPackages.add(pkg.getPackageName()); + } } finally { Binder.restoreCallingIdentity(token); } } - private void updateEnabledState(AndroidPackage pkg) { - // TODO(b/135203078): Do not use toAppInfo - final boolean enabled = - mInjector.getCompatibility().isChangeEnabled( - PackageManager.FILTER_APPLICATION_QUERY, - pkg.toAppInfoWithoutState()); - if (enabled) { - mDisabledPackages.remove(pkg.getPackageName()); - } else { - mDisabledPackages.add(pkg.getPackageName()); - } - } - @Override public void updatePackageState(PackageSetting setting, boolean removed) { final boolean enableLogging = @@ -313,7 +275,7 @@ public class AppsFilter { final boolean forceSystemAppsQueryable = injector.getContext().getResources() .getBoolean(R.bool.config_forceSystemPackagesQueryable); - final FeatureConfigImpl featureConfig = new FeatureConfigImpl(pms, injector); + final FeatureConfig featureConfig = new FeatureConfigImpl(pms, injector); final String[] forcedQueryablePackageNames; if (forceSystemAppsQueryable) { // all system apps already queryable, no need to read and parse individual exceptions @@ -326,16 +288,8 @@ public class AppsFilter { forcedQueryablePackageNames[i] = forcedQueryablePackageNames[i].intern(); } } - final StateProvider stateProvider = command -> { - synchronized (injector.getLock()) { - command.currentState(injector.getSettings().mPackages, - injector.getUserManagerInternal().getUserIds()); - } - }; - AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig, - forcedQueryablePackageNames, forceSystemAppsQueryable, null); - featureConfig.setAppsFilter(appsFilter); - return appsFilter; + return new AppsFilter(featureConfig, forcedQueryablePackageNames, + forceSystemAppsQueryable, null); } public FeatureConfig getFeatureConfig() { @@ -458,54 +412,28 @@ public class AppsFilter { * visibility of the caller from the target. * * @param recipientUid the uid gaining visibility of the {@code visibleUid}. - * @param visibleUid the uid becoming visible to the {@recipientUid} + * @param visibleUid the uid becoming visible to the {@recipientUid} */ public void grantImplicitAccess(int recipientUid, int visibleUid) { - if (recipientUid != visibleUid) { - if (mImplicitlyQueryable.add(recipientUid, visibleUid) && DEBUG_LOGGING) { - Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid); - } - if (mShouldFilterCache != null) { - // update the cache in a one-off manner since we've got all the information we need. - SparseBooleanArray visibleUids = mShouldFilterCache.get(recipientUid); - if (visibleUids == null) { - visibleUids = new SparseBooleanArray(); - mShouldFilterCache.put(recipientUid, visibleUids); - } - visibleUids.put(visibleUid, false); - } + if (recipientUid != visibleUid + && mImplicitlyQueryable.add(recipientUid, visibleUid) && DEBUG_LOGGING) { + Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid); } } public void onSystemReady() { - mShouldFilterCache = new SparseArray<>(); mFeatureConfig.onSystemReady(); mOverlayReferenceMapper.rebuildIfDeferred(); - updateEntireShouldFilterCache(); } /** * Adds a package that should be considered when filtering visibility between apps. * - * @param newPkgSetting the new setting being added + * @param newPkgSetting the new setting being added + * @param existingSettings all other settings currently on the device. */ - public void addPackage(PackageSetting newPkgSetting) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage"); - try { - mStateProvider.runWithState((settings, users) -> { - addPackageInternal(newPkgSetting, settings, users); - if (mShouldFilterCache != null) { - updateShouldFilterCacheForPackage( - null, newPkgSetting, settings, users, settings.size()); - } // else, rebuild entire cache when system is ready - }); - } finally { - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } - } - - private void addPackageInternal(PackageSetting newPkgSetting, - ArrayMap<String, PackageSetting> existingSettings, int[] allUsers) { + public void addPackage(PackageSetting newPkgSetting, + ArrayMap<String, PackageSetting> existingSettings) { if (Objects.equals("android", newPkgSetting.name)) { // let's set aside the framework signatures mSystemSigningDetails = newPkgSetting.signatures.mSigningDetails; @@ -518,151 +446,79 @@ public class AppsFilter { } } - final AndroidPackage newPkg = newPkgSetting.pkg; - if (newPkg == null) { - // nothing to add - return; - } - - if (!newPkg.getProtectedBroadcasts().isEmpty()) { - mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts()); - recomputeComponentVisibility(existingSettings, newPkg.getPackageName()); - } + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage"); + try { + final AndroidPackage newPkg = newPkgSetting.pkg; + if (newPkg == null) { + // nothing to add + return; + } - final boolean newIsForceQueryable = - mForceQueryable.contains(newPkgSetting.appId) - /* shared user that is already force queryable */ - || newPkg.isForceQueryable() - || newPkgSetting.forceQueryableOverride - || (newPkgSetting.isSystem() && (mSystemAppsQueryable - || ArrayUtils.contains(mForceQueryableByDevicePackageNames, - newPkg.getPackageName()))); - if (newIsForceQueryable - || (mSystemSigningDetails != null - && isSystemSigned(mSystemSigningDetails, newPkgSetting))) { - mForceQueryable.add(newPkgSetting.appId); - } + if (!newPkg.getProtectedBroadcasts().isEmpty()) { + mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts()); + recomputeComponentVisibility(existingSettings, newPkg.getPackageName()); + } - for (int i = existingSettings.size() - 1; i >= 0; i--) { - final PackageSetting existingSetting = existingSettings.valueAt(i); - if (existingSetting.appId == newPkgSetting.appId || existingSetting.pkg == null) { - continue; + final boolean newIsForceQueryable = + mForceQueryable.contains(newPkgSetting.appId) + /* shared user that is already force queryable */ + || newPkg.isForceQueryable() + || newPkgSetting.forceQueryableOverride + || (newPkgSetting.isSystem() && (mSystemAppsQueryable + || ArrayUtils.contains(mForceQueryableByDevicePackageNames, + newPkg.getPackageName()))); + if (newIsForceQueryable + || (mSystemSigningDetails != null + && isSystemSigned(mSystemSigningDetails, newPkgSetting))) { + mForceQueryable.add(newPkgSetting.appId); } - final AndroidPackage existingPkg = existingSetting.pkg; - // let's evaluate the ability of already added packages to see this new package - if (!newIsForceQueryable) { - if (canQueryViaComponents(existingPkg, newPkg, mProtectedBroadcasts)) { - mQueriesViaComponent.add(existingSetting.appId, newPkgSetting.appId); + + for (int i = existingSettings.size() - 1; i >= 0; i--) { + final PackageSetting existingSetting = existingSettings.valueAt(i); + if (existingSetting.appId == newPkgSetting.appId || existingSetting.pkg == null) { + continue; } - if (canQueryViaPackage(existingPkg, newPkg) - || canQueryAsInstaller(existingSetting, newPkg)) { - mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId); + final AndroidPackage existingPkg = existingSetting.pkg; + // let's evaluate the ability of already added packages to see this new package + if (!newIsForceQueryable) { + if (canQueryViaComponents(existingPkg, newPkg, mProtectedBroadcasts)) { + mQueriesViaComponent.add(existingSetting.appId, newPkgSetting.appId); + } + if (canQueryViaPackage(existingPkg, newPkg) + || canQueryAsInstaller(existingSetting, newPkg)) { + mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId); + } } - } - // now we'll evaluate our new package's ability to see existing packages - if (!mForceQueryable.contains(existingSetting.appId)) { - if (canQueryViaComponents(newPkg, existingPkg, mProtectedBroadcasts)) { - mQueriesViaComponent.add(newPkgSetting.appId, existingSetting.appId); + // now we'll evaluate our new package's ability to see existing packages + if (!mForceQueryable.contains(existingSetting.appId)) { + if (canQueryViaComponents(newPkg, existingPkg, mProtectedBroadcasts)) { + mQueriesViaComponent.add(newPkgSetting.appId, existingSetting.appId); + } + if (canQueryViaPackage(newPkg, existingPkg) + || canQueryAsInstaller(newPkgSetting, existingPkg)) { + mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId); + } } - if (canQueryViaPackage(newPkg, existingPkg) - || canQueryAsInstaller(newPkgSetting, existingPkg)) { + // if either package instruments the other, mark both as visible to one another + if (pkgInstruments(newPkgSetting, existingSetting) + || pkgInstruments(existingSetting, newPkgSetting)) { mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId); + mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId); } } - // if either package instruments the other, mark both as visible to one another - if (pkgInstruments(newPkgSetting, existingSetting) - || pkgInstruments(existingSetting, newPkgSetting)) { - mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId); - mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId); - } - } - - int existingSize = existingSettings.size(); - ArrayMap<String, AndroidPackage> existingPkgs = new ArrayMap<>(existingSize); - for (int index = 0; index < existingSize; index++) { - PackageSetting pkgSetting = existingSettings.valueAt(index); - if (pkgSetting.pkg != null) { - existingPkgs.put(pkgSetting.name, pkgSetting.pkg); - } - } - mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs); - mFeatureConfig.updatePackageState(newPkgSetting, false /*removed*/); - } - - private void removeAppIdFromVisibilityCache(int appId) { - if (mShouldFilterCache == null) { - return; - } - for (int i = mShouldFilterCache.size() - 1; i >= 0; i--) { - if (UserHandle.getAppId(mShouldFilterCache.keyAt(i)) == appId) { - mShouldFilterCache.removeAt(i); - continue; - } - SparseBooleanArray targetSparseArray = mShouldFilterCache.valueAt(i); - for (int j = targetSparseArray.size() - 1; j >= 0; j--) { - if (UserHandle.getAppId(targetSparseArray.keyAt(j)) == appId) { - targetSparseArray.removeAt(j); - } - } - } - } - - private void updateEntireShouldFilterCache() { - mStateProvider.runWithState((settings, users) -> { - mShouldFilterCache.clear(); - for (int i = settings.size() - 1; i >= 0; i--) { - updateShouldFilterCacheForPackage( - null /*skipPackage*/, settings.valueAt(i), settings, users, i); - } - }); - } - - public void onUsersChanged() { - if (mShouldFilterCache != null) { - updateEntireShouldFilterCache(); - } - } - private void updateShouldFilterCacheForPackage(String packageName) { - mStateProvider.runWithState((settings, users) -> { - updateShouldFilterCacheForPackage(null /* skipPackage */, settings.get(packageName), - settings, users, settings.size() /*maxIndex*/); - }); - - } - - private void updateShouldFilterCacheForPackage(@Nullable String skipPackageName, - PackageSetting subjectSetting, ArrayMap<String, PackageSetting> allSettings, - int[] allUsers, int maxIndex) { - for (int i = Math.min(maxIndex, allSettings.size() - 1); i >= 0; i--) { - PackageSetting otherSetting = allSettings.valueAt(i); - if (subjectSetting.appId == otherSetting.appId) { - continue; - } - //noinspection StringEquality - if (subjectSetting.name == skipPackageName || otherSetting.name == skipPackageName) { - continue; - } - for (int su = 0; su < allUsers.length; su++) { - int subjectUser = allUsers[su]; - for (int ou = su; ou < allUsers.length; ou++) { - int otherUser = allUsers[ou]; - int subjectUid = UserHandle.getUid(subjectUser, subjectSetting.appId); - if (!mShouldFilterCache.contains(subjectUid)) { - mShouldFilterCache.put(subjectUid, new SparseBooleanArray()); - } - int otherUid = UserHandle.getUid(otherUser, otherSetting.appId); - if (!mShouldFilterCache.contains(otherUid)) { - mShouldFilterCache.put(otherUid, new SparseBooleanArray()); - } - mShouldFilterCache.get(subjectUid).put(otherUid, - shouldFilterApplicationInternal( - subjectUid, subjectSetting, otherSetting, otherUser)); - mShouldFilterCache.get(otherUid).put(subjectUid, - shouldFilterApplicationInternal( - otherUid, otherSetting, subjectSetting, subjectUser)); + int existingSize = existingSettings.size(); + ArrayMap<String, AndroidPackage> existingPkgs = new ArrayMap<>(existingSize); + for (int index = 0; index < existingSize; index++) { + PackageSetting pkgSetting = existingSettings.valueAt(index); + if (pkgSetting.pkg != null) { + existingPkgs.put(pkgSetting.name, pkgSetting.pkg); } } + mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs); + mFeatureConfig.updatePackageState(newPkgSetting, false /*removed*/); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } @@ -713,7 +569,6 @@ public class AppsFilter { } } } - /** * Fetches all app Ids that a given setting is currently visible to, per provided user. This * only includes UIDs >= {@link Process#FIRST_APPLICATION_UID} as all other UIDs can already see @@ -722,11 +577,11 @@ public class AppsFilter { * If the setting is visible to all UIDs, null is returned. If an app is not visible to any * applications, the int array will be empty. * - * @param users the set of users that should be evaluated for this calculation + * @param users the set of users that should be evaluated for this calculation * @param existingSettings the set of all package settings that currently exist on device * @return a SparseArray mapping userIds to a sorted int array of appIds that may view the - * provided setting or null if the app is visible to all and no whitelist should be - * applied. + * provided setting or null if the app is visible to all and no whitelist should be + * applied. */ @Nullable public SparseArray<int[]> getVisibilityWhitelist(PackageSetting setting, int[] users, @@ -771,60 +626,52 @@ public class AppsFilter { /** * Removes a package for consideration when filtering visibility between apps. * - * @param setting the setting of the package being removed. + * @param setting the setting of the package being removed. + * @param allUsers array of all current users on device. */ - public void removePackage(PackageSetting setting) { - removeAppIdFromVisibilityCache(setting.appId); - mStateProvider.runWithState((settings, users) -> { - for (int u = 0; u < users.length; u++) { - final int userId = users[u]; - final int removingUid = UserHandle.getUid(userId, setting.appId); - mImplicitlyQueryable.remove(removingUid); - for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) { - mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), removingUid); - } - } - - mQueriesViaComponent.remove(setting.appId); - for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) { - mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.appId); - } - mQueriesViaPackage.remove(setting.appId); - for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) { - mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.appId); - } - - // re-add other shared user members to re-establish visibility between them and other - // packages - if (setting.sharedUser != null) { - for (int i = setting.sharedUser.packages.size() - 1; i >= 0; i--) { - if (setting.sharedUser.packages.valueAt(i) == setting) { - continue; - } - addPackageInternal( - setting.sharedUser.packages.valueAt(i), settings, users); - } - } + public void removePackage(PackageSetting setting, int[] allUsers, + ArrayMap<String, PackageSetting> existingSettings) { + mForceQueryable.remove(setting.appId); - if (!setting.pkg.getProtectedBroadcasts().isEmpty()) { - final String removingPackageName = setting.pkg.getPackageName(); - mProtectedBroadcasts.clear(); - mProtectedBroadcasts.addAll( - collectProtectedBroadcasts(settings, removingPackageName)); - recomputeComponentVisibility(settings, removingPackageName); + for (int u = 0; u < allUsers.length; u++) { + final int userId = allUsers[u]; + final int removingUid = UserHandle.getUid(userId, setting.appId); + mImplicitlyQueryable.remove(removingUid); + for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) { + mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), removingUid); } + } - mOverlayReferenceMapper.removePkg(setting.name); - mFeatureConfig.updatePackageState(setting, true /*removed*/); + mQueriesViaComponent.remove(setting.appId); + for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) { + mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.appId); + } + mQueriesViaPackage.remove(setting.appId); + for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) { + mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.appId); + } - if (mShouldFilterCache != null) { - updateShouldFilterCacheForPackage( - setting.name, setting, settings, users, settings.size()); + // re-add other shared user members to re-establish visibility between them and other + // packages + if (setting.sharedUser != null) { + for (int i = setting.sharedUser.packages.size() - 1; i >= 0; i--) { + if (setting.sharedUser.packages.valueAt(i) == setting) { + continue; + } + addPackage(setting.sharedUser.packages.valueAt(i), existingSettings); } - }); - mForceQueryable.remove(setting.appId); + } + if (!setting.pkg.getProtectedBroadcasts().isEmpty()) { + final String removingPackageName = setting.pkg.getPackageName(); + mProtectedBroadcasts.clear(); + mProtectedBroadcasts.addAll( + collectProtectedBroadcasts(existingSettings, removingPackageName)); + recomputeComponentVisibility(existingSettings, removingPackageName); + } + mOverlayReferenceMapper.removePkg(setting.name); + mFeatureConfig.updatePackageState(setting, true /*removed*/); } /** @@ -841,32 +688,11 @@ public class AppsFilter { PackageSetting targetPkgSetting, int userId) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplication"); try { - if (callingUid < Process.FIRST_APPLICATION_UID - || UserHandle.getAppId(callingUid) == targetPkgSetting.appId) { + + if (!shouldFilterApplicationInternal( + callingUid, callingSetting, targetPkgSetting, userId)) { return false; } - if (mShouldFilterCache != null) { // use cache - SparseBooleanArray shouldFilterTargets = mShouldFilterCache.get(callingUid); - final int targetUid = UserHandle.getUid(userId, targetPkgSetting.appId); - if (shouldFilterTargets == null) { - Slog.wtf(TAG, "Encountered calling uid with no cached rules: " + callingUid); - return true; - } - int indexOfTargetUid = shouldFilterTargets.indexOfKey(targetUid); - if (indexOfTargetUid < 0) { - Slog.w(TAG, "Encountered calling -> target with no cached rules: " - + callingUid + " -> " + targetUid); - return true; - } - if (!shouldFilterTargets.valueAt(indexOfTargetUid)) { - return false; - } - } else { - if (!shouldFilterApplicationInternal( - callingUid, callingSetting, targetPkgSetting, userId)) { - return false; - } - } if (DEBUG_LOGGING || mFeatureConfig.isLoggingEnabled(UserHandle.getAppId(callingUid))) { log(callingSetting, targetPkgSetting, "BLOCKED"); } @@ -877,7 +703,7 @@ public class AppsFilter { } private boolean shouldFilterApplicationInternal(int callingUid, SettingBase callingSetting, - PackageSetting targetPkgSetting, int targetUserId) { + PackageSetting targetPkgSetting, int userId) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplicationInternal"); try { final boolean featureEnabled = mFeatureConfig.isGloballyEnabled(); @@ -887,6 +713,12 @@ public class AppsFilter { } return false; } + if (callingUid < Process.FIRST_APPLICATION_UID) { + if (DEBUG_LOGGING) { + Slog.d(TAG, "filtering skipped; " + callingUid + " is system"); + } + return false; + } if (callingSetting == null) { Slog.wtf(TAG, "No setting found for non system uid " + callingUid); return true; @@ -895,14 +727,8 @@ public class AppsFilter { final ArraySet<PackageSetting> callingSharedPkgSettings; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingSetting instanceof"); if (callingSetting instanceof PackageSetting) { - if (((PackageSetting) callingSetting).sharedUser == null) { - callingPkgSetting = (PackageSetting) callingSetting; - callingSharedPkgSettings = null; - } else { - callingPkgSetting = null; - callingSharedPkgSettings = - ((PackageSetting) callingSetting).sharedUser.packages; - } + callingPkgSetting = (PackageSetting) callingSetting; + callingSharedPkgSettings = null; } else { callingPkgSetting = null; callingSharedPkgSettings = ((SharedUserSetting) callingSetting).packages; @@ -960,17 +786,13 @@ public class AppsFilter { } try { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "requestsQueryAllPackages"); - if (callingPkgSetting != null) { - if (requestsQueryAllPackages(callingPkgSetting)) { - return false; - } - } else { - for (int i = callingSharedPkgSettings.size() - 1; i >= 0; i--) { - if (requestsQueryAllPackages(callingSharedPkgSettings.valueAt(i))) { - return false; - } + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "hasPermission"); + if (callingSetting.getPermissionsState().hasPermission( + Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "has query-all permission"); } + return false; } } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -1011,7 +833,7 @@ public class AppsFilter { try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mImplicitlyQueryable"); - final int targetUid = UserHandle.getUid(targetUserId, targetAppId); + final int targetUid = UserHandle.getUid(userId, targetAppId); if (mImplicitlyQueryable.contains(callingUid, targetUid)) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "implicitly queryable for user"); @@ -1049,20 +871,13 @@ public class AppsFilter { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + return true; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } - - private static boolean requestsQueryAllPackages(PackageSetting pkgSetting) { - // we're not guaranteed to have permissions yet analyzed at package add, so we inspect the - // package directly - return pkgSetting.pkg.getRequestedPermissions().contains( - Manifest.permission.QUERY_ALL_PACKAGES); - } - /** Returns {@code true} if the source package instruments the target package. */ private static boolean pkgInstruments(PackageSetting source, PackageSetting target) { try { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index fd37f041450e..ae8b3a0e9acc 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -11067,7 +11067,7 @@ public class PackageManagerService extends IPackageManager.Stub } else { pkgSetting = result.pkgSetting; if (originalPkgSetting != null) { - mSettings.addRenamedPackageLPw(parsedPackage.getPackageName(), + mSettings.addRenamedPackageLPw(parsedPackage.getRealPackage(), originalPkgSetting.name); mTransferredPackages.add(originalPkgSetting.name); } @@ -11176,7 +11176,7 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mLock") private @Nullable PackageSetting getOriginalPackageLocked(@NonNull AndroidPackage pkg, @Nullable String renamedPkgName) { - if (!isPackageRenamed(pkg, renamedPkgName)) { + if (isPackageRenamed(pkg, renamedPkgName)) { return null; } for (int i = ArrayUtils.size(pkg.getOriginalPackages()) - 1; i >= 0; --i) { @@ -12362,7 +12362,7 @@ public class PackageManagerService extends IPackageManager.Stub ksms.addScannedPackageLPw(pkg); mComponentResolver.addAllComponents(pkg, chatty); - mAppsFilter.addPackage(pkgSetting); + mAppsFilter.addPackage(pkgSetting, mSettings.mPackages); // Don't allow ephemeral applications to define new permissions groups. if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { @@ -12536,6 +12536,8 @@ public class PackageManagerService extends IPackageManager.Stub void cleanPackageDataStructuresLILPw(AndroidPackage pkg, boolean chatty) { mComponentResolver.removeAllComponents(pkg, chatty); + mAppsFilter.removePackage(getPackageSetting(pkg.getPackageName()), + mInjector.getUserManagerInternal().getUserIds(), mSettings.mPackages); mPermissionManager.removeAllPermissions(pkg, chatty); final int instrumentationSize = ArrayUtils.size(pkg.getInstrumentations()); @@ -13516,6 +13518,17 @@ public class PackageManagerService extends IPackageManager.Stub removeSuspensionsBySuspendingPackage(allPackages, suspendingPackage::equals, userId); } + boolean isSuspendingAnyPackages(String suspendingPackage, int userId) { + synchronized (mLock) { + for (final PackageSetting ps : mSettings.mPackages.values()) { + if (ps.isSuspendedBy(suspendingPackage, userId)) { + return true; + } + } + } + return false; + } + /** * Removes any suspensions on given packages that were added by packages that pass the given * predicate. @@ -14251,7 +14264,7 @@ public class PackageManagerService extends IPackageManager.Stub // Okay! targetPackageSetting.setInstallerPackageName(installerPackageName); mSettings.addInstallerPackageNames(targetPackageSetting.installSource); - mAppsFilter.addPackage(targetPackageSetting); + mAppsFilter.addPackage(targetPackageSetting, mSettings.mPackages); scheduleWriteSettingsLocked(); } } @@ -18704,7 +18717,6 @@ public class PackageManagerService extends IPackageManager.Stub clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL, true); clearDefaultBrowserIfNeeded(packageName); mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName); - mAppsFilter.removePackage(getPackageSetting(packageName)); removedAppId = mSettings.removePackageLPw(packageName); if (outInfo != null) { outInfo.removedAppId = removedAppId; @@ -23462,7 +23474,6 @@ public class PackageManagerService extends IPackageManager.Stub scheduleWritePackageRestrictionsLocked(userId); scheduleWritePackageListLocked(userId); primeDomainVerificationsLPw(userId); - mAppsFilter.onUsersChanged(); } } @@ -23913,7 +23924,6 @@ public class PackageManagerService extends IPackageManager.Stub callingUid); } - @Override public boolean isPlatformSigned(String packageName) { PackageSetting packageSetting = mSettings.mPackages.get(packageName); @@ -25018,6 +25028,16 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.writePackageRestrictionsLPr(userId); } } + + @Override + public void unsuspendForSuspendingPackage(final String packageName, int affectedUser) { + PackageManagerService.this.unsuspendForSuspendingPackage(packageName, affectedUser); + } + + @Override + public boolean isSuspendingAnyPackages(String suspendingPackage, int userId) { + return PackageManagerService.this.isSuspendingAnyPackages(suspendingPackage, userId); + } } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 00a5fe766593..834303cc14c6 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -422,6 +422,11 @@ public abstract class PackageSettingBase extends SettingBase { return readUserState(userId).suspended; } + boolean isSuspendedBy(String suspendingPackage, int userId) { + final PackageUserState state = readUserState(userId); + return state.suspendParams != null && state.suspendParams.containsKey(suspendingPackage); + } + void addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo, PersistableBundle appExtras, PersistableBundle launcherExtras, int userId) { final PackageUserState existingUserState = modifyUserState(userId); diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index 2614076e9b6c..eb51cc3cd25c 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -69,6 +69,9 @@ "exclude-annotation": "androidx.test.filters.Suppress" } ] + }, + { + "name": "PackageManagerServiceHostTests" } ], "postsubmit": [ diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 0b95be15f157..8f0fd607f928 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -616,6 +616,7 @@ public class Notifier { } catch (RemoteException ex) { // Ignore } + FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_WAKE_REPORTED, reason); } /** diff --git a/services/core/java/com/android/server/slice/SliceClientPermissions.java b/services/core/java/com/android/server/slice/SliceClientPermissions.java index ab94a59c4d9c..e241205cfb99 100644 --- a/services/core/java/com/android/server/slice/SliceClientPermissions.java +++ b/services/core/java/com/android/server/slice/SliceClientPermissions.java @@ -160,6 +160,9 @@ public class SliceClientPermissions implements DirtyTracker, Persistable { // Get to the beginning of the provider. while (parser.getEventType() != XmlPullParser.START_TAG || !TAG_CLIENT.equals(parser.getName())) { + if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { + throw new XmlPullParserException("Can't find client tag in xml"); + } parser.next(); } int depth = parser.getDepth(); @@ -173,6 +176,9 @@ public class SliceClientPermissions implements DirtyTracker, Persistable { parser.next(); while (parser.getDepth() > depth) { + if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { + return provider; + } if (parser.getEventType() == XmlPullParser.START_TAG && TAG_AUTHORITY.equals(parser.getName())) { try { diff --git a/services/core/java/com/android/server/slice/SlicePermissionManager.java b/services/core/java/com/android/server/slice/SlicePermissionManager.java index 1d1c28f5f9b7..343d2e353abb 100644 --- a/services/core/java/com/android/server/slice/SlicePermissionManager.java +++ b/services/core/java/com/android/server/slice/SlicePermissionManager.java @@ -130,7 +130,7 @@ public class SlicePermissionManager implements DirtyTracker { } SliceClientPermissions client = getClient(pkgUser); client.clear(); - mHandler.obtainMessage(H.MSG_REMOVE, pkgUser); + mHandler.obtainMessage(H.MSG_REMOVE, pkgUser).sendToTarget(); } public String[] getAllPackagesGranted(String pkg) { diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java index 2f963b7e6b35..522e5e189232 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java @@ -20,8 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback; import android.hardware.soundtrigger.V2_2.ISoundTriggerHw; -import android.media.audio.common.AudioConfig; -import android.media.audio.common.AudioOffloadInfo; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerModule; import android.media.soundtrigger_middleware.ModelParameterRange; @@ -30,6 +28,7 @@ import android.media.soundtrigger_middleware.PhraseRecognitionExtra; import android.media.soundtrigger_middleware.PhraseSoundModel; import android.media.soundtrigger_middleware.RecognitionConfig; import android.media.soundtrigger_middleware.RecognitionEvent; +import android.media.soundtrigger_middleware.RecognitionStatus; import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundModelType; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; @@ -579,7 +578,7 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient { @NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent, int cookie) { synchronized (SoundTriggerModule.this) { - android.media.soundtrigger_middleware.RecognitionEvent aidlEvent = + RecognitionEvent aidlEvent = ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent); aidlEvent.captureSession = mSession.mSessionHandle; try { @@ -589,8 +588,7 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient { // In any case, client callbacks are considered best effort. Log.e(TAG, "Client callback execption.", e); } - if (aidlEvent.status - != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) { + if (aidlEvent.status != RecognitionStatus.FORCED) { setState(ModelState.LOADED); } } @@ -601,7 +599,7 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient { @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent, int cookie) { synchronized (SoundTriggerModule.this) { - android.media.soundtrigger_middleware.PhraseRecognitionEvent aidlEvent = + PhraseRecognitionEvent aidlEvent = ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent); aidlEvent.common.captureSession = mSession.mSessionHandle; try { @@ -611,8 +609,7 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient { // In any case, client callbacks are considered best effort. Log.e(TAG, "Client callback execption.", e); } - if (aidlEvent.common.status - != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) { + if (aidlEvent.common.status != RecognitionStatus.FORCED) { setState(ModelState.LOADED); } } @@ -623,15 +620,13 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient { /** * Creates a default-initialized recognition event. * - * Object fields are default constructed. - * Array fields are initialized to 0 length. + * Non-nullable object fields are default constructed. + * Non-nullable array fields are initialized to 0 length. * * @return The event. */ private static RecognitionEvent newEmptyRecognitionEvent() { RecognitionEvent result = new RecognitionEvent(); - result.audioConfig = new AudioConfig(); - result.audioConfig.offloadInfo = new AudioOffloadInfo(); result.data = new byte[0]; return result; } @@ -639,8 +634,8 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient { /** * Creates a default-initialized phrase recognition event. * - * Object fields are default constructed. - * Array fields are initialized to 0 length. + * Non-nullable object fields are default constructed. + * Non-nullable array fields are initialized to 0 length. * * @return The event. */ diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index c2bae1a8962b..bfdb9d291f28 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -3577,7 +3577,16 @@ public class StatsPullAtomService extends SystemService { private void processHistoricalOp(AppOpsManager.HistoricalOp op, List<AppOpEntry> opsList, int uid, int samplingRatio, String packageName, @Nullable String attributionTag) { - AppOpEntry entry = new AppOpEntry(packageName, attributionTag, op, uid); + int firstChar = 0; + if (attributionTag != null && attributionTag.startsWith(packageName)) { + firstChar = packageName.length(); + if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar) == '.') { + firstChar++; + } + } + AppOpEntry entry = new AppOpEntry(packageName, + attributionTag == null ? null : attributionTag.substring(firstChar), op, + uid); if (entry.mHash < samplingRatio) { opsList.add(entry); } diff --git a/services/core/java/com/android/server/textclassifier/FixedSizeQueue.java b/services/core/java/com/android/server/textclassifier/FixedSizeQueue.java new file mode 100644 index 000000000000..edb258db88c8 --- /dev/null +++ b/services/core/java/com/android/server/textclassifier/FixedSizeQueue.java @@ -0,0 +1,103 @@ +/* + * 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.textclassifier; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + +import java.util.ArrayDeque; +import java.util.Objects; +import java.util.Queue; + +/** + * A fixed-size queue which automatically evicts the oldest element from the queue when it is full. + * + * <p>This class does not accept null element. + * + * @param <E> the type of elements held in this queue + */ +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +public final class FixedSizeQueue<E> { + + private final Queue<E> mDelegate; + + @Nullable + private final OnEntryEvictedListener<E> mOnEntryEvictedListener; + + private final int mMaxSize; + + public FixedSizeQueue(int maxSize, @Nullable OnEntryEvictedListener<E> onEntryEvictedListener) { + Preconditions.checkArgument(maxSize > 0, "maxSize (%s) must > 0", maxSize); + mDelegate = new ArrayDeque<>(maxSize); + mMaxSize = maxSize; + mOnEntryEvictedListener = onEntryEvictedListener; + } + + /** Returns the number of items in the queue. */ + public int size() { + return mDelegate.size(); + } + + /** Adds an element to the queue, evicts the oldest element if it reaches its max capacity. */ + public boolean add(@NonNull E element) { + Objects.requireNonNull(element); + if (size() == mMaxSize) { + E removed = mDelegate.remove(); + if (mOnEntryEvictedListener != null) { + mOnEntryEvictedListener.onEntryEvicted(removed); + } + } + mDelegate.add(element); + return true; + } + + /** + * Returns and removes the head of the queue, or returns null if this queue is empty. + */ + @Nullable + public E poll() { + return mDelegate.poll(); + } + + /** + * Removes an element from the queue, returns a boolean to indicate if an element is removed. + */ + public boolean remove(@NonNull E element) { + Objects.requireNonNull(element); + return mDelegate.remove(element); + } + + /** Returns whether the queue is empty. */ + public boolean isEmpty() { + return mDelegate.isEmpty(); + } + + /** + * A listener to get notified when an element is evicted. + * + * @param <E> the type of element + */ + public interface OnEntryEvictedListener<E> { + /** + * Notifies that an element is evicted because the queue is reaching its max capacity. + */ + void onEntryEvicted(@NonNull E element); + } +} diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 1c96a2e8c5c2..1707d9542813 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -41,6 +41,7 @@ import android.service.textclassifier.TextClassifierService; import android.service.textclassifier.TextClassifierService.ConnectionState; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.LruCache; import android.util.Slog; import android.util.SparseArray; import android.view.textclassifier.ConversationAction; @@ -68,12 +69,10 @@ import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Queue; /** * A manager for TextClassifier services. @@ -308,13 +307,15 @@ public final class TextClassificationManagerService extends ITextClassifierServi Objects.requireNonNull(classificationContext); Objects.requireNonNull(classificationContext.getSystemTextClassifierMetadata()); + synchronized (mLock) { + mSessionCache.put(sessionId, classificationContext); + } handleRequest( classificationContext.getSystemTextClassifierMetadata(), /* verifyCallingPackage= */ true, /* attemptToBind= */ false, service -> { service.onCreateTextClassificationSession(classificationContext, sessionId); - mSessionCache.put(sessionId, classificationContext); }, "onCreateTextClassificationSession", NO_OP_CALLBACK); @@ -588,12 +589,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi * are cleaned up automatically when the client process is dead. */ static final class SessionCache { + private static final int MAX_CACHE_SIZE = 100; + @NonNull private final Object mLock; @NonNull @GuardedBy("mLock") - private final Map<TextClassificationSessionId, StrippedTextClassificationContext> mCache = - new ArrayMap<>(); + private final LruCache<TextClassificationSessionId, StrippedTextClassificationContext> + mCache = new LruCache<>(MAX_CACHE_SIZE); @NonNull @GuardedBy("mLock") private final Map<TextClassificationSessionId, DeathRecipient> mDeathRecipients = @@ -775,6 +778,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi } private final class ServiceState { + private static final int MAX_PENDING_REQUESTS = 20; + @UserIdInt final int mUserId; @NonNull @@ -786,7 +791,15 @@ public final class TextClassificationManagerService extends ITextClassifierServi final int mBindServiceFlags; @NonNull @GuardedBy("mLock") - final Queue<PendingRequest> mPendingRequests = new ArrayDeque<>(); + final FixedSizeQueue<PendingRequest> mPendingRequests = + new FixedSizeQueue<>(MAX_PENDING_REQUESTS, + request -> { + Slog.w(LOG_TAG, + String.format("Pending request[%s] is dropped", request.mName)); + if (request.mOnServiceFailure != null) { + request.mOnServiceFailure.run(); + } + }); @Nullable @GuardedBy("mLock") ITextClassifierService mService; @@ -910,7 +923,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi pw.printPair("bindServiceFlags", mBindServiceFlags); pw.printPair("boundServiceUid", mBoundServiceUid); pw.printPair("binding", mBinding); - pw.printPair("numberRequests", mPendingRequests.size()); + pw.printPair("numOfPendingRequests", mPendingRequests.size()); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 7c935d0ea546..6dd1ea934497 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1685,6 +1685,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final NeededUriGrants resultGrants = collectGrants(resultData, r.resultTo); synchronized (mGlobalLock) { + // Sanity check in case activity was removed before entering global lock. + if (!r.isInHistory()) { + return true; + } + // Keep track of the root activity of the task before we finish it final Task tr = r.getTask(); final ActivityRecord rootR = tr.getRootActivity(); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 77bc37f0c2d7..bf9a78489167 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -178,6 +178,7 @@ class InsetsStateController { if (imeSource != null && imeSource.isVisible()) { imeSource = new InsetsSource(imeSource); imeSource.setVisible(false); + imeSource.setFrame(0, 0, 0, 0); state = new InsetsState(state); state.addSource(imeSource); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3ee7ee7a4276..ce2ae2a42069 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1436,15 +1436,6 @@ class Task extends WindowContainer<WindowContainer> { mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged(); } - final boolean isRootTask = isRootTask(); - if (isRootTask) { - final DisplayContent display = getDisplayContent(); - if (display.isSingleTaskInstance()) { - mAtmService.notifySingleTaskDisplayEmpty(display.mDisplayId); - } - display.mDisplayContent.setLayoutNeeded(); - } - if (hasChild()) { updateEffectiveIntent(); @@ -1465,7 +1456,7 @@ class Task extends WindowContainer<WindowContainer> { } else if (!mReuseTask && !mCreatedByOrganizer) { // Remove entire task if it doesn't have any activity left and it isn't marked for reuse // or created by task organizer. - if (!isRootTask) { + if (!isRootTask()) { getStack().removeChild(this, reason); } EventLogTags.writeWmTaskRemoved(mTaskId, @@ -2817,6 +2808,10 @@ class Task extends WindowContainer<WindowContainer> { if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId); EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask"); + if (mDisplayContent != null && mDisplayContent.isSingleTaskInstance()) { + mAtmService.notifySingleTaskDisplayEmpty(mDisplayContent.mDisplayId); + } + // If applicable let the TaskOrganizer know the Task is vanishing. setTaskOrganizer(null); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index df55b3bbd1a4..10ad07cff847 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -2755,7 +2755,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Slog.i(LOG_TAG, "Giving the PO additional power..."); markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(poAdminComponent, poUserId); Slog.i(LOG_TAG, "Migrating DO policies to PO..."); - moveDoPoliciesToProfileParentAdmin(doAdmin, poAdmin.getParentActiveAdmin()); + moveDoPoliciesToProfileParentAdminLocked(doAdmin, poAdmin.getParentActiveAdmin()); + migratePersonalAppSuspensionLocked(doUserId, poUserId, poAdmin); saveSettingsLocked(poUserId); Slog.i(LOG_TAG, "Clearing the DO..."); final ComponentName doAdminReceiver = doAdmin.info.getComponent(); @@ -2775,6 +2776,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } + @GuardedBy("getLockObject()") + private void migratePersonalAppSuspensionLocked( + int doUserId, int poUserId, ActiveAdmin poAdmin) { + final PackageManagerInternal pmi = mInjector.getPackageManagerInternal(); + if (!pmi.isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, doUserId)) { + Slog.i(LOG_TAG, "DO is not suspending any apps."); + return; + } + + if (getTargetSdk(poAdmin.info.getPackageName(), poUserId) >= Build.VERSION_CODES.R) { + Slog.i(LOG_TAG, "PO is targeting R+, keeping personal apps suspended."); + getUserData(doUserId).mAppsSuspended = true; + poAdmin.mSuspendPersonalApps = true; + } else { + Slog.i(LOG_TAG, "PO isn't targeting R+, unsuspending personal apps."); + pmi.unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, doUserId); + } + } + private void uninstallOrDisablePackage(String packageName, int userHandle) { final ApplicationInfo appInfo; try { @@ -2816,7 +2836,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pi.uninstall(packageName, 0 /* flags */, new IntentSender((IIntentSender) mLocalSender)); } - private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) { + @GuardedBy("getLockObject()") + private void moveDoPoliciesToProfileParentAdminLocked( + ActiveAdmin doAdmin, ActiveAdmin parentAdmin) { // The following policies can be already controlled via parent instance, skip if so. if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) { parentAdmin.mPasswordPolicy = doAdmin.mPasswordPolicy; @@ -16147,25 +16169,34 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Slog.i(LOG_TAG, String.format("%s personal apps for user %d", suspended ? "Suspending" : "Unsuspending", userId)); + + if (suspended) { + suspendPersonalAppsInPackageManager(userId); + } else { + mInjector.getPackageManagerInternal().unsuspendForSuspendingPackage( + PLATFORM_PACKAGE_NAME, userId); + } + + synchronized (getLockObject()) { + getUserData(userId).mAppsSuspended = suspended; + saveSettingsLocked(userId); + } + } + + private void suspendPersonalAppsInPackageManager(int userId) { mInjector.binderWithCleanCallingIdentity(() -> { try { final String[] appsToSuspend = mInjector.getPersonalAppsForSuspension(userId); - final String[] failedPackages = mIPackageManager.setPackagesSuspendedAsUser( - appsToSuspend, suspended, null, null, null, PLATFORM_PACKAGE_NAME, userId); - if (!ArrayUtils.isEmpty(failedPackages)) { - Slog.wtf(LOG_TAG, String.format("Failed to %s packages: %s", - suspended ? "suspend" : "unsuspend", String.join(",", failedPackages))); + final String[] failedApps = mIPackageManager.setPackagesSuspendedAsUser( + appsToSuspend, true, null, null, null, PLATFORM_PACKAGE_NAME, userId); + if (!ArrayUtils.isEmpty(failedApps)) { + Slog.wtf(LOG_TAG, "Failed to suspend apps: " + String.join(",", failedApps)); } } catch (RemoteException re) { // Shouldn't happen. Slog.e(LOG_TAG, "Failed talking to the package manager", re); } }); - - synchronized (getLockObject()) { - getUserData(userId).mAppsSuspended = suspended; - saveSettingsLocked(userId); - } } @GuardedBy("getLockObject()") diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java index 2e60f2afcdea..236ac8407faa 100644 --- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java +++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java @@ -16,6 +16,8 @@ package com.android.server.people.prediction; +import static java.util.Collections.reverseOrder; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -39,6 +41,7 @@ import com.android.server.people.data.PackageData; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.function.Consumer; @@ -85,7 +88,9 @@ class ShareTargetPredictor extends AppTargetPredictor { List<ShareTarget> shareTargets = getDirectShareTargets(); SharesheetModelScorer.computeScore(shareTargets, getShareEventType(mIntentFilter), System.currentTimeMillis()); - Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore())); + Collections.sort(shareTargets, + Comparator.comparing(ShareTarget::getScore, reverseOrder()) + .thenComparing(t -> t.getAppTarget().getRank())); List<AppTarget> res = new ArrayList<>(); for (int i = 0; i < Math.min(getPredictionContext().getPredictedTargetCount(), shareTargets.size()); i++) { @@ -135,6 +140,7 @@ class ShareTargetPredictor extends AppTargetPredictor { new AppTargetId(shortcutInfo.getId()), shortcutInfo) .setClassName(shareShortcut.getTargetComponent().getClassName()) + .setRank(shortcutInfo.getRank()) .build(); String packageName = shortcutInfo.getPackage(); int userId = shortcutInfo.getUserId(); diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp new file mode 100644 index 000000000000..dad001b52b15 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/Android.bp @@ -0,0 +1,33 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +java_test_host { + name: "PackageManagerServiceHostTests", + srcs: ["src/**/*.kt"], + libs: [ + "tradefed", + "junit", + "truth-prebuilt", + ], + static_libs: [ + "frameworks-base-hostutils", + ], + test_suites: ["general-tests"], + java_resources: [ + ":PackageManagerDummyAppVersion1", + ":PackageManagerDummyAppVersion2", + ":PackageManagerDummyAppVersion3", + ":PackageManagerDummyAppOriginalOverride", + ] +} diff --git a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml new file mode 100644 index 000000000000..dc8c8113fac2 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<configuration description="Test module config for PackageManagerServiceHostTests"> + <option name="test-tag" value="PackageManagerServiceHostTests" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + + <test class="com.android.tradefed.testtype.HostTest"> + <option name="jar" value="PackageManagerServiceHostTests.jar" /> + </test> +</configuration> diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt new file mode 100644 index 000000000000..4927c45550b5 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt @@ -0,0 +1,46 @@ +/* + * 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.test + +import com.android.internal.util.test.SystemPreparer +import com.android.tradefed.device.ITestDevice +import java.io.File + +internal fun SystemPreparer.pushApk(file: String, partition: Partition) = + pushResourceFile(file, HostUtils.makePathForApk(file, partition)) + +internal fun SystemPreparer.deleteApk(file: String, partition: Partition) = + deleteFile(partition.baseFolder.resolve(file.removeSuffix(".apk")).toString()) + +internal object HostUtils { + + fun getDataDir(device: ITestDevice, pkgName: String) = + device.executeShellCommand("dumpsys package $pkgName") + .lineSequence() + .map(String::trim) + .single { it.startsWith("dataDir=") } + .removePrefix("dataDir=") + + fun makePathForApk(fileName: String, partition: Partition) = + makePathForApk(File(fileName), partition) + + fun makePathForApk(file: File, partition: Partition) = + partition.baseFolder + .resolve(file.nameWithoutExtension) + .resolve(file.name) + .toString() +} diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt new file mode 100644 index 000000000000..90494c591363 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt @@ -0,0 +1,98 @@ +/* + * 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.test + +import com.android.internal.util.test.SystemPreparer +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test +import com.google.common.truth.Truth.assertThat +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith + +@RunWith(DeviceJUnit4ClassRunner::class) +class OriginalPackageMigrationTest : BaseHostJUnit4Test() { + + companion object { + private const val TEST_PKG_NAME = "com.android.server.pm.test.dummy_app" + private const val VERSION_ONE = "PackageManagerDummyAppVersion1.apk" + private const val VERSION_TWO = "PackageManagerDummyAppVersion2.apk" + private const val VERSION_THREE = "PackageManagerDummyAppVersion3.apk" + private const val NEW_PKG = "PackageManagerDummyAppOriginalOverride.apk" + + @get:ClassRule + val deviceRebootRule = SystemPreparer.TestRuleDelegate(true) + } + + private val tempFolder = TemporaryFolder() + private val preparer: SystemPreparer = SystemPreparer(tempFolder, + SystemPreparer.RebootStrategy.START_STOP, deviceRebootRule) { this.device } + + @get:Rule + val rules = RuleChain.outerRule(tempFolder).around(preparer)!! + + @Test + fun lowerVersion() { + runForApk(VERSION_ONE) + } + + @Test + fun sameVersion() { + runForApk(VERSION_TWO) + } + + @Test + fun higherVersion() { + runForApk(VERSION_THREE) + } + + // A bug was found where renamed the package during parsing was leading to an invalid version + // code check at scan time. A lower version package was being dropped after reboot. To test + // this, the override APK is defined as versionCode 2 and the original package is given + // versionCode 1, 2, and 3 from the other methods. + private fun runForApk(apk: String) { + preparer.pushApk(apk, Partition.SYSTEM) + .reboot() + + device.getAppPackageInfo(TEST_PKG_NAME).run { + assertThat(codePath).contains(apk.removeSuffix(".apk")) + } + + // Ensure data is preserved by writing to the original dataDir + val file = tempFolder.newFile().apply { writeText("Test") } + device.pushFile(file, "${HostUtils.getDataDir(device, TEST_PKG_NAME)}/files/test.txt") + + preparer.deleteApk(apk, Partition.SYSTEM) + .pushApk(NEW_PKG, Partition.SYSTEM) + .reboot() + + device.getAppPackageInfo(TEST_PKG_NAME) + .run { + assertThat(this.toString()).isNotEmpty() + assertThat(codePath) + .contains(NEW_PKG.removeSuffix(".apk")) + } + + // And then reading the data contents back + assertThat(device.pullFileContents( + "${HostUtils.getDataDir(device, TEST_PKG_NAME)}/files/test.txt")) + .isEqualTo("Test") + } +} diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt new file mode 100644 index 000000000000..35192a73ceda --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.test + +import java.nio.file.Path +import java.nio.file.Paths + +// Unfortunately no easy way to access PMS SystemPartitions, so mock them here +internal enum class Partition(val baseFolder: Path) { + SYSTEM("/system/app"), + VENDOR("/vendor/app"), + PRODUCT("/product/app"), + SYSTEM_EXT("/system_ext/app") + ; + + constructor(baseFolder: String) : this(Paths.get(baseFolder)) +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp new file mode 100644 index 000000000000..9568faa7dfd0 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp @@ -0,0 +1,33 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test_helper_app { + name: "PackageManagerDummyAppVersion1", + manifest: "AndroidManifestVersion1.xml" +} + +android_test_helper_app { + name: "PackageManagerDummyAppVersion2", + manifest: "AndroidManifestVersion2.xml" +} + +android_test_helper_app { + name: "PackageManagerDummyAppVersion3", + manifest: "AndroidManifestVersion3.xml" +} + +android_test_helper_app { + name: "PackageManagerDummyAppOriginalOverride", + manifest: "AndroidManifestOriginalOverride.xml" +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestOriginalOverride.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestOriginalOverride.xml new file mode 100644 index 000000000000..f16e1bc8a927 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestOriginalOverride.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.dummy_app.override" + android:versionCode="2" + > + + <original-package android:name="com.android.server.pm.test.dummy_app"/> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml new file mode 100644 index 000000000000..d772050d7fd0 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.dummy_app" + android:versionCode="1" + /> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml new file mode 100644 index 000000000000..53f836b222e6 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.dummy_app" + android:versionCode="2" + /> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml new file mode 100644 index 000000000000..90ca9d0ac02c --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.dummy_app" + android:versionCode="3" + /> diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index ff34ebd8aa9d..b4e0f10f14b0 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -21,7 +21,7 @@ android_test { "services.core", "services.net", "service-jobscheduler", - "service-permission", + "service-permission.impl", "service-blobstore", "androidx.test.runner", "androidx.test.ext.truth", diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 40a17061c7f1..979f4e179e95 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -44,7 +44,7 @@ android_test { "hamcrest-library", "servicestests-utils", "service-jobscheduler", - "service-permission", + "service-permission.impl", // TODO: remove once Android migrates to JUnit 4.12, // which provides assertThrows "testng", diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java index a0b9d9d2a875..3167820f0a48 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -18,17 +18,24 @@ package com.android.server.devicepolicy; import static android.os.UserHandle.USER_SYSTEM; import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static org.junit.Assert.assertArrayEquals; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.ComponentName; +import android.content.Intent; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -354,8 +361,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { prepareAdmin1AsDo(); prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID); - final DevicePolicyManagerServiceTestable dpms; - dpms = bootDpmsUp(); + final DevicePolicyManagerServiceTestable dpms = bootDpmsUp(); // DO should still be DO since no migration should happen. assertTrue(dpms.mOwners.hasDeviceOwner()); @@ -364,13 +370,12 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { @SmallTest public void testCompMigrationAffiliated() throws Exception { prepareAdmin1AsDo(); - prepareAdmin1AsPo(COPE_PROFILE_USER_ID); + prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R); // Secure lock screen is needed for password policy APIs to work. when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true); - final DevicePolicyManagerServiceTestable dpms; - dpms = bootDpmsUp(); + final DevicePolicyManagerServiceTestable dpms = bootDpmsUp(); // DO should cease to be DO. assertFalse(dpms.mOwners.hasDeviceOwner()); @@ -408,6 +413,66 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { dpms.getProfileOwnerAdminLocked(COPE_PROFILE_USER_ID) .getEffectiveRestrictions() .containsKey(UserManager.DISALLOW_CONFIG_DATE_TIME)); + assertEquals("Personal apps suspension wasn't migrated", + DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED, + dpm.getPersonalAppsSuspendedReasons(admin1)); + }); + } + + @SmallTest + public void testCompMigration_keepSuspendedAppsWhenDpcIsRPlus() throws Exception { + prepareAdmin1AsDo(); + prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R); + + // Pretend some packages are suspended. + when(getServices().packageManagerInternal.isSuspendingAnyPackages( + PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true); + + final DevicePolicyManagerServiceTestable dpms = bootDpmsUp(); + + verify(getServices().packageManagerInternal, never()) + .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM); + + sendBroadcastWithUser(dpms, Intent.ACTION_USER_STARTED, USER_SYSTEM); + + // Verify that actual package suspension state is not modified after user start + verify(getServices().packageManagerInternal, never()) + .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM); + verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser( + any(), anyBoolean(), any(), any(), any(), any(), anyInt()); + + final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext); + poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID); + + runAsCaller(poContext, dpms, dpm -> { + assertEquals("Personal apps suspension wasn't migrated", + DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY, + dpm.getPersonalAppsSuspendedReasons(admin1)); + }); + } + + @SmallTest + public void testCompMigration_unsuspendAppsWhenDpcNotRPlus() throws Exception { + prepareAdmin1AsDo(); + prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q); + + // Pretend some packages are suspended. + when(getServices().packageManagerInternal.isSuspendingAnyPackages( + PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true); + + final DevicePolicyManagerServiceTestable dpms = bootDpmsUp(); + + // Verify that apps get unsuspended. + verify(getServices().packageManagerInternal) + .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM); + + final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext); + poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID); + + runAsCaller(poContext, dpms, dpm -> { + assertEquals("Personal apps weren't unsuspended", + DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED, + dpm.getPersonalAppsSuspendedReasons(admin1)); }); } @@ -439,22 +504,23 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { .getAbsoluteFile()); } - private void prepareAdmin1AsPo(int profileUserId) throws Exception { + private void prepareAdmin1AsPo(int profileUserId, int targetSdk) throws Exception { preparePo(profileUserId, admin1, R.raw.comp_profile_owner_same_package, - R.raw.comp_policies_profile_same_package, COPE_ADMIN1_APP_ID); + R.raw.comp_policies_profile_same_package, COPE_ADMIN1_APP_ID, targetSdk); } private void prepareAdminAnotherPackageAsPo(int profileUserId) throws Exception { preparePo(profileUserId, adminAnotherPackage, R.raw.comp_profile_owner_another_package, - R.raw.comp_policies_profile_another_package, COPE_ANOTHER_ADMIN_APP_ID); + R.raw.comp_policies_profile_another_package, COPE_ANOTHER_ADMIN_APP_ID, + Build.VERSION.SDK_INT); } private void preparePo(int profileUserId, ComponentName admin, int profileOwnerXmlResId, - int policyXmlResId, int adminAppId) throws Exception { + int policyXmlResId, int adminAppId, int targetSdk) throws Exception { final File profileDir = getServices().addUser(profileUserId, 0, UserManager.USER_TYPE_PROFILE_MANAGED, USER_SYSTEM /* profile group */); - setUpPackageManagerForFakeAdmin( - admin, UserHandle.getUid(profileUserId, adminAppId), admin1); + setUpPackageManagerForFakeAdmin(admin, UserHandle.getUid(profileUserId, adminAppId), + /* enabledSetting =*/ null, targetSdk, admin1); writeInputStreamToFile(getRawStream(policyXmlResId), (new File(profileDir, "device_policies.xml")).getAbsoluteFile()); writeInputStreamToFile(getRawStream(profileOwnerXmlResId), diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 4a774898e1b5..daaabf8141ff 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -254,7 +254,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Override protected void tearDown() throws Exception { - flushTasks(); + flushTasks(dpms); getMockTransferMetadataManager().deleteMetadataFile(); super.tearDown(); } @@ -4961,7 +4961,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // CertificateMonitor.updateInstalledCertificates is called on the background thread, // let it finish with system uid, otherwise it will throw and crash. - flushTasks(); + flushTasks(dpms); mContext.binder.restoreCallingIdentity(ident); } @@ -5459,7 +5459,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { getServices().injectBroadcast(mServiceContext, new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED) .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()), callerUser.getIdentifier()); - flushTasks(); + flushTasks(dpms); final List<String> ownerInstalledCaCerts = new ArrayList<>(); @@ -5486,7 +5486,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { getServices().injectBroadcast(mServiceContext, new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED) .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()), callerUser.getIdentifier()); - flushTasks(); + flushTasks(dpms); // Verify that the CA cert is no longer reported as installed by the Device Owner / Profile // Owner. @@ -5530,7 +5530,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { getServices().injectBroadcast(mServiceContext, new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED) .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()), callerUser.getIdentifier()); - flushTasks(); + flushTasks(dpms); // Removing the Profile Owner should clear the information on which CA certs were installed runAsCaller(admin1Context, dpms, dpm -> dpm.clearProfileOwner(admin1)); @@ -6311,7 +6311,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { clearInvocations(getServices().alarmManager); setUserUnlocked(CALLER_USER_HANDLE, false); - sendBroadcastWithUser(Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE); // Verify the alarm was scheduled for time when the warning should be shown. verify(getServices().alarmManager, times(1)) @@ -6325,7 +6325,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Pretend the alarm went off. dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_WARNING_TIME + 10); - sendBroadcastWithUser(ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE); // Verify the alarm was scheduled for the actual deadline this time. verify(getServices().alarmManager, times(1)).set(anyInt(), eq(PROFILE_OFF_DEADLINE), any()); @@ -6340,7 +6340,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Pretend the alarm went off. dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_DEADLINE + 10); - sendBroadcastWithUser(ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE); // Verify the alarm was not set. verifyZeroInteractions(getServices().alarmManager); @@ -6364,10 +6364,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; setUserUnlocked(CALLER_USER_HANDLE, false); - sendBroadcastWithUser(Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE); clearInvocations(getServices().alarmManager); setUserUnlocked(CALLER_USER_HANDLE, true); - sendBroadcastWithUser(Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE); // Verify that the alarm got discharged. verify(getServices().alarmManager, times(1)).cancel((PendingIntent) null); @@ -6384,16 +6384,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; setUserUnlocked(CALLER_USER_HANDLE, false); - sendBroadcastWithUser(Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE); // Pretend the alarm went off. dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_WARNING_TIME + 10); - sendBroadcastWithUser(ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE); clearInvocations(getServices().alarmManager); clearInvocations(getServices().notificationManager); setUserUnlocked(CALLER_USER_HANDLE, true); - sendBroadcastWithUser(Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE); // Verify that the alarm got discharged. verify(getServices().alarmManager, times(1)).cancel((PendingIntent) null); @@ -6413,24 +6413,24 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; setUserUnlocked(CALLER_USER_HANDLE, false); - sendBroadcastWithUser(Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE); // Pretend the alarm went off after the deadline. dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_DEADLINE + 10); - sendBroadcastWithUser(ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE); clearInvocations(getServices().alarmManager); clearInvocations(getServices().notificationManager); clearInvocations(getServices().ipackageManager); // Pretend the user clicked on the "apps suspended" notification to turn the profile on. - sendBroadcastWithUser(ACTION_TURN_PROFILE_ON_NOTIFICATION, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, ACTION_TURN_PROFILE_ON_NOTIFICATION, CALLER_USER_HANDLE); // Verify that the profile is turned on. verify(getServices().userManager, times(1)) .requestQuietModeEnabled(eq(false), eq(UserHandle.of(CALLER_USER_HANDLE))); setUserUnlocked(CALLER_USER_HANDLE, true); - sendBroadcastWithUser(Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE); + sendBroadcastWithUser(dpms, Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE); // Verify that the notification is removed (at this point DPC should show it). verify(getServices().notificationManager, times(1)) @@ -6454,13 +6454,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { .isEqualTo(DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT); } - private void sendBroadcastWithUser(String action, int userHandle) throws Exception { - final Intent intent = new Intent(action); - intent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle); - getServices().injectBroadcast(mServiceContext, intent, userHandle); - flushTasks(); - } - private void setUserUnlocked(int userHandle, boolean unlocked) { when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked); } @@ -6471,10 +6464,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManager.isUserUnlocked()).thenReturn(true); - // Pretend our admin handles CHECK_POLICY_COMPLIANCE intent. - final Intent intent = new Intent(ACTION_CHECK_POLICY_COMPLIANCE); - intent.setPackage(admin1.getPackageName()); - doReturn(Collections.singletonList(new ResolveInfo())) .when(getServices().packageManager).queryIntentActivitiesAsUser( any(Intent.class), anyInt(), eq(CALLER_USER_HANDLE)); @@ -6674,12 +6663,4 @@ public class DevicePolicyManagerTest extends DpmTestBase { return new StringParceledListSlice(Arrays.asList(s)); } - private void flushTasks() throws Exception { - dpms.mHandler.runWithScissors(() -> {}, 0 /*now*/); - dpms.mBackgroundHandler.runWithScissors(() -> {}, 0 /*now*/); - - // We can't let exceptions happen on the background thread. Throw them here if they happen - // so they still cause the test to fail despite being suppressed. - getServices().rethrowBackgroundBroadcastExceptions(); - } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java index 9a1a5fbfd186..41d54e9010d3 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java @@ -83,6 +83,23 @@ public abstract class DpmTestBase extends AndroidTestCase { return mServices; } + protected void sendBroadcastWithUser(DevicePolicyManagerServiceTestable dpms, String action, + int userHandle) throws Exception { + final Intent intent = new Intent(action); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle); + getServices().injectBroadcast(getContext(), intent, userHandle); + flushTasks(dpms); + } + + protected void flushTasks(DevicePolicyManagerServiceTestable dpms) throws Exception { + dpms.mHandler.runWithScissors(() -> { }, 0 /*now*/); + dpms.mBackgroundHandler.runWithScissors(() -> { }, 0 /*now*/); + + // We can't let exceptions happen on the background thread. Throw them here if they happen + // so they still cause the test to fail despite being suppressed. + getServices().rethrowBackgroundBroadcastExceptions(); + } + protected interface DpmRunnable { void run(DevicePolicyManager dpm) throws Exception; } @@ -180,7 +197,7 @@ public abstract class DpmTestBase extends AndroidTestCase { * @param copyFromAdmin package information for {@code admin} will be built based on this * component's information. */ - private void setUpPackageManagerForFakeAdmin(ComponentName admin, int packageUid, + protected void setUpPackageManagerForFakeAdmin(ComponentName admin, int packageUid, Integer enabledSetting, Integer appTargetSdk, ComponentName copyFromAdmin) throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java index 60104d390eb7..b09a3c374e86 100644 --- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java +++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java @@ -117,10 +117,10 @@ public final class ShareTargetPredictorTest { @Test public void testPredictTargets() { - mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4", 0)); when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class)); when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class)); @@ -165,12 +165,12 @@ public final class ShareTargetPredictorTest { @Test public void testPredictTargets_reachTargetsLimit() { - mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc5")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc6")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc5", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc6", 0)); when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class)); when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class)); @@ -250,6 +250,41 @@ public final class ShareTargetPredictorTest { } @Test + public void testPredictTargets_noSharingHistoryRankedByShortcutRank() { + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 3)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 2)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3", 1)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4", 0)); + + when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class)); + when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class)); + when(mPackageData2.getConversationInfo("sc3")).thenReturn(mock(ConversationInfo.class)); + // "sc4" does not have a ConversationInfo. + + mPredictor.predictTargets(); + + verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture()); + List<AppTarget> res = mAppTargetCaptor.getValue(); + assertEquals(4, res.size()); + + assertEquals("sc4", res.get(0).getId().getId()); + assertEquals(CLASS_2, res.get(0).getClassName()); + assertEquals(PACKAGE_2, res.get(0).getPackageName()); + + assertEquals("sc3", res.get(1).getId().getId()); + assertEquals(CLASS_2, res.get(1).getClassName()); + assertEquals(PACKAGE_2, res.get(1).getPackageName()); + + assertEquals("sc2", res.get(2).getId().getId()); + assertEquals(CLASS_1, res.get(2).getClassName()); + assertEquals(PACKAGE_1, res.get(2).getPackageName()); + + assertEquals("sc1", res.get(3).getId().getId()); + assertEquals(CLASS_1, res.get(3).getClassName()); + assertEquals(PACKAGE_1, res.get(3).getPackageName()); + } + + @Test public void testSortTargets() { AppTarget appTarget1 = new AppTarget.Builder( new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID)) @@ -348,19 +383,20 @@ public final class ShareTargetPredictorTest { } private static ShareShortcutInfo buildShareShortcut( - String packageName, String className, String shortcutId) { - ShortcutInfo shortcutInfo = buildShortcut(packageName, shortcutId); + String packageName, String className, String shortcutId, int rank) { + ShortcutInfo shortcutInfo = buildShortcut(packageName, shortcutId, rank); ComponentName componentName = new ComponentName(packageName, className); return new ShareShortcutInfo(shortcutInfo, componentName); } - private static ShortcutInfo buildShortcut(String packageName, String shortcutId) { + private static ShortcutInfo buildShortcut(String packageName, String shortcutId, int rank) { Context mockContext = mock(Context.class); when(mockContext.getPackageName()).thenReturn(packageName); when(mockContext.getUserId()).thenReturn(USER_ID); when(mockContext.getUser()).thenReturn(UserHandle.of(USER_ID)); ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mockContext, shortcutId) .setShortLabel(shortcutId) + .setRank(rank) .setIntent(new Intent("TestIntent")); return builder.build(); } diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 399a00f0cb5a..f205fde88c0d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -23,7 +23,6 @@ import static org.hamcrest.Matchers.empty; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -58,7 +57,6 @@ import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.mockito.stubbing.Answer; import java.security.cert.CertificateException; import java.util.ArrayList; @@ -71,20 +69,16 @@ import java.util.Set; @RunWith(JUnit4.class) public class AppsFilterTest { - private static final int DUMMY_CALLING_APPID = 10345; - private static final int DUMMY_TARGET_APPID = 10556; - private static final int DUMMY_ACTOR_APPID = 10656; - private static final int DUMMY_OVERLAY_APPID = 10756; - private static final int SYSTEM_USER = 0; - private static final int[] SINGLE_USER_ARRAY = {SYSTEM_USER}; + private static final int DUMMY_CALLING_UID = 10345; + private static final int DUMMY_TARGET_UID = 10556; + private static final int DUMMY_ACTOR_UID = 10656; + private static final int DUMMY_OVERLAY_UID = 10756; + private static final int DUMMY_ACTOR_TWO_UID = 10856; @Mock AppsFilter.FeatureConfig mFeatureConfigMock; - @Mock - AppsFilter.StateProvider mStateProvider; private ArrayMap<String, PackageSetting> mExisting = new ArrayMap<>(); - private Object mDummyLock = new Object(); private static ParsingPackage pkg(String packageName) { return PackageImpl.forTesting(packageName) @@ -176,24 +170,15 @@ public class AppsFilterTest { mExisting = new ArrayMap<>(); MockitoAnnotations.initMocks(this); - doAnswer(invocation -> { - ((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0)) - .currentState(mExisting, SINGLE_USER_ARRAY); - return null; - }).when(mStateProvider) - .runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class)); - when(mFeatureConfigMock.isGloballyEnabled()).thenReturn(true); - when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))).thenAnswer( - (Answer<Boolean>) invocation -> - ((AndroidPackage)invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion() - >= Build.VERSION_CODES.R); + when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))) + .thenReturn(true); } @Test public void testSystemReadyPropogates() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); appsFilter.onSystemReady(); verify(mFeatureConfigMock).onSystemReady(); } @@ -201,23 +186,22 @@ public class AppsFilterTest { @Test public void testQueriesAction_FilterMatches() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_APPID); + pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID); + pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_UID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); final Signature frameworkSignature = Mockito.mock(Signature.class); final PackageParser.SigningDetails frameworkSigningDetails = new PackageParser.SigningDetails(new Signature[]{frameworkSignature}, 1); @@ -227,174 +211,164 @@ public class AppsFilterTest { b -> b.setSigningDetails(frameworkSigningDetails)); appsFilter.onSystemReady(); - final int activityUid = DUMMY_TARGET_APPID; + final int activityUid = DUMMY_TARGET_UID; PackageSetting targetActivity = simulateAddPackage(appsFilter, pkg("com.target.activity", new IntentFilter("TEST_ACTION")), activityUid); - final int receiverUid = DUMMY_TARGET_APPID + 1; + final int receiverUid = DUMMY_TARGET_UID + 1; PackageSetting targetReceiver = simulateAddPackage(appsFilter, pkgWithReceiver("com.target.receiver", new IntentFilter("TEST_ACTION")), receiverUid); - final int callingUid = DUMMY_CALLING_APPID; + final int callingUid = DUMMY_CALLING_UID; PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.calling.action", new Intent("TEST_ACTION")), callingUid); - final int wildcardUid = DUMMY_CALLING_APPID + 1; + final int wildcardUid = DUMMY_CALLING_UID + 1; PackageSetting callingWildCard = simulateAddPackage(appsFilter, pkg("com.calling.wildcard", new Intent("*")), wildcardUid); - assertFalse(appsFilter.shouldFilterApplication(callingUid, calling, targetActivity, - SYSTEM_USER)); - assertTrue(appsFilter.shouldFilterApplication(callingUid, calling, targetReceiver, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(callingUid, calling, targetActivity, 0)); + assertTrue(appsFilter.shouldFilterApplication(callingUid, calling, targetReceiver, 0)); assertFalse(appsFilter.shouldFilterApplication( - wildcardUid, callingWildCard, targetActivity, SYSTEM_USER)); + wildcardUid, callingWildCard, targetActivity, 0)); assertTrue(appsFilter.shouldFilterApplication( - wildcardUid, callingWildCard, targetReceiver, SYSTEM_USER)); + wildcardUid, callingWildCard, targetReceiver, 0)); } @Test public void testQueriesProvider_FilterMatches() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID); + pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, pkgQueriesProvider("com.some.other.package", "com.some.authority"), - DUMMY_CALLING_APPID); + DUMMY_CALLING_UID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testQueriesDifferentProvider_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID); + pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, pkgQueriesProvider("com.some.other.package", "com.some.other.authority"), - DUMMY_CALLING_APPID); + DUMMY_CALLING_UID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testQueriesProviderWithSemiColon_FilterMatches() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"), - DUMMY_TARGET_APPID); + DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, pkgQueriesProvider("com.some.other.package", "com.some.authority"), - DUMMY_CALLING_APPID); + DUMMY_CALLING_UID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testQueriesAction_NoMatchingAction_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_APPID); + pkg("com.some.package"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID); + pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_UID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_APPID); - ParsingPackage callingPkg = pkg("com.some.other.package", - new Intent("TEST_ACTION")) - .setTargetSdkVersion(Build.VERSION_CODES.P); - PackageSetting calling = simulateAddPackage(appsFilter, callingPkg, - DUMMY_CALLING_APPID); + pkg("com.some.package"), DUMMY_TARGET_UID); + PackageSetting calling = simulateAddPackage(appsFilter, + pkg("com.some.other.package", + new Intent("TEST_ACTION")) + .setTargetSdkVersion(Build.VERSION_CODES.P), + DUMMY_CALLING_UID); + when(mFeatureConfigMock.packageIsEnabled(calling.pkg)).thenReturn(false); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testNoQueries_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_APPID); + pkg("com.some.package"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_APPID); + pkg("com.some.other.package"), DUMMY_CALLING_UID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testForceQueryable_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID); + pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_APPID); + pkg("com.some.other.package"), DUMMY_CALLING_UID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testForceQueryableByDevice_SystemCaller_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"}, - false, null); + new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_APPID, + pkg("com.some.package"), DUMMY_TARGET_UID, setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_APPID); + pkg("com.some.other.package"), DUMMY_CALLING_UID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testSystemSignedTarget_DoesntFilter() throws CertificateException { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); appsFilter.onSystemReady(); final Signature frameworkSignature = Mockito.mock(Signature.class); @@ -408,67 +382,62 @@ public class AppsFilterTest { simulateAddPackage(appsFilter, pkg("android"), 1000, b -> b.setSigningDetails(frameworkSigningDetails)); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_APPID, + DUMMY_TARGET_UID, b -> b.setSigningDetails(frameworkSigningDetails) .setPkgFlags(ApplicationInfo.FLAG_SYSTEM)); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_APPID, + pkg("com.some.other.package"), DUMMY_CALLING_UID, b -> b.setSigningDetails(otherSigningDetails)); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testForceQueryableByDevice_NonSystemCaller_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"}, - false, null); + new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_APPID); + pkg("com.some.package"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_APPID); + pkg("com.some.other.package"), DUMMY_CALLING_UID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testSystemQueryable_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, + new AppsFilter(mFeatureConfigMock, new String[]{}, true /* system force queryable */, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_APPID, + pkg("com.some.package"), DUMMY_TARGET_UID, setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_APPID); + pkg("com.some.other.package"), DUMMY_CALLING_UID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testQueriesPackage_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_APPID); + pkg("com.some.package"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", "com.some.package"), DUMMY_CALLING_APPID); + pkg("com.some.other.package", "com.some.package"), DUMMY_CALLING_UID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test @@ -476,67 +445,63 @@ public class AppsFilterTest { when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))) .thenReturn(false); final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage( - appsFilter, pkg("com.some.package"), DUMMY_TARGET_APPID); + appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage( - appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_APPID); + appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_UID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testSystemUid_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_APPID); + pkg("com.some.package"), DUMMY_TARGET_UID); - assertFalse(appsFilter.shouldFilterApplication(SYSTEM_USER, null, target, SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(0, null, target, 0)); assertFalse(appsFilter.shouldFilterApplication(Process.FIRST_APPLICATION_UID - 1, - null, target, SYSTEM_USER)); + null, target, 0)); } @Test public void testNonSystemUid_NoCallingSetting_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_APPID); + pkg("com.some.package"), DUMMY_TARGET_UID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, null, target, - SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, null, target, 0)); } @Test public void testNoTargetPackage_filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = new PackageSettingBuilder() - .setAppId(DUMMY_TARGET_APPID) .setName("com.some.package") .setCodePath("/") .setResourcePath("/") .setPVersionCode(1L) .build(); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID); + pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_UID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test @@ -551,11 +516,7 @@ public class AppsFilterTest { .setOverlayTargetName("overlayableName"); ParsingPackage actor = pkg("com.some.package.actor"); - final AppsFilter appsFilter = new AppsFilter( - mStateProvider, - mFeatureConfigMock, - new String[]{}, - false, + final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false, new OverlayReferenceMapper.Provider() { @Nullable @Override @@ -583,34 +544,31 @@ public class AppsFilterTest { simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); - PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID); - PackageSetting overlaySetting = - simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID); - PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID); + PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID); + PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID); + PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_UID); // Actor can see both target and overlay - assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting, - targetSetting, SYSTEM_USER)); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting, - overlaySetting, SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting, + targetSetting, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting, + overlaySetting, 0)); // But target/overlay can't see each other - assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, targetSetting, - overlaySetting, SYSTEM_USER)); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting, - targetSetting, SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting, + overlaySetting, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting, + targetSetting, 0)); // And can't see the actor - assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, targetSetting, - actorSetting, SYSTEM_USER)); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting, - actorSetting, SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting, + actorSetting, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting, + actorSetting, 0)); } @Test public void testActsOnTargetOfOverlayThroughSharedUser() throws Exception { -// Debug.waitForDebugger(); - final String actorName = "overlay://test/actorName"; ParsingPackage target = pkg("com.some.package.target") @@ -622,11 +580,7 @@ public class AppsFilterTest { ParsingPackage actorOne = pkg("com.some.package.actor.one"); ParsingPackage actorTwo = pkg("com.some.package.actor.two"); - final AppsFilter appsFilter = new AppsFilter( - mStateProvider, - mFeatureConfigMock, - new String[]{}, - false, + final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false, new OverlayReferenceMapper.Provider() { @Nullable @Override @@ -655,114 +609,108 @@ public class AppsFilterTest { simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); - PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID); - SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser", - targetSetting.pkgFlags, targetSetting.pkgPrivateFlags); - PackageSetting overlaySetting = - simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID); - simulateAddPackage(appsFilter, actorOne, DUMMY_ACTOR_APPID, - null /*settingBuilder*/, actorSharedSetting); - simulateAddPackage(appsFilter, actorTwo, DUMMY_ACTOR_APPID, - null /*settingBuilder*/, actorSharedSetting); + PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID); + PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID); + PackageSetting actorOneSetting = simulateAddPackage(appsFilter, actorOne, DUMMY_ACTOR_UID); + PackageSetting actorTwoSetting = simulateAddPackage(appsFilter, actorTwo, + DUMMY_ACTOR_TWO_UID); + SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser", + actorOneSetting.pkgFlags, actorOneSetting.pkgPrivateFlags); + actorSharedSetting.addPackage(actorOneSetting); + actorSharedSetting.addPackage(actorTwoSetting); // actorTwo can see both target and overlay - assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSharedSetting, - targetSetting, SYSTEM_USER)); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSharedSetting, - overlaySetting, SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting, + targetSetting, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting, + overlaySetting, 0)); } @Test public void testInitiatingApp_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_APPID); + DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), - DUMMY_CALLING_APPID, withInstallSource(target.name, null, null, false)); + DUMMY_CALLING_UID, withInstallSource(target.name, null, null, false)); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testUninstalledInitiatingApp_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_APPID); + DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), - DUMMY_CALLING_APPID, withInstallSource(target.name, null, null, true)); + DUMMY_CALLING_UID, withInstallSource(target.name, null, null, true)); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testOriginatingApp_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_APPID); + DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), - DUMMY_CALLING_APPID, withInstallSource(null, target.name, null, false)); + DUMMY_CALLING_UID, withInstallSource(null, target.name, null, false)); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testInstallingApp_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_APPID); + DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), - DUMMY_CALLING_APPID, withInstallSource(null, null, target.name, false)); + DUMMY_CALLING_UID, withInstallSource(null, null, target.name, false)); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, - SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } @Test public void testInstrumentation_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_APPID); + DUMMY_TARGET_UID); PackageSetting instrumentation = simulateAddPackage(appsFilter, pkgWithInstrumentation("com.some.other.package", "com.some.package"), - DUMMY_CALLING_APPID); + DUMMY_CALLING_UID); assertFalse( - appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target, - SYSTEM_USER)); + appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, instrumentation, target, 0)); assertFalse( - appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation, - SYSTEM_USER)); + appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, target, instrumentation, 0)); } @Test public void testWhoCanSee() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -780,25 +728,21 @@ public class AppsFilterTest { queriesProviderAppId); final int[] systemFilter = - appsFilter.getVisibilityWhitelist(system, SINGLE_USER_ARRAY, mExisting) - .get(SYSTEM_USER); + appsFilter.getVisibilityWhitelist(system, new int[]{0}, mExisting).get(0); assertThat(toList(systemFilter), empty()); final int[] seesNothingFilter = - appsFilter.getVisibilityWhitelist(seesNothing, SINGLE_USER_ARRAY, mExisting) - .get(SYSTEM_USER); + appsFilter.getVisibilityWhitelist(seesNothing, new int[]{0}, mExisting).get(0); assertThat(toList(seesNothingFilter), contains(seesNothingAppId)); final int[] hasProviderFilter = - appsFilter.getVisibilityWhitelist(hasProvider, SINGLE_USER_ARRAY, mExisting) - .get(SYSTEM_USER); + appsFilter.getVisibilityWhitelist(hasProvider, new int[]{0}, mExisting).get(0); assertThat(toList(hasProviderFilter), contains(hasProviderAppId, queriesProviderAppId)); int[] queriesProviderFilter = - appsFilter.getVisibilityWhitelist(queriesProvider, SINGLE_USER_ARRAY, mExisting) - .get(SYSTEM_USER); + appsFilter.getVisibilityWhitelist(queriesProvider, new int[]{0}, mExisting).get(0); assertThat(toList(queriesProviderFilter), contains(queriesProviderAppId)); @@ -807,8 +751,7 @@ public class AppsFilterTest { // ensure implicit access is included in the filter queriesProviderFilter = - appsFilter.getVisibilityWhitelist(queriesProvider, SINGLE_USER_ARRAY, mExisting) - .get(SYSTEM_USER); + appsFilter.getVisibilityWhitelist(queriesProvider, new int[]{0}, mExisting).get(0); assertThat(toList(queriesProviderFilter), contains(hasProviderAppId, queriesProviderAppId)); } @@ -836,17 +779,11 @@ public class AppsFilterTest { private PackageSetting simulateAddPackage(AppsFilter filter, ParsingPackage newPkgBuilder, int appId) { - return simulateAddPackage(filter, newPkgBuilder, appId, null /*settingBuilder*/); + return simulateAddPackage(filter, newPkgBuilder, appId, null); } private PackageSetting simulateAddPackage(AppsFilter filter, ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action) { - return simulateAddPackage(filter, newPkgBuilder, appId, action, null /*sharedUserSetting*/); - } - - private PackageSetting simulateAddPackage(AppsFilter filter, - ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action, - @Nullable SharedUserSetting sharedUserSetting) { AndroidPackage newPkg = ((ParsedPackage) newPkgBuilder.hideAsParsed()).hideAsFinal(); final PackageSettingBuilder settingBuilder = new PackageSettingBuilder() @@ -858,12 +795,8 @@ public class AppsFilterTest { .setPVersionCode(1L); final PackageSetting setting = (action == null ? settingBuilder : action.withBuilder(settingBuilder)).build(); + filter.addPackage(setting, mExisting); mExisting.put(newPkg.getPackageName(), setting); - if (sharedUserSetting != null) { - sharedUserSetting.addPackage(setting); - setting.sharedUser = sharedUserSetting; - } - filter.addPackage(setting); return setting; } @@ -876,3 +809,4 @@ public class AppsFilterTest { return setting -> setting.setInstallSource(installSource); } } + diff --git a/services/tests/servicestests/src/com/android/server/textclassifier/FixedSizeQueueTest.java b/services/tests/servicestests/src/com/android/server/textclassifier/FixedSizeQueueTest.java new file mode 100644 index 000000000000..90527b82c2c3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/textclassifier/FixedSizeQueueTest.java @@ -0,0 +1,109 @@ +/* + * 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.textclassifier; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class FixedSizeQueueTest { + + @Test + public void add_belowMaxCapacity() { + FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null); + assertThat(queue.size()).isEqualTo(0); + + queue.add(1); + + assertThat(queue.size()).isEqualTo(1); + assertThat(queue.poll()).isEqualTo(1); + } + + @Test + public void add_exceedMaxCapacity() { + FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(2, /* onEntryEvictedListener= */ null); + + queue.add(1); + queue.add(2); + queue.add(3); + + assertThat(queue.size()).isEqualTo(2); + assertThat(queue.poll()).isEqualTo(2); + assertThat(queue.poll()).isEqualTo(3); + } + + @Test + public void poll() { + FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null); + + queue.add(1); + + assertThat(queue.poll()).isEqualTo(1); + assertThat(queue.poll()).isNull(); + } + + @Test + public void remove() { + FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null); + + queue.add(1); + + assertThat(queue.remove(1)).isTrue(); + assertThat(queue.isEmpty()).isTrue(); + } + + @Test + public void remove_noSuchElement() { + FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null); + + queue.add(1); + + assertThat(queue.remove(2)).isFalse(); + } + + @Test + public void isEmpty_true() { + FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null); + + assertThat(queue.isEmpty()).isTrue(); + } + + @Test + public void isEmpty_false() { + FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null); + + queue.add(1); + + assertThat(queue.isEmpty()).isFalse(); + } + + @Test + public void onEntryEvicted() { + List<Integer> onEntryEvictedElements = new ArrayList<>(); + FixedSizeQueue<Integer> queue = + new FixedSizeQueue<>(1, onEntryEvictedElements::add); + + queue.add(1); + queue.add(2); + queue.add(3); + + assertThat(onEntryEvictedElements).containsExactly(1, 2).inOrder(); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index ae22b2be0c69..ef4d5db2f32f 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -178,6 +178,7 @@ import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -347,6 +348,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { super.setNotificationAssistantAccessGrantedForUserInternal(assistant, userId, granted); } + @Override + protected String[] getStringArrayResource(int key) { + return new String[] {PKG_O}; + } + private void setNotificationAssistantAccessGrantedCallback( @Nullable NotificationAssistantAccessGrantedCallback callback) { this.mNotificationAssistantAccessGrantedCallback = callback; @@ -434,7 +440,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Setup managed services mListener = mListeners.new ManagedServiceInfo( - null, new ComponentName(PKG, "test_class"), mUid, true, null, 0); + null, new ComponentName(PKG, "test_class"), + UserHandle.getUserId(mUid), true, null, 0); ComponentName defaultComponent = ComponentName.unflattenFromString("config/device"); ArraySet<ComponentName> components = new ArraySet<>(); components.add(defaultComponent); @@ -444,7 +451,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.getDefaultComponents()).thenReturn(components); when(mAssistants.queryPackageForServices( anyString(), anyInt(), anyInt())).thenReturn(components); - when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + when(mListeners.checkServiceTokenLocked(null)).thenReturn(mListener); ManagedServices.Config listenerConfig = new ManagedServices.Config(); listenerConfig.xmlTag = NotificationListeners.TAG_ENABLED_NOTIFICATION_LISTENERS; when(mListeners.getConfig()).thenReturn(listenerConfig); @@ -497,6 +504,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.createNotificationChannels( PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel))); + mBinderService.createNotificationChannels( + PKG_P, new ParceledListSlice(Arrays.asList(mTestNotificationChannel))); + mBinderService.createNotificationChannels( + PKG_O, new ParceledListSlice(Arrays.asList(mTestNotificationChannel))); assertNotNull(mBinderService.getNotificationChannel( PKG, mContext.getUserId(), PKG, TEST_CHANNEL_ID)); clearInvocations(mRankingHandler); @@ -583,7 +594,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setGroupSummary(isSummary); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag" + System.currentTimeMillis(), mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); return new NotificationRecord(mContext, sbn, channel); } @@ -603,7 +614,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nb.extend(extender); } StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); return new NotificationRecord(mContext, sbn, channel); } @@ -635,7 +646,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey, isSummary); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, tag, mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); return new NotificationRecord(mContext, sbn, channel); } @@ -1328,6 +1339,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @Ignore public void testPostCancelPostNotifiesListeners() throws Exception { // WHEN a notification is posted final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); @@ -1687,7 +1699,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setSmallIcon(android.R.drawable.sym_def_app_icon) .build(); StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, null, mUid, 0, - n, new UserHandle(mUid), null, 0); + n, UserHandle.getUserHandleForUid(mUid), null, 0); sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; mBinderService.enqueueNotificationWithTag(PKG, PKG, null, sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -2191,7 +2203,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testCreateChannelNotifyListener() throws Exception { List<String> associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean())) @@ -2219,7 +2232,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testCreateChannelGroupNotifyListener() throws Exception { List<String> associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); mService.setPreferencesHelper(mPreferencesHelper); NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b"); NotificationChannelGroup group2 = new NotificationChannelGroup("n", "m"); @@ -2239,7 +2253,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testUpdateChannelNotifyListener() throws Exception { List<String> associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); mService.setPreferencesHelper(mPreferencesHelper); mTestNotificationChannel.setLightColor(Color.CYAN); when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), @@ -2257,7 +2272,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testDeleteChannelNotifyListener() throws Exception { List<String> associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean())) @@ -2273,7 +2289,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testDeleteChannelGroupNotifyListener() throws Exception { List<String> associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c"); mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt())) @@ -2290,7 +2307,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.setPreferencesHelper(mPreferencesHelper); List<String> associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean())) .thenReturn(mTestNotificationChannel); @@ -2310,7 +2328,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); List<String> associations = new ArrayList<>(); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); try { mBinderService.updateNotificationChannelFromPrivilegedListener( @@ -2333,7 +2352,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.setPreferencesHelper(mPreferencesHelper); List<String> associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); mListener = mock(ManagedServices.ManagedServiceInfo.class); mListener.component = new ComponentName(PKG, PKG); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); @@ -2360,7 +2380,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.setPreferencesHelper(mPreferencesHelper); List<String> associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); mBinderService.getNotificationChannelsFromPrivilegedListener( null, PKG, Process.myUserHandle()); @@ -2373,7 +2394,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testGetNotificationChannelFromPrivilegedListener_cdm_noAccess() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); List<String> associations = new ArrayList<>(); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); try { mBinderService.getNotificationChannelsFromPrivilegedListener( @@ -2391,7 +2413,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testGetNotificationChannelFromPrivilegedListener_assistant_success() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(new ArrayList<>()); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(new ArrayList<>()); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); mBinderService.getNotificationChannelsFromPrivilegedListener( @@ -2405,7 +2428,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testGetNotificationChannelFromPrivilegedListener_assistant_noAccess() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(new ArrayList<>()); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(new ArrayList<>()); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false); try { @@ -2425,7 +2449,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.setPreferencesHelper(mPreferencesHelper); List<String> associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); mListener = mock(ManagedServices.ManagedServiceInfo.class); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); @@ -2447,7 +2472,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.setPreferencesHelper(mPreferencesHelper); List<String> associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); mBinderService.getNotificationChannelGroupsFromPrivilegedListener( null, PKG, Process.myUserHandle()); @@ -2459,7 +2485,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); List<String> associations = new ArrayList<>(); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); try { mBinderService.getNotificationChannelGroupsFromPrivilegedListener( @@ -2476,7 +2503,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); List<String> associations = new ArrayList<>(); - when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); mListener = mock(ManagedServices.ManagedServiceInfo.class); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); @@ -3026,8 +3054,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setNotificationAssistantAccessGranted(c, true); verify(mListeners).migrateToXml(); - verify(mListeners).notifyNotificationChannelChanged(anyString(), any(), any(), - anyInt()); verify(mConditionProviders).setPackageOrComponentEnabled( anyString(), anyInt(), anyBoolean(), anyBoolean()); verify(mAssistants).migrateToXml(); @@ -3041,8 +3067,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true); verify(mListeners).migrateToXml(); - verify(mListeners).notifyNotificationChannelChanged(anyString(), any(), any(), - anyInt()); verify(mConditionProviders).setPackageOrComponentEnabled( anyString(), anyInt(), anyBoolean(), anyBoolean()); verify(mAssistants).migrateToXml(); @@ -3178,7 +3202,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "testNoFakeColorizedPermission", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), @@ -3216,7 +3240,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .build(); StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "tag", mUid, 0, - n, new UserHandle(mUid), null, 0); + n, UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord otherPackage = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.addEnqueuedNotification(otherPackage); @@ -3224,7 +3248,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Same notifications are enqueued as posted, everything counts b/c id and tag don't match // anything that's currently enqueued or posted - int userId = new UserHandle(mUid).getIdentifier(); + int userId = UserHandle.getUserId(mUid); assertEquals(40, mService.getNotificationCountLocked(PKG, userId, 0, null)); assertEquals(40, @@ -3441,8 +3465,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "testBumpFGImportance_noChannelChangePreOApp", - Binder.getCallingUid(), 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, - 0); + Binder.getCallingUid(), 0, nb.build(), + UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0); mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), sbn.getOpPkg(), sbn.getTag(), sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -3459,7 +3483,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "testBumpFGImportance_noChannelChangePreOApp", Binder.getCallingUid(), - 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0); + 0, nb.build(), UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0); mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg, "testBumpFGImportance_noChannelChangePreOApp", @@ -3813,7 +3837,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .addMessage(message1) .addMessage(message2)); NotificationRecord recordA = new NotificationRecord(mContext, new StatusBarNotification( - PKG, PKG, 0, "tag", mUid, 0, nbA.build(), new UserHandle(mUid), null, 0), c); + PKG, PKG, 0, "tag", mUid, 0, nbA.build(), UserHandle.getUserHandleForUid(mUid), + null, 0), c); // First post means we grant access to both reset(mUgm); @@ -3831,7 +3856,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setSmallIcon(android.R.drawable.sym_def_app_icon) .setStyle(new Notification.MessagingStyle("").addMessage(message2)); NotificationRecord recordB = new NotificationRecord(mContext, new StatusBarNotification(PKG, - PKG, 0, "tag", mUid, 0, nbB.build(), new UserHandle(mUid), null, 0), c); + PKG, 0, "tag", mUid, 0, nbB.build(), UserHandle.getUserHandleForUid(mUid), null, 0), + c); // Update means we drop access to first reset(mUgmInternal); @@ -3870,7 +3896,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.MessagingStyle("") .addMessage(message1)); NotificationRecord recordA = new NotificationRecord(mContext, new StatusBarNotification( - PKG, PKG, 0, "tag", mUid, 0, nbA.build(), new UserHandle(mUid), null, 0), c); + PKG, PKG, 0, "tag", mUid, 0, nbA.build(), UserHandle.getUserHandleForUid(mUid), + null, 0), c); doThrow(new SecurityException("no access")).when(mUgm) .grantUriPermissionFromOwner( @@ -4029,7 +4056,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setContentTitle("foo"); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb1.build(), new UserHandle(mUid), null, 0); + nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); @@ -4037,7 +4064,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setFlag(FLAG_FOREGROUND_SERVICE, true) .setContentTitle("bar"); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4049,14 +4076,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setContentTitle("foo"); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb1.build(), new UserHandle(mUid), null, 0); + nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setContentTitle("bar"); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4069,7 +4096,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.InboxStyle() .addLine("line1").addLine("line2")); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb1.build(), new UserHandle(mUid), null, 0); + nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); @@ -4077,7 +4104,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.InboxStyle() .addLine("line1").addLine("line2_changed")); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4087,7 +4114,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.InboxStyle() .addLine("line1")); StatusBarNotification sbn3 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb3.build(), new UserHandle(mUid), null, 0); + nb3.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r3 = new NotificationRecord(mContext, sbn3, mock(NotificationChannel.class)); @@ -4097,7 +4124,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.InboxStyle() .addLine("line1").addLine("line2").addLine("line3")); StatusBarNotification sbn4 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb4.build(), new UserHandle(mUid), null, 0); + nb4.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r4 = new NotificationRecord(mContext, sbn4, mock(NotificationChannel.class)); @@ -4106,7 +4133,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb5 = new Notification.Builder(mContext, "") .setContentText("not an inbox"); StatusBarNotification sbn5 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb5.build(), new UserHandle(mUid), null, 0); + nb5.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r5 = new NotificationRecord(mContext, sbn5, mock(NotificationChannel.class)); @@ -4118,14 +4145,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setContentText("foo"); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb1.build(), new UserHandle(mUid), null, 0); + nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setContentText("bar"); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4137,14 +4164,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setContentText("foo"); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb1.build(), new UserHandle(mUid), null, 0); + nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setContentText("foo"); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4156,14 +4183,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setContentText(Html.fromHtml("<b>foo</b>")); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb1.build(), new UserHandle(mUid), null, 0); + nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setContentText(Html.fromHtml("<b>foo</b>")); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4175,14 +4202,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setContentText(Html.fromHtml("<b>foo</b>")); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb1.build(), new UserHandle(mUid), null, 0); + nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setContentText(Html.fromHtml("<b>bar</b>")); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4194,14 +4221,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setProgress(100, 90, false); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb1.build(), new UserHandle(mUid), null, 0); + nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setProgress(100, 100, false); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4213,14 +4240,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setProgress(100, 90, false); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb1.build(), new UserHandle(mUid), null, 0); + nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setProgress(100, 91, false); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4232,14 +4259,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb1 = new Notification.Builder(mContext, "") .setProgress(100, 100, false); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb1.build(), new UserHandle(mUid), null, 0); + nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); Notification.Builder nb2 = new Notification.Builder(mContext, "") .setProgress(100, 100, false); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4253,7 +4280,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setFlag(Notification.FLAG_GROUP_SUMMARY, true) .setContentText("foo"); StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb1.build(), new UserHandle(mUid), null, 0); + nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class)); @@ -4262,7 +4289,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setFlag(Notification.FLAG_GROUP_SUMMARY, true) .setContentText("bar"); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4276,7 +4303,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setFlag(Notification.FLAG_GROUP_SUMMARY, true) .setContentText("bar"); StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, - nb2.build(), new UserHandle(mUid), null, 0); + nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class)); @@ -4694,7 +4721,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { n.flags |= FLAG_FOREGROUND_SERVICE; StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 9, null, mUid, 0, - n, new UserHandle(mUid), null, 0); + n, UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.addEnqueuedNotification(r); @@ -4713,7 +4740,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { n.flags |= FLAG_FOREGROUND_SERVICE; StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 9, null, mUid, 0, - n, new UserHandle(mUid), null, 0); + n, UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.addNotification(r); @@ -5026,7 +5053,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(PKG, "opPkg", 0, "tag", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.reportSeen(r); @@ -5050,7 +5077,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, r.getSbn().getId(), r.getSbn().getTag(), mUid, 0, new Notification.Builder(mContext, mTestNotificationChannel.getId()).build(), - new UserHandle(mUid), null, 0); + UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.addEnqueuedNotification(update); assertNull(update.getSbn().getNotification().getSmallIcon()); @@ -5084,7 +5111,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testgetNotificationChannels_crossUser() throws Exception { + public void testGetNotificationChannels_crossUser() throws Exception { // same user no problem mBinderService.getNotificationChannels("src", "target", mContext.getUserId()); @@ -5273,7 +5300,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setSmallIcon(android.R.drawable.sym_def_app_icon) .setBubbleMetadata(getBubbleMetadata()); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); // Say we're foreground @@ -5319,7 +5346,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nb.setShortcutId(null); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -5363,7 +5390,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "testFlagBubbleNotifs_noFlag_notBubble", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); // Post the notification @@ -6178,7 +6205,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nb.setShortcutId(VALID_CONVO_SHORTCUT_ID); nb.setBubbleMetadata(metadata); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, - "tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0); + "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); // Test: Send the bubble notification @@ -6203,7 +6230,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Test: Remove the shortcut when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null); launcherAppsCallback.getValue().onShortcutsChanged(PKG, Collections.emptyList(), - new UserHandle(mUid)); + UserHandle.getUserHandleForUid(mUid)); waitForIdle(); // Verify: @@ -6238,7 +6265,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nb.setShortcutId(shortcutId); nb.setBubbleMetadata(metadata); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, - "tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0); + "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); // Pretend the shortcut exists @@ -6686,15 +6713,43 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testRecordMessages_invalidMsg() throws RemoteException { - NotificationRecord nr = - generateMessageBubbleNotifRecord(mTestNotificationChannel, - "testRecordMessages_invalidMsg"); + Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */, + null /* groupKey */, false /* isSummary */); + nb.setShortcutId(null); + StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1, + "testRecordMessages_invalidMsg", mUid, 0, nb.build(), + UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + mBinderService.enqueueNotificationWithTag(PKG_P, PKG_P, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); - assertTrue(mBinderService.isInInvalidMsgState(PKG, mUid)); + assertTrue(mBinderService.isInInvalidMsgState(PKG_P, mUid)); + } + + @Test + public void testRecordMessages_invalidMsg_notMessageStyle() throws RemoteException { + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setShortcutId(null) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setCategory(Notification.CATEGORY_MESSAGE); + StatusBarNotification sbn = new StatusBarNotification(PKG_O, PKG_O, 1, + "testRecordMessages_invalidMsg_notMessageStyle", mUid, 0, nb.build(), + UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null); + mBinderService.enqueueNotificationWithTag(PKG_O, PKG_O, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + // PKG_O is allowed to be in conversation space b/c of override in + // TestableNotificationManagerService + assertTrue(mBinderService.isInInvalidMsgState(PKG_O, mUid)); } @Test @@ -6702,24 +6757,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */, null /* groupKey */, false /* isSummary */); nb.setShortcutId(null); - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, - "testRecordMessages_validMsg", mUid, 0, nb.build(), new UserHandle(mUid), null, 0); + StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1, + "testRecordMessages_validMsg", mUid, 0, nb.build(), + UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + mBinderService.enqueueNotificationWithTag(PKG_P, PKG_P, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); - assertTrue(mBinderService.isInInvalidMsgState(PKG, mUid)); + assertTrue(mBinderService.isInInvalidMsgState(PKG_P, mUid)); nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testRecordMessages_validMsg"); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + mBinderService.enqueueNotificationWithTag(PKG_P, PKG_P, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); - assertFalse(mBinderService.isInInvalidMsgState(PKG, mUid)); + assertFalse(mBinderService.isInInvalidMsgState(PKG_P, mUid)); } @Test @@ -6739,7 +6795,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nb.setShortcutId(null); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "testRecordMessages_invalidMsg_afterValidMsg_2", mUid, 0, nb.build(), - new UserHandle(mUid), null, 0); + UserHandle.getUserHandleForUid(mUid), null, 0); nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 30df0d4b4ad9..4040fa6a675e 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -54,6 +54,8 @@ <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityViewTestActivity" /> <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityInActivityView" android:resizeableActivity="true" /> + <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityLaunchesNewActivityInActivityView" + android:resizeableActivity="true" /> <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$LandscapeActivity" android:screenOrientation="sensorLandscape" android:showWhenLocked="true" diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java index 2c17bbeae498..2bd342420e2c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java @@ -299,6 +299,20 @@ public class TaskStackChangedListenerTest { waitForCallback(singleTaskDisplayDrawnLatch); } + public static class ActivityLaunchesNewActivityInActivityView extends TestActivity { + private boolean mActivityBLaunched = false; + + @Override + protected void onPostResume() { + super.onPostResume(); + if (mActivityBLaunched) { + return; + } + mActivityBLaunched = true; + startActivity(new Intent(this, ActivityB.class)); + } + } + @Test public void testSingleTaskDisplayEmpty() throws Exception { final Instrumentation instrumentation = getInstrumentation(); @@ -335,13 +349,20 @@ public class TaskStackChangedListenerTest { }); waitForCallback(activityViewReadyLatch); + // 1. start ActivityLaunchesNewActivityInActivityView in an ActivityView + // 2. ActivityLaunchesNewActivityInActivityView launches ActivityB + // 3. ActivityB finishes self. + // 4. Verify ITaskStackListener#onSingleTaskDisplayEmpty is not called yet. final Context context = instrumentation.getContext(); - Intent intent = new Intent(context, ActivityInActivityView.class); + Intent intent = new Intent(context, ActivityLaunchesNewActivityInActivityView.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); activityView.startActivity(intent); waitForCallback(singleTaskDisplayDrawnLatch); + UiDevice.getInstance(getInstrumentation()).waitForIdle(); assertEquals(1, singleTaskDisplayEmptyLatch.getCount()); + // 5. Release the container, and ActivityLaunchesNewActivityInActivityView finishes. + // 6. Verify ITaskStackListener#onSingleTaskDisplayEmpty is called. activityView.release(); waitForCallback(activityViewDestroyedLatch); waitForCallback(singleTaskDisplayEmptyLatch); diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index b3d7c0d36763..4606fb4b631c 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -21,7 +21,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.os.SystemConfigManager; import android.os.UserHandle; import android.permission.PermissionManager; @@ -30,9 +29,7 @@ import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.Log; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.util.ArrayUtils; import java.util.ArrayList; import java.util.List; @@ -162,12 +159,9 @@ public final class CarrierAppUtils { try { for (ApplicationInfo ai : candidates) { String packageName = ai.packageName; - String[] restrictedCarrierApps = Resources.getSystem().getStringArray( - R.array.config_restrictedPreinstalledCarrierApps); boolean hasPrivileges = telephonyManager != null && telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) - == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS - && !ArrayUtils.contains(restrictedCarrierApps, packageName); + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; // add hiddenUntilInstalled flag for carrier apps and associated apps packageManager.setSystemAppState( diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt index de65ba24972b..a4d8353d1253 100644 --- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt +++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt @@ -44,7 +44,7 @@ class NetworkAgentConfigTest { setPartialConnectivityAcceptable(false) setUnvalidatedConnectivityAcceptable(true) }.build() - assertParcelSane(config, 9) + assertParcelSane(config, 10) } @Test @IgnoreUpTo(Build.VERSION_CODES.Q) diff --git a/tests/utils/hostutils/Android.bp b/tests/utils/hostutils/Android.bp new file mode 100644 index 000000000000..c9ad70280aa6 --- /dev/null +++ b/tests/utils/hostutils/Android.bp @@ -0,0 +1,28 @@ +// 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. +// + +java_library_host { + name: "frameworks-base-hostutils", + + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + + libs: [ + "tradefed", + "junit", + ], +} diff --git a/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java new file mode 100644 index 000000000000..6bd6985f9675 --- /dev/null +++ b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java @@ -0,0 +1,362 @@ +/* + * 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.internal.util.test; + +import static org.junit.Assert.assertTrue; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.rules.ExternalResource; +import org.junit.rules.TemporaryFolder; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +import javax.annotation.Nullable; + +/** + * Allows pushing files onto the device and various options for rebooting. Useful for installing + * APKs/files to system partitions which otherwise wouldn't be easily changed. + * + * It's strongly recommended to pass in a {@link ClassRule} annotated {@link TestRuleDelegate} to + * do a full reboot at the end of a test to ensure the device is in a valid state, assuming the + * default {@link RebootStrategy#FULL} isn't used. + */ +public class SystemPreparer extends ExternalResource { + private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000; + + // The paths of the files pushed onto the device through this rule. + private ArrayList<String> mPushedFiles = new ArrayList<>(); + + // The package names of packages installed through this rule. + private ArrayList<String> mInstalledPackages = new ArrayList<>(); + + private final TemporaryFolder mHostTempFolder; + private final DeviceProvider mDeviceProvider; + private final RebootStrategy mRebootStrategy; + private final TearDownRule mTearDownRule; + + public SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) { + this(hostTempFolder, RebootStrategy.FULL, null, deviceProvider); + } + + public SystemPreparer(TemporaryFolder hostTempFolder, RebootStrategy rebootStrategy, + @Nullable TestRuleDelegate testRuleDelegate, DeviceProvider deviceProvider) { + mHostTempFolder = hostTempFolder; + mDeviceProvider = deviceProvider; + mRebootStrategy = rebootStrategy; + mTearDownRule = new TearDownRule(mDeviceProvider); + if (testRuleDelegate != null) { + testRuleDelegate.setDelegate(mTearDownRule); + } + } + + /** Copies a file within the host test jar to a path on device. */ + public SystemPreparer pushResourceFile(String filePath, String outputPath) + throws DeviceNotAvailableException, IOException { + final ITestDevice device = mDeviceProvider.getDevice(); + remount(); + assertTrue(device.pushFile(copyResourceToTemp(filePath), outputPath)); + mPushedFiles.add(outputPath); + return this; + } + + /** Copies a file directly from the host file system to a path on device. */ + public SystemPreparer pushFile(File file, String outputPath) + throws DeviceNotAvailableException { + final ITestDevice device = mDeviceProvider.getDevice(); + remount(); + assertTrue(device.pushFile(file, outputPath)); + mPushedFiles.add(outputPath); + return this; + } + + /** Deletes the given path from the device */ + public SystemPreparer deleteFile(String file) throws DeviceNotAvailableException { + final ITestDevice device = mDeviceProvider.getDevice(); + remount(); + device.deleteFile(file); + return this; + } + + /** Installs an APK within the host test jar onto the device. */ + public SystemPreparer installResourceApk(String resourcePath, String packageName) + throws DeviceNotAvailableException, IOException { + final ITestDevice device = mDeviceProvider.getDevice(); + final File tmpFile = copyResourceToTemp(resourcePath); + final String result = device.installPackage(tmpFile, true /* reinstall */); + Assert.assertNull(result); + mInstalledPackages.add(packageName); + return this; + } + + /** Sets the enable state of an overlay package. */ + public SystemPreparer setOverlayEnabled(String packageName, boolean enabled) + throws DeviceNotAvailableException { + final ITestDevice device = mDeviceProvider.getDevice(); + final String enable = enabled ? "enable" : "disable"; + + // Wait for the overlay to change its enabled state. + final long endMillis = System.currentTimeMillis() + OVERLAY_ENABLE_TIMEOUT_MS; + String result; + while (System.currentTimeMillis() <= endMillis) { + device.executeShellCommand(String.format("cmd overlay %s %s", enable, packageName)); + result = device.executeShellCommand("cmd overlay dump isenabled " + + packageName); + if (((enabled) ? "true\n" : "false\n").equals(result)) { + return this; + } + + try { + Thread.sleep(200); + } catch (InterruptedException ignore) { + } + } + + throw new IllegalStateException(String.format("Failed to %s overlay %s:\n%s", enable, + packageName, device.executeShellCommand("cmd overlay list"))); + } + + /** Restarts the device and waits until after boot is completed. */ + public SystemPreparer reboot() throws DeviceNotAvailableException { + ITestDevice device = mDeviceProvider.getDevice(); + switch (mRebootStrategy) { + case FULL: + device.reboot(); + break; + case UNTIL_ONLINE: + device.rebootUntilOnline(); + break; + case USERSPACE: + device.rebootUserspace(); + break; + case USERSPACE_UNTIL_ONLINE: + device.rebootUserspaceUntilOnline(); + break; + case START_STOP: + device.executeShellCommand("stop"); + device.executeShellCommand("start"); + ITestDevice.RecoveryMode cachedRecoveryMode = device.getRecoveryMode(); + device.setRecoveryMode(ITestDevice.RecoveryMode.ONLINE); + + if (device.isEncryptionSupported()) { + if (device.isDeviceEncrypted()) { + LogUtil.CLog.e("Device is encrypted after userspace reboot!"); + device.unlockDevice(); + } + } + + device.setRecoveryMode(cachedRecoveryMode); + device.waitForDeviceAvailable(); + break; + } + return this; + } + + public SystemPreparer remount() throws DeviceNotAvailableException { + mTearDownRule.remount(); + return this; + } + + /** Copies a file within the host test jar to a temporary file on the host machine. */ + private File copyResourceToTemp(String resourcePath) throws IOException { + final File tempFile = mHostTempFolder.newFile(); + final ClassLoader classLoader = getClass().getClassLoader(); + try (InputStream assetIs = classLoader.getResource(resourcePath).openStream(); + FileOutputStream assetOs = new FileOutputStream(tempFile)) { + if (assetIs == null) { + throw new IllegalStateException("Failed to find resource " + resourcePath); + } + + int b; + while ((b = assetIs.read()) >= 0) { + assetOs.write(b); + } + } + + return tempFile; + } + + /** Removes installed packages and files that were pushed to the device. */ + @Override + protected void after() { + final ITestDevice device = mDeviceProvider.getDevice(); + try { + remount(); + for (final String file : mPushedFiles) { + device.deleteFile(file); + } + for (final String packageName : mInstalledPackages) { + device.uninstallPackage(packageName); + } + reboot(); + } catch (DeviceNotAvailableException e) { + Assert.fail(e.toString()); + } + } + + /** + * A hacky workaround since {@link org.junit.AfterClass} and {@link ClassRule} require static + * members. Will defer assignment of the actual {@link TestRule} to execute until after any + * test case has been run. + * + * In effect, this makes the {@link ITestDevice} to be accessible after all test cases have + * been executed, allowing {@link ITestDevice#reboot()} to be used to fully restore the device. + */ + public static class TestRuleDelegate implements TestRule { + + private boolean mThrowOnNull; + + @Nullable + private TestRule mTestRule; + + public TestRuleDelegate(boolean throwOnNull) { + mThrowOnNull = throwOnNull; + } + + public void setDelegate(TestRule testRule) { + mTestRule = testRule; + } + + @Override + public Statement apply(Statement base, Description description) { + if (mTestRule == null) { + if (mThrowOnNull) { + throw new IllegalStateException("TestRule delegate was not set"); + } else { + return new Statement() { + @Override + public void evaluate() throws Throwable { + base.evaluate(); + } + }; + } + } + + Statement statement = mTestRule.apply(base, description); + mTestRule = null; + return statement; + } + } + + /** + * Forces a full reboot at the end of the test class to restore any device state. + */ + private static class TearDownRule extends ExternalResource { + + private DeviceProvider mDeviceProvider; + private boolean mInitialized; + private boolean mWasVerityEnabled; + private boolean mWasAdbRoot; + private boolean mIsVerityEnabled; + + TearDownRule(DeviceProvider deviceProvider) { + mDeviceProvider = deviceProvider; + } + + @Override + protected void before() { + // This method will never be run + } + + @Override + protected void after() { + try { + initialize(); + ITestDevice device = mDeviceProvider.getDevice(); + if (mWasVerityEnabled != mIsVerityEnabled) { + device.executeShellCommand( + mWasVerityEnabled ? "enable-verity" : "disable-verity"); + } + device.reboot(); + if (!mWasAdbRoot) { + device.disableAdbRoot(); + } + } catch (DeviceNotAvailableException e) { + Assert.fail(e.toString()); + } + } + + /** + * Remount is done inside this class so that the verity state can be tracked. + */ + public void remount() throws DeviceNotAvailableException { + initialize(); + ITestDevice device = mDeviceProvider.getDevice(); + device.enableAdbRoot(); + if (mIsVerityEnabled) { + mIsVerityEnabled = false; + device.executeShellCommand("disable-verity"); + device.reboot(); + } + device.executeShellCommand("remount"); + device.waitForDeviceAvailable(); + } + + private void initialize() throws DeviceNotAvailableException { + if (mInitialized) { + return; + } + mInitialized = true; + ITestDevice device = mDeviceProvider.getDevice(); + mWasAdbRoot = device.isAdbRoot(); + device.enableAdbRoot(); + String veritySystem = device.getProperty("partition.system.verified"); + String verityVendor = device.getProperty("partition.vendor.verified"); + mWasVerityEnabled = (veritySystem != null && !veritySystem.isEmpty()) + || (verityVendor != null && !verityVendor.isEmpty()); + mIsVerityEnabled = mWasVerityEnabled; + } + } + + public interface DeviceProvider { + ITestDevice getDevice(); + } + + /** + * How to reboot the device. Ordered from slowest to fastest. + */ + public enum RebootStrategy { + /** @see ITestDevice#reboot() */ + FULL, + + /** @see ITestDevice#rebootUntilOnline() () */ + UNTIL_ONLINE, + + /** @see ITestDevice#rebootUserspace() */ + USERSPACE, + + /** @see ITestDevice#rebootUserspaceUntilOnline() () */ + USERSPACE_UNTIL_ONLINE, + + /** + * Uses shell stop && start to "reboot" the device. May leave invalid state after each test. + * Whether this matters or not depends on what's being tested. + */ + START_STOP + } +} |